mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merge V3.2 To HEAD
17894 : Merge from NEILM to V3.2 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@18233 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -34,6 +34,17 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<!-- this bean supports the registration of 'action filter' beans. These beans should extend the
|
||||||
|
baseActionFilter bean below, which provides automatic registration with the
|
||||||
|
default AsynchronousActionService. These filter beans are used to detect equivalent pending
|
||||||
|
actions and to drop unnecessary repeat actions.
|
||||||
|
See bean createThumbnailActionFilter for an example. -->
|
||||||
|
<bean name="baseActionFilter" abstract="true" init-method="init" class="org.alfresco.repo.action.AbstractAsynchronousActionFilter">
|
||||||
|
<property name="asynchronousActionExecutionQueue">
|
||||||
|
<ref bean="defaultAsynchronousActionExecutionQueue"/>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="defaultAsynchronousActionExecutionQueue" class="org.alfresco.repo.action.AsynchronousActionExecutionQueueImpl" init-method="init">
|
<bean id="defaultAsynchronousActionExecutionQueue" class="org.alfresco.repo.action.AsynchronousActionExecutionQueueImpl" init-method="init">
|
||||||
<property name="threadPoolExecutor">
|
<property name="threadPoolExecutor">
|
||||||
<ref bean="defaultAsyncThreadPool"/>
|
<ref bean="defaultAsyncThreadPool"/>
|
||||||
|
@@ -387,6 +387,7 @@ deployment.rmi.service.port=50507
|
|||||||
mbean.server.locateExistingServerIfPossible=true
|
mbean.server.locateExistingServerIfPossible=true
|
||||||
|
|
||||||
# External executable locations
|
# External executable locations
|
||||||
|
#ooo.exe=/Applications/OpenOffice.org.app/Contents/MacOS/soffice
|
||||||
ooo.exe=soffice
|
ooo.exe=soffice
|
||||||
ooo.user=${dir.root}/oouser
|
ooo.user=${dir.root}/oouser
|
||||||
|
|
||||||
|
@@ -184,4 +184,17 @@
|
|||||||
<property name="serviceRegistry" ref="ServiceRegistry"/>
|
<property name="serviceRegistry" ref="ServiceRegistry"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<!-- This action filter bean prevents multiple equivalent create-thumbnail actions from executing
|
||||||
|
simultaneously in the Asynchronous Action Execution Service -->
|
||||||
|
<bean id="createThumbnailActionFilter" class="org.alfresco.repo.action.CreateThumbnailActionFilter" parent="baseActionFilter">
|
||||||
|
<property name="name">
|
||||||
|
<value>preventMultipleCreateThumbnailActions</value>
|
||||||
|
</property>
|
||||||
|
<!-- The action-definition-name against which this bean will be registered. -->
|
||||||
|
<property name="actionDefinitionName">
|
||||||
|
<value>create-thumbnail</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
|
||||||
</beans>
|
</beans>
|
||||||
|
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
* As a special exception to the terms and conditions of version 2.0 of
|
||||||
|
* the GPL, you may redistribute this Program in connection with Free/Libre
|
||||||
|
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
||||||
|
* FLOSS exception. You should have recieved a copy of the text describing
|
||||||
|
* the FLOSS exception, and it is also available here:
|
||||||
|
* http://www.alfresco.com/legal/licensing"
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.action;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is the base filter class for asynchronous actions. These filters are used in identifying
|
||||||
|
* 'equivalent' actions in the asynchronous action execution service. By registering
|
||||||
|
* a subclass of this type, all actions of a given action-definition-name that are still pending
|
||||||
|
* (i.e. currently executing or in the queue awaiting execution) will be compared to any new action
|
||||||
|
* and if they are equal (as determined by the compare implementation defined herein) the newly
|
||||||
|
* submitted action will not be added to the queue and will be dropped.
|
||||||
|
*
|
||||||
|
* Concrete subclasses can be implemented and then dependency-injected using the spring-bean
|
||||||
|
* baseActionFilter as their parent.
|
||||||
|
*
|
||||||
|
* @author Neil McErlean
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAsynchronousActionFilter implements Comparator<OngoingAsyncAction>
|
||||||
|
{
|
||||||
|
private String name;
|
||||||
|
private String actionDefinitionName;
|
||||||
|
private AsynchronousActionExecutionQueueImpl asynchronousActionExecutionQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of this comparator.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of this comparator.
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the action definition name against which this comparator is registered.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getActionDefinitionName()
|
||||||
|
{
|
||||||
|
return this.actionDefinitionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionDefinitionName(String actionDefinitionName)
|
||||||
|
{
|
||||||
|
this.actionDefinitionName = actionDefinitionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsynchronousActionExecutionQueue(
|
||||||
|
AsynchronousActionExecutionQueueImpl asynchronousActionExecutionQueue)
|
||||||
|
{
|
||||||
|
this.asynchronousActionExecutionQueue = asynchronousActionExecutionQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
this.asynchronousActionExecutionQueue.registerActionFilter(this);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2005-2007 Alfresco Software Limited.
|
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -25,6 +25,8 @@
|
|||||||
package org.alfresco.repo.action;
|
package org.alfresco.repo.action;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -35,6 +37,7 @@ import org.alfresco.repo.action.evaluator.InCategoryEvaluator;
|
|||||||
import org.alfresco.repo.action.evaluator.NoConditionEvaluator;
|
import org.alfresco.repo.action.evaluator.NoConditionEvaluator;
|
||||||
import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation;
|
import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation;
|
||||||
import org.alfresco.repo.action.executer.ActionExecuter;
|
import org.alfresco.repo.action.executer.ActionExecuter;
|
||||||
|
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
|
||||||
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
|
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
|
||||||
import org.alfresco.repo.action.executer.CheckInActionExecuter;
|
import org.alfresco.repo.action.executer.CheckInActionExecuter;
|
||||||
import org.alfresco.repo.action.executer.CheckOutActionExecuter;
|
import org.alfresco.repo.action.executer.CheckOutActionExecuter;
|
||||||
@@ -42,6 +45,7 @@ import org.alfresco.repo.action.executer.CompositeActionExecuter;
|
|||||||
import org.alfresco.repo.action.executer.MoveActionExecuter;
|
import org.alfresco.repo.action.executer.MoveActionExecuter;
|
||||||
import org.alfresco.repo.action.executer.ScriptActionExecuter;
|
import org.alfresco.repo.action.executer.ScriptActionExecuter;
|
||||||
import org.alfresco.repo.content.MimetypeMap;
|
import org.alfresco.repo.content.MimetypeMap;
|
||||||
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.service.cmr.action.Action;
|
import org.alfresco.service.cmr.action.Action;
|
||||||
import org.alfresco.service.cmr.action.ActionCondition;
|
import org.alfresco.service.cmr.action.ActionCondition;
|
||||||
@@ -49,6 +53,7 @@ import org.alfresco.service.cmr.action.ActionConditionDefinition;
|
|||||||
import org.alfresco.service.cmr.action.ActionDefinition;
|
import org.alfresco.service.cmr.action.ActionDefinition;
|
||||||
import org.alfresco.service.cmr.action.CompositeAction;
|
import org.alfresco.service.cmr.action.CompositeAction;
|
||||||
import org.alfresco.service.cmr.action.CompositeActionCondition;
|
import org.alfresco.service.cmr.action.CompositeActionCondition;
|
||||||
|
import org.alfresco.service.cmr.action.ParameterDefinition;
|
||||||
import org.alfresco.service.cmr.repository.ContentData;
|
import org.alfresco.service.cmr.repository.ContentData;
|
||||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
@@ -56,6 +61,7 @@ import org.alfresco.service.cmr.repository.NodeService;
|
|||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
|
import org.alfresco.util.ApplicationContextHelper;
|
||||||
import org.alfresco.util.BaseAlfrescoSpringTest;
|
import org.alfresco.util.BaseAlfrescoSpringTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,12 +75,28 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
|
|
||||||
private NodeRef nodeRef;
|
private NodeRef nodeRef;
|
||||||
private NodeRef folder;
|
private NodeRef folder;
|
||||||
|
private RetryingTransactionHelper transactionHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] getConfigLocations()
|
||||||
|
{
|
||||||
|
String[] existingConfigLocations = ApplicationContextHelper.CONFIG_LOCATIONS;
|
||||||
|
|
||||||
|
List<String> locations = Arrays.asList(existingConfigLocations);
|
||||||
|
List<String> mutableLocationsList = new ArrayList<String>(locations);
|
||||||
|
mutableLocationsList.add("classpath:org/alfresco/repo/action/test-action-services-context.xml");
|
||||||
|
|
||||||
|
String[] result = mutableLocationsList.toArray(new String[mutableLocationsList.size()]);
|
||||||
|
return (String[]) result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSetUpInTransaction() throws Exception
|
protected void onSetUpInTransaction() throws Exception
|
||||||
{
|
{
|
||||||
super.onSetUpInTransaction();
|
super.onSetUpInTransaction();
|
||||||
|
|
||||||
|
this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper");
|
||||||
|
|
||||||
// Create the node used for tests
|
// Create the node used for tests
|
||||||
this.nodeRef = this.nodeService.createNode(
|
this.nodeRef = this.nodeService.createNode(
|
||||||
this.rootNodeRef,
|
this.rootNodeRef,
|
||||||
@@ -761,6 +783,85 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
* Test asynchronous actions
|
* Test asynchronous actions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test checks that a series of "equivalent" actions submitted for asynchronous execution
|
||||||
|
* will be correctly filtered so that no 2 equivalent actions are executed at the same time.
|
||||||
|
*/
|
||||||
|
public void testAsyncLongRunningActionsFilter()
|
||||||
|
{
|
||||||
|
setComplete();
|
||||||
|
endTransaction();
|
||||||
|
|
||||||
|
final SleepActionExecuter sleepAction = (SleepActionExecuter)applicationContext.getBean("sleep-action");
|
||||||
|
assertNotNull(sleepAction);
|
||||||
|
|
||||||
|
final int actionSubmissonCount = 4; // Rather arbitrary count.
|
||||||
|
for (int i = 0; i < actionSubmissonCount; i ++)
|
||||||
|
{
|
||||||
|
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
|
||||||
|
{
|
||||||
|
public Void execute() throws Throwable
|
||||||
|
{
|
||||||
|
Action action = actionService.createAction(SleepActionExecuter.NAME);
|
||||||
|
action.setExecuteAsynchronously(true);
|
||||||
|
|
||||||
|
actionService.executeAction(action, nodeRef);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait long enough for previous action(s) to have executed and then submit another
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(sleepAction.getSleepMs() * actionSubmissonCount + 1000); // Enough time for all actions and an extra second for luck.
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignored)
|
||||||
|
{
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
|
||||||
|
{
|
||||||
|
public Void execute() throws Throwable
|
||||||
|
{
|
||||||
|
Action action = actionService.createAction(SleepActionExecuter.NAME);
|
||||||
|
action.setExecuteAsynchronously(true);
|
||||||
|
|
||||||
|
actionService.executeAction(action, nodeRef);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(sleepAction.getSleepMs() + 2000); // Enough time for latest action and an extra 2 seconds for luck.
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignored)
|
||||||
|
{
|
||||||
|
// intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int sleepTime = 0; // Do not sleep during execution as the Action itself sleeps.
|
||||||
|
int maxTries = 1;
|
||||||
|
postAsyncActionTest(
|
||||||
|
this.transactionService,
|
||||||
|
sleepTime,
|
||||||
|
maxTries,
|
||||||
|
new AsyncTest()
|
||||||
|
{
|
||||||
|
public String executeTest()
|
||||||
|
{
|
||||||
|
final int expectedResult = 2;
|
||||||
|
int actualResult = sleepAction.getTimesExecuted();
|
||||||
|
return actualResult == expectedResult ? null : "Expected timesExecuted " + expectedResult + " was " + actualResult;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test asynchronous execute action
|
* Test asynchronous execute action
|
||||||
*/
|
*/
|
||||||
@@ -786,10 +887,10 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
10,
|
10,
|
||||||
new AsyncTest()
|
new AsyncTest()
|
||||||
{
|
{
|
||||||
public boolean executeTest()
|
public String executeTest()
|
||||||
{
|
{
|
||||||
return (
|
boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE);
|
||||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE));
|
return result ? null : "Expected aspect Classifiable";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -828,11 +929,11 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
10,
|
10,
|
||||||
new AsyncTest()
|
new AsyncTest()
|
||||||
{
|
{
|
||||||
public boolean executeTest()
|
public String executeTest()
|
||||||
{
|
{
|
||||||
return (
|
boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE) &&
|
||||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE) &&
|
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE);
|
||||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE));
|
return result ? null : "Expected aspects Versionable & Lockable";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -872,8 +973,8 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
boolean done = false;
|
String errorMsg = null;
|
||||||
while (done == false && tries < maxTries)
|
while (errorMsg == null && tries < maxTries)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -883,16 +984,16 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
// Sleep for a bit
|
// Sleep for a bit
|
||||||
Thread.sleep(sleepTime);
|
Thread.sleep(sleepTime);
|
||||||
|
|
||||||
done = (transactionService.getRetryingTransactionHelper().doInTransaction(
|
errorMsg = (transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||||
new RetryingTransactionCallback<Boolean>()
|
new RetryingTransactionCallback<String>()
|
||||||
{
|
{
|
||||||
public Boolean execute()
|
public String execute()
|
||||||
{
|
{
|
||||||
// See if the action has been performed
|
// See if the action has been performed
|
||||||
boolean done = test.executeTest();
|
String done = test.executeTest();
|
||||||
return done;
|
return done;
|
||||||
}
|
}
|
||||||
})).booleanValue();
|
}));
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (InterruptedException e)
|
||||||
{
|
{
|
||||||
@@ -901,9 +1002,9 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (done == false)
|
if (errorMsg != null)
|
||||||
{
|
{
|
||||||
throw new RuntimeException("Asynchronous action was not executed.");
|
throw new RuntimeException("Asynchronous action was not executed. " + errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable exception)
|
catch (Throwable exception)
|
||||||
@@ -918,7 +1019,11 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
*/
|
*/
|
||||||
public interface AsyncTest
|
public interface AsyncTest
|
||||||
{
|
{
|
||||||
boolean executeTest();
|
/**
|
||||||
|
*
|
||||||
|
* @return <code>null</code> if the test succeeded, else an error message for use in JUnit report.
|
||||||
|
*/
|
||||||
|
String executeTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ===================================================================================
|
/** ===================================================================================
|
||||||
@@ -1009,10 +1114,10 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
10,
|
10,
|
||||||
new AsyncTest()
|
new AsyncTest()
|
||||||
{
|
{
|
||||||
public boolean executeTest()
|
public String executeTest()
|
||||||
{
|
{
|
||||||
return (
|
boolean result = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE);
|
||||||
ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE));
|
return result == true ? null : "Expected aspect Classifiable";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1040,4 +1145,68 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
|||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is only used during JUnit testing.
|
||||||
|
*
|
||||||
|
* @author Neil Mc Erlean
|
||||||
|
*/
|
||||||
|
public static class SleepActionFilter extends AbstractAsynchronousActionFilter
|
||||||
|
{
|
||||||
|
public int compare(OngoingAsyncAction sae1, OngoingAsyncAction sae2)
|
||||||
|
{
|
||||||
|
// Sleep actions are always equivalent.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is only intended for use in JUnit tests.
|
||||||
|
*
|
||||||
|
* @author Neil McErlean.
|
||||||
|
*/
|
||||||
|
public static class SleepActionExecuter extends ActionExecuterAbstractBase
|
||||||
|
{
|
||||||
|
public static final String NAME = "sleep-action";
|
||||||
|
private int sleepMs;
|
||||||
|
|
||||||
|
private int timesExecuted = 0;
|
||||||
|
private void incrementTimesExecutedCount() {timesExecuted++;}
|
||||||
|
public int getTimesExecuted() {return timesExecuted;}
|
||||||
|
|
||||||
|
public int getSleepMs()
|
||||||
|
{
|
||||||
|
return sleepMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSleepMs(int sleepMs)
|
||||||
|
{
|
||||||
|
this.sleepMs = sleepMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add parameter definitions
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
|
||||||
|
{
|
||||||
|
// Intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(sleepMs);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignored)
|
||||||
|
{
|
||||||
|
// Intentionally empty
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
incrementTimesExecutedCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -25,8 +25,13 @@
|
|||||||
package org.alfresco.repo.action;
|
package org.alfresco.repo.action;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Vector;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
import org.alfresco.error.StackTraceUtil;
|
import org.alfresco.error.StackTraceUtil;
|
||||||
@@ -47,6 +52,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
|||||||
import org.alfresco.service.cmr.repository.NodeService;
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
@@ -64,7 +70,17 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
|||||||
private TransactionService transactionService;
|
private TransactionService transactionService;
|
||||||
private AuthenticationContext authenticationContext;
|
private AuthenticationContext authenticationContext;
|
||||||
private PolicyComponent policyComponent;
|
private PolicyComponent policyComponent;
|
||||||
private NodeService nodeService;
|
private Map<String, AbstractAsynchronousActionFilter>
|
||||||
|
actionFilters = new HashMap<String, AbstractAsynchronousActionFilter>();
|
||||||
|
|
||||||
|
private NodeService nodeService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We keep a record of ongoing asynchronous actions (this includes those being executed and
|
||||||
|
* those that are in the queue).
|
||||||
|
* This needs to be thread-safe - hence the Vector.
|
||||||
|
*/
|
||||||
|
List<OngoingAsyncAction> ongoingActions = new Vector<OngoingAsyncAction>();
|
||||||
|
|
||||||
// Policy delegates
|
// Policy delegates
|
||||||
private ClassPolicyDelegate<OnAsyncActionExecute> onAsyncActionExecuteDelegate;
|
private ClassPolicyDelegate<OnAsyncActionExecute> onAsyncActionExecuteDelegate;
|
||||||
@@ -169,6 +185,37 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
|||||||
return qnames;
|
return qnames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method registers an action filter, which can be used to prevent unwanted or unnecessary
|
||||||
|
* asynchronous actions from being scheduled for execution.
|
||||||
|
*
|
||||||
|
* @param filter the filter implementation.
|
||||||
|
*/
|
||||||
|
public void registerActionFilter(AbstractAsynchronousActionFilter filter)
|
||||||
|
{
|
||||||
|
String filterName = filter.getName();
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Registered asynchronous action filter ")
|
||||||
|
.append(filter.getName()).append(" for action ")
|
||||||
|
.append(filter.getActionDefinitionName());
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractAsynchronousActionFilter existingFilter = actionFilters.get(filterName);
|
||||||
|
if (logger.isDebugEnabled() && existingFilter != null)
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("This replaces previous filter ")
|
||||||
|
.append(existingFilter.getName());
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actionFilters.put(filter.getName(), filter);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@@ -185,6 +232,22 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
|||||||
public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef,
|
public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef,
|
||||||
boolean checkConditions, Set<String> actionChain, NodeRef actionExecutionHistoryNodeRef)
|
boolean checkConditions, Set<String> actionChain, NodeRef actionExecutionHistoryNodeRef)
|
||||||
{
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Received request to execute async action ").append(action.getActionDefinitionName())
|
||||||
|
.append(" on ").append(actionedUponNodeRef);
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
|
||||||
|
msg = new StringBuilder();
|
||||||
|
msg.append("ThreadPool's active count = ").append(this.threadPoolExecutor.getActiveCount());
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
|
||||||
|
msg = new StringBuilder();
|
||||||
|
msg.append("ThreadPool's queue size = ").append(this.threadPoolExecutor.getQueue().size());
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
Set<RuleServiceImpl.ExecutedRuleData> executedRules =
|
Set<RuleServiceImpl.ExecutedRuleData> executedRules =
|
||||||
(Set<RuleServiceImpl.ExecutedRuleData>) AlfrescoTransactionSupport.getResource("RuleServiceImpl.ExecutedRules");
|
(Set<RuleServiceImpl.ExecutedRuleData>) AlfrescoTransactionSupport.getResource("RuleServiceImpl.ExecutedRules");
|
||||||
Runnable runnable = new ActionExecutionWrapper(
|
Runnable runnable = new ActionExecutionWrapper(
|
||||||
@@ -195,7 +258,60 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
|||||||
actionExecutionHistoryNodeRef,
|
actionExecutionHistoryNodeRef,
|
||||||
actionChain,
|
actionChain,
|
||||||
executedRules);
|
executedRules);
|
||||||
threadPoolExecutor.execute(runnable);
|
|
||||||
|
// Consider whether this action should be filtered out by one of the registered filters.
|
||||||
|
boolean newActionShouldBeFilteredOut = false;
|
||||||
|
OngoingAsyncAction nodeBeingNewlyActioned = new OngoingAsyncAction(actionedUponNodeRef, action);
|
||||||
|
|
||||||
|
for (Entry<String, AbstractAsynchronousActionFilter> entry : actionFilters.entrySet())
|
||||||
|
{
|
||||||
|
AbstractAsynchronousActionFilter comparator = entry.getValue();
|
||||||
|
String actionDefinitionName = comparator.getActionDefinitionName();
|
||||||
|
|
||||||
|
if (actionDefinitionName.equals(action.getActionDefinitionName()) == false)
|
||||||
|
{
|
||||||
|
// We're only interested in registered actions with the same name as this one.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
// Now we've found a registered action that matches the current one.
|
||||||
|
// So we'll go through the actions that are ongoing and consider them for matches with this one.
|
||||||
|
for (OngoingAsyncAction ongoingAction : this.ongoingActions)
|
||||||
|
{
|
||||||
|
if (comparator.compare(ongoingAction, nodeBeingNewlyActioned) == 0)
|
||||||
|
{
|
||||||
|
newActionShouldBeFilteredOut = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newActionShouldBeFilteredOut)
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Dropping action ").append(action).append(" as equivalent is ongoing.");
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Executing action ").append(action);
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue it and do it.
|
||||||
|
ongoingActions.add(nodeBeingNewlyActioned);
|
||||||
|
threadPoolExecutor.execute(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
@@ -214,8 +330,19 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleAsyncActionIsCompleted(NodeRef n, Action action) {
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Completed action ").append(action);
|
||||||
|
logger.debug(msg.toString());
|
||||||
|
}
|
||||||
|
OngoingAsyncAction ongoing = new OngoingAsyncAction(n, action);
|
||||||
|
ongoingActions.remove(ongoing);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tansaction listener used to invoke callback policies
|
* Transaction listener used to invoke callback policies
|
||||||
*/
|
*/
|
||||||
public class CallbackTransactionListener extends TransactionListenerAdapter
|
public class CallbackTransactionListener extends TransactionListenerAdapter
|
||||||
{
|
{
|
||||||
@@ -384,6 +511,7 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
|||||||
{
|
{
|
||||||
logger.error("Failed to execute asynchronous action: " + action, exception);
|
logger.error("Failed to execute asynchronous action: " + action, exception);
|
||||||
}
|
}
|
||||||
|
handleAsyncActionIsCompleted(actionedUponNodeRef, action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
* As a special exception to the terms and conditions of version 2.0 of
|
||||||
|
* the GPL, you may redistribute this Program in connection with Free/Libre
|
||||||
|
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
||||||
|
* FLOSS exception. You should have recieved a copy of the text describing
|
||||||
|
* the FLOSS exception, and it is also available here:
|
||||||
|
* http://www.alfresco.com/legal/licensing"
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.action;
|
||||||
|
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This filter class is used to identify equivalent create-thumbnail actions.
|
||||||
|
* Two create-thumbnail actions are considered equivalent if they are on the same NodeRef and
|
||||||
|
* if they have the same thumbnail-name parameter.
|
||||||
|
*
|
||||||
|
* @author Neil McErlean
|
||||||
|
*/
|
||||||
|
public class CreateThumbnailActionFilter extends AbstractAsynchronousActionFilter
|
||||||
|
{
|
||||||
|
private static final String PARAM_THUMBNAIL_NAME = "thumbnail-name";
|
||||||
|
|
||||||
|
public int compare(OngoingAsyncAction nodeAction1, OngoingAsyncAction nodeAction2)
|
||||||
|
{
|
||||||
|
NodeRef n1 = nodeAction1.getNodeRef();
|
||||||
|
NodeRef n2 = nodeAction2.getNodeRef();
|
||||||
|
if (n1.equals(n2) == false)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String thName1 = (String)nodeAction1.getAction().getParameterValue(PARAM_THUMBNAIL_NAME);
|
||||||
|
String thName2 = (String)nodeAction2.getAction().getParameterValue(PARAM_THUMBNAIL_NAME);
|
||||||
|
|
||||||
|
return thName1.compareTo(thName2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
source/java/org/alfresco/repo/action/OngoingAsyncAction.java
Normal file
80
source/java/org/alfresco/repo/action/OngoingAsyncAction.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
* As a special exception to the terms and conditions of version 2.0 of
|
||||||
|
* the GPL, you may redistribute this Program in connection with Free/Libre
|
||||||
|
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
||||||
|
* FLOSS exception. You should have received a copy of the text describing
|
||||||
|
* the FLOSS exception, and it is also available here:
|
||||||
|
* http://www.alfresco.com/legal/licensing"
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.action;
|
||||||
|
|
||||||
|
import org.alfresco.service.cmr.action.Action;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.util.EqualsHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents an ongoing asynchronous action.
|
||||||
|
*/
|
||||||
|
public class OngoingAsyncAction
|
||||||
|
{
|
||||||
|
private final NodeRef node;
|
||||||
|
private final Action action;
|
||||||
|
|
||||||
|
public OngoingAsyncAction(NodeRef node, Action action)
|
||||||
|
{
|
||||||
|
this.node = node;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeRef getNodeRef()
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action getAction()
|
||||||
|
{
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object otherObj)
|
||||||
|
{
|
||||||
|
if (otherObj == null || !otherObj.getClass().equals(this.getClass()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OngoingAsyncAction otherNodeBeingActioned = (OngoingAsyncAction)otherObj;
|
||||||
|
|
||||||
|
return EqualsHelper.nullSafeEquals(this.node, otherNodeBeingActioned.node) &&
|
||||||
|
EqualsHelper.nullSafeEquals(this.action.getActionDefinitionName(), otherNodeBeingActioned.action.getActionDefinitionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.node.hashCode() + 7 * this.action.getActionDefinitionName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append(node).append(", ").append(action.getActionDefinitionName());
|
||||||
|
return msg.toString();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||||
|
|
||||||
|
<beans>
|
||||||
|
<bean id="sleep-action" class="org.alfresco.repo.action.ActionServiceImplTest$SleepActionExecuter" parent="action-executer">
|
||||||
|
<property name="publicAction">
|
||||||
|
<value>false</value>
|
||||||
|
</property>
|
||||||
|
<property name="sleepMs">
|
||||||
|
<value>1000</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="sleepActionFilter" class="org.alfresco.repo.action.ActionServiceImplTest$SleepActionFilter" parent="baseActionFilter">
|
||||||
|
<property name="name">
|
||||||
|
<value>preventMultipleSleepActions</value>
|
||||||
|
</property>
|
||||||
|
<property name="actionDefinitionName">
|
||||||
|
<value>sleep-action</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
</beans>
|
@@ -171,7 +171,7 @@ public abstract class AbstractContentTransformer2 extends ContentTransformerHelp
|
|||||||
// Make sure that this transformation gets set back i.t.o. time taken.
|
// Make sure that this transformation gets set back i.t.o. time taken.
|
||||||
// This will ensure that transformers that compete for the same transformation
|
// This will ensure that transformers that compete for the same transformation
|
||||||
// will be prejudiced against transformers that tend to fail
|
// will be prejudiced against transformers that tend to fail
|
||||||
recordTime(10000); // 10 seconds, i.e. rubbish
|
recordTime(60 * 1000); // 1 minute, i.e. rubbish
|
||||||
|
|
||||||
throw new ContentIOException("Content conversion failed: \n" +
|
throw new ContentIOException("Content conversion failed: \n" +
|
||||||
" reader: " + reader + "\n" +
|
" reader: " + reader + "\n" +
|
||||||
|
@@ -295,11 +295,12 @@ public class RuleServiceCoverageTest extends TestCase
|
|||||||
100,
|
100,
|
||||||
new AsyncTest()
|
new AsyncTest()
|
||||||
{
|
{
|
||||||
public boolean executeTest()
|
public String executeTest()
|
||||||
{
|
{
|
||||||
return RuleServiceCoverageTest.this.nodeService.hasAspect(
|
boolean result = RuleServiceCoverageTest.this.nodeService.hasAspect(
|
||||||
newNodeRef,
|
newNodeRef,
|
||||||
ContentModel.ASPECT_VERSIONABLE);
|
ContentModel.ASPECT_VERSIONABLE);
|
||||||
|
return result ? "" : "Expected aspect Versionable";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user