mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
[MNT-21901] Prevent update preferences concurrency errors (#821)
* [MNT-21901] Added PersistenceException to retry exceptions. Added validation to get retry cause * [MNT-21901] Check exception message only if it is JavaScriptException * [MNT-21901] Removed unit test from repository. Added new test on remote-api. Changed ExceptionStackUtil validation. * [MNT-21901] Improved JavascriptException validation. Added exception delimiter to prevent accepting exceptions with the same partial name.
This commit is contained in:
@@ -25,6 +25,9 @@ package org.alfresco.error;
|
|||||||
*/
|
*/
|
||||||
public class ExceptionStackUtil
|
public class ExceptionStackUtil
|
||||||
{
|
{
|
||||||
|
private static final String JAVASCRIPT_EXCEPTION = "org.mozilla.javascript.JavaScriptException";
|
||||||
|
private static final String EXCEPTION_DELIMITER = ":";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches through the exception stack of the given throwable to find any instance
|
* Searches through the exception stack of the given throwable to find any instance
|
||||||
* of the possible cause. The top-level throwable will also be tested.
|
* of the possible cause. The top-level throwable will also be tested.
|
||||||
@@ -38,10 +41,17 @@ public class ExceptionStackUtil
|
|||||||
{
|
{
|
||||||
while (throwable != null)
|
while (throwable != null)
|
||||||
{
|
{
|
||||||
|
Class<?> throwableClass = throwable.getClass();
|
||||||
|
|
||||||
|
boolean isJavaScriptException = throwableClass.getName().contains(JAVASCRIPT_EXCEPTION);
|
||||||
|
String throwableMsg = throwable.getMessage() != null ? throwable.getMessage() : "";
|
||||||
|
|
||||||
for (Class<?> possibleCauseClass : possibleCauses)
|
for (Class<?> possibleCauseClass : possibleCauses)
|
||||||
{
|
{
|
||||||
Class<?> throwableClass = throwable.getClass();
|
String possibleCauseClassName = possibleCauseClass.getName();
|
||||||
if (possibleCauseClass.isAssignableFrom(throwableClass))
|
|
||||||
|
if (possibleCauseClass.isAssignableFrom(throwableClass)
|
||||||
|
|| (isJavaScriptException && throwableMsg.contains(possibleCauseClassName + EXCEPTION_DELIMITER)))
|
||||||
{
|
{
|
||||||
// We have a match
|
// We have a match
|
||||||
return throwable;
|
return throwable;
|
||||||
|
@@ -55,10 +55,14 @@ public class PreferenceServiceTest extends BaseWebScriptTest
|
|||||||
private PersonService personService;
|
private PersonService personService;
|
||||||
|
|
||||||
private static final String USER_ONE = "PreferenceTestOne" + System.currentTimeMillis();
|
private static final String USER_ONE = "PreferenceTestOne" + System.currentTimeMillis();
|
||||||
|
private static final String USER_TWO = "PreferenceTestTwo" + System.currentTimeMillis();
|
||||||
private static final String USER_BAD = "PreferenceTestBad" + System.currentTimeMillis();
|
private static final String USER_BAD = "PreferenceTestBad" + System.currentTimeMillis();
|
||||||
|
|
||||||
private static final String URL = "/api/people/" + USER_ONE + "/preferences";;
|
private static final String URL = "/api/people/" + USER_ONE + "/preferences";
|
||||||
|
private static final String URL2 = "/api/people/" + USER_TWO + "/preferences";
|
||||||
|
|
||||||
|
private static final int NUM_THREADS = 3;
|
||||||
|
private static Integer THREADS_FINISHED = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception
|
protected void setUp() throws Exception
|
||||||
@@ -75,6 +79,7 @@ public class PreferenceServiceTest extends BaseWebScriptTest
|
|||||||
|
|
||||||
// Create users
|
// Create users
|
||||||
createUser(USER_ONE);
|
createUser(USER_ONE);
|
||||||
|
createUser(USER_TWO);
|
||||||
createUser(USER_BAD);
|
createUser(USER_BAD);
|
||||||
|
|
||||||
// Do tests as user one
|
// Do tests as user one
|
||||||
@@ -103,7 +108,6 @@ public class PreferenceServiceTest extends BaseWebScriptTest
|
|||||||
{
|
{
|
||||||
super.tearDown();
|
super.tearDown();
|
||||||
this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
|
this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPreferences() throws Exception
|
public void testPreferences() throws Exception
|
||||||
@@ -193,6 +197,64 @@ public class PreferenceServiceTest extends BaseWebScriptTest
|
|||||||
sendRequest(new GetRequest("/api/people/noExistUser/preferences"), 404);
|
sendRequest(new GetRequest("/api/people/noExistUser/preferences"), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPreferencesMNT21901() throws Exception
|
||||||
|
{
|
||||||
|
String[] body = {
|
||||||
|
"{\"org\":{\"alfresco\":{\"share\":{\"forum\":{\"summary\":{\"dashlet\":{\"component-1-5\":{\"topics\":\"mine\"}}}}}}}}",
|
||||||
|
"{\"org\":{\"alfresco\":{\"share\":{\"forum\":{\"summary\":{\"dashlet\":{\"component-2-5\":{\"topics\":\"mine\"}}}}}}}}",
|
||||||
|
"{\"org\":{\"alfresco\":{\"share\":{\"forum\":{\"summary\":{\"dashlet\":{\"component-2-5\":{\"history\":\"1\"}}}}}}}}"
|
||||||
|
};
|
||||||
|
|
||||||
|
Thread[] threads = new Thread[NUM_THREADS];
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_THREADS; i++)
|
||||||
|
{
|
||||||
|
UpdatePreferencesThread t = new UpdatePreferencesThread(i, body[i]);
|
||||||
|
threads[i] = t;
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < threads.length; i++)
|
||||||
|
{
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticationComponent.setCurrentUser(USER_ONE);
|
||||||
|
|
||||||
|
if (THREADS_FINISHED != NUM_THREADS)
|
||||||
|
{
|
||||||
|
fail("An error has occurred when updating preferences in concurrency context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdatePreferencesThread extends Thread
|
||||||
|
{
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
UpdatePreferencesThread(int threadNum, String body)
|
||||||
|
{
|
||||||
|
super(UpdatePreferencesThread.class.getSimpleName() + "-" + threadNum);
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
authenticationComponent.setCurrentUser(USER_TWO);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Response resp = sendRequest(new PostRequest(URL2, body, "application/json"), 200);
|
||||||
|
assertEquals(0, resp.getContentLength());
|
||||||
|
THREADS_FINISHED++;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Intentionally empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private JSONObject getPreferenceObj() throws JSONException
|
private JSONObject getPreferenceObj() throws JSONException
|
||||||
{
|
{
|
||||||
JSONObject jsonObject = new JSONObject();
|
JSONObject jsonObject = new JSONObject();
|
||||||
@@ -208,5 +270,4 @@ public class PreferenceServiceTest extends BaseWebScriptTest
|
|||||||
assertEquals(10, jsonObject.get("numberValue"));
|
assertEquals(10, jsonObject.get("numberValue"));
|
||||||
assertEquals(BigDecimal.valueOf(3.142), jsonObject.get("numberValue2"));
|
assertEquals(BigDecimal.valueOf(3.142), jsonObject.get("numberValue2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -51,6 +51,7 @@ import org.alfresco.service.license.LicenseIntegrityException;
|
|||||||
import org.alfresco.util.LockHelper.LockTryException;
|
import org.alfresco.util.LockHelper.LockTryException;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.ibatis.exceptions.PersistenceException;
|
||||||
import org.apache.ibatis.exceptions.TooManyResultsException;
|
import org.apache.ibatis.exceptions.TooManyResultsException;
|
||||||
import org.springframework.aop.MethodBeforeAdvice;
|
import org.springframework.aop.MethodBeforeAdvice;
|
||||||
import org.springframework.aop.framework.ProxyFactory;
|
import org.springframework.aop.framework.ProxyFactory;
|
||||||
@@ -105,7 +106,8 @@ public class RetryingTransactionHelper
|
|||||||
DataIntegrityViolationException.class,
|
DataIntegrityViolationException.class,
|
||||||
LicenseIntegrityException.class,
|
LicenseIntegrityException.class,
|
||||||
TooManyResultsException.class, // Expected one result but found multiple (bad key alert)
|
TooManyResultsException.class, // Expected one result but found multiple (bad key alert)
|
||||||
LockTryException.class
|
LockTryException.class,
|
||||||
|
PersistenceException.class
|
||||||
};
|
};
|
||||||
|
|
||||||
List<Class<?>> retryExceptions = new ArrayList<Class<?>>();
|
List<Class<?>> retryExceptions = new ArrayList<Class<?>>();
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
function testPreferences()
|
||||||
|
{
|
||||||
|
var preferences = {
|
||||||
|
"org.alfresco.share.forum.summary.dashlet.component-1-3.history": "1"
|
||||||
|
};
|
||||||
|
|
||||||
|
preferenceService.setPreferences(username, preferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute tests
|
||||||
|
testPreferences();
|
Reference in New Issue
Block a user