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>
|
||||
</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">
|
||||
<property name="threadPoolExecutor">
|
||||
<ref bean="defaultAsyncThreadPool"/>
|
||||
|
@@ -387,6 +387,7 @@ deployment.rmi.service.port=50507
|
||||
mbean.server.locateExistingServerIfPossible=true
|
||||
|
||||
# External executable locations
|
||||
#ooo.exe=/Applications/OpenOffice.org.app/Contents/MacOS/soffice
|
||||
ooo.exe=soffice
|
||||
ooo.user=${dir.root}/oouser
|
||||
|
||||
|
@@ -183,5 +183,18 @@
|
||||
</property>
|
||||
<property name="serviceRegistry" ref="ServiceRegistry"/>
|
||||
</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>
|
||||
|
@@ -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
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -25,6 +25,8 @@
|
||||
package org.alfresco.repo.action;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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.compare.ComparePropertyValueOperation;
|
||||
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.CheckInActionExecuter;
|
||||
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.ScriptActionExecuter;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
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.CompositeAction;
|
||||
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.ContentWriter;
|
||||
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.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.alfresco.util.BaseAlfrescoSpringTest;
|
||||
|
||||
/**
|
||||
@@ -69,12 +75,28 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
|
||||
private NodeRef nodeRef;
|
||||
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
|
||||
protected void onSetUpInTransaction() throws Exception
|
||||
{
|
||||
super.onSetUpInTransaction();
|
||||
|
||||
this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper");
|
||||
|
||||
// Create the node used for tests
|
||||
this.nodeRef = this.nodeService.createNode(
|
||||
this.rootNodeRef,
|
||||
@@ -761,6 +783,85 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
* 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
|
||||
*/
|
||||
@@ -786,10 +887,10 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
10,
|
||||
new AsyncTest()
|
||||
{
|
||||
public boolean executeTest()
|
||||
public String executeTest()
|
||||
{
|
||||
return (
|
||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE));
|
||||
boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE);
|
||||
return result ? null : "Expected aspect Classifiable";
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -828,11 +929,11 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
10,
|
||||
new AsyncTest()
|
||||
{
|
||||
public boolean executeTest()
|
||||
public String executeTest()
|
||||
{
|
||||
return (
|
||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE) &&
|
||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE));
|
||||
boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE) &&
|
||||
finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE);
|
||||
return result ? null : "Expected aspects Versionable & Lockable";
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -872,8 +973,8 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
try
|
||||
{
|
||||
int tries = 0;
|
||||
boolean done = false;
|
||||
while (done == false && tries < maxTries)
|
||||
String errorMsg = null;
|
||||
while (errorMsg == null && tries < maxTries)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -883,16 +984,16 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
// Sleep for a bit
|
||||
Thread.sleep(sleepTime);
|
||||
|
||||
done = (transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Boolean>()
|
||||
errorMsg = (transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<String>()
|
||||
{
|
||||
public Boolean execute()
|
||||
public String execute()
|
||||
{
|
||||
// See if the action has been performed
|
||||
boolean done = test.executeTest();
|
||||
String done = test.executeTest();
|
||||
return done;
|
||||
}
|
||||
})).booleanValue();
|
||||
}));
|
||||
}
|
||||
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)
|
||||
@@ -918,7 +1019,11 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
|
||||
*/
|
||||
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,
|
||||
new AsyncTest()
|
||||
{
|
||||
public boolean executeTest()
|
||||
public String executeTest()
|
||||
{
|
||||
return (
|
||||
ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE));
|
||||
boolean result = 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
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.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -64,7 +70,17 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
||||
private TransactionService transactionService;
|
||||
private AuthenticationContext authenticationContext;
|
||||
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
|
||||
private ClassPolicyDelegate<OnAsyncActionExecute> onAsyncActionExecuteDelegate;
|
||||
@@ -169,6 +185,37 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
||||
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}
|
||||
*/
|
||||
@@ -185,6 +232,22 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
||||
public void executeAction(RuntimeActionService actionService, Action action, NodeRef actionedUponNodeRef,
|
||||
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>) AlfrescoTransactionSupport.getResource("RuleServiceImpl.ExecutedRules");
|
||||
Runnable runnable = new ActionExecutionWrapper(
|
||||
@@ -195,7 +258,60 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
||||
actionExecutionHistoryNodeRef,
|
||||
actionChain,
|
||||
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
|
||||
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
|
||||
{
|
||||
@@ -384,6 +511,7 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
|
||||
{
|
||||
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.
|
||||
// This will ensure that transformers that compete for the same transformation
|
||||
// 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" +
|
||||
" reader: " + reader + "\n" +
|
||||
|
@@ -295,11 +295,12 @@ public class RuleServiceCoverageTest extends TestCase
|
||||
100,
|
||||
new AsyncTest()
|
||||
{
|
||||
public boolean executeTest()
|
||||
public String executeTest()
|
||||
{
|
||||
return RuleServiceCoverageTest.this.nodeService.hasAspect(
|
||||
boolean result = RuleServiceCoverageTest.this.nodeService.hasAspect(
|
||||
newNodeRef,
|
||||
ContentModel.ASPECT_VERSIONABLE);
|
||||
return result ? "" : "Expected aspect Versionable";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user