diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index f525758b20..1ad56c09aa 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -34,6 +34,17 @@ + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 21e396c3e3..ce14685a9c 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -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 diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index 960a96ed79..8253423431 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -183,5 +183,18 @@ + + + + + preventMultipleCreateThumbnailActions + + + + create-thumbnail + + + diff --git a/source/java/org/alfresco/repo/action/AbstractAsynchronousActionFilter.java b/source/java/org/alfresco/repo/action/AbstractAsynchronousActionFilter.java new file mode 100644 index 0000000000..dd8bf5dd87 --- /dev/null +++ b/source/java/org/alfresco/repo/action/AbstractAsynchronousActionFilter.java @@ -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 +{ + 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); + } +} diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 8b60b01dfa..853936f7da 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -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 locations = Arrays.asList(existingConfigLocations); + List mutableLocationsList = new ArrayList(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() + { + 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() + { + 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() + errorMsg = (transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { - 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 null 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 paramList) + { + // Intentionally empty + } + + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { + try + { + Thread.sleep(sleepMs); + } + catch (InterruptedException ignored) + { + // Intentionally empty + } + finally + { + incrementTimesExecutedCount(); + } + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java index bfc085939f..3250243d58 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java @@ -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 + actionFilters = new HashMap(); + + 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 ongoingActions = new Vector(); // Policy delegates private ClassPolicyDelegate 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 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 executedRules = (Set) 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 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); + } } -} +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/CreateThumbnailActionFilter.java b/source/java/org/alfresco/repo/action/CreateThumbnailActionFilter.java new file mode 100644 index 0000000000..c5e3fa912f --- /dev/null +++ b/source/java/org/alfresco/repo/action/CreateThumbnailActionFilter.java @@ -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); + } + } +} diff --git a/source/java/org/alfresco/repo/action/OngoingAsyncAction.java b/source/java/org/alfresco/repo/action/OngoingAsyncAction.java new file mode 100644 index 0000000000..c2cee14a6b --- /dev/null +++ b/source/java/org/alfresco/repo/action/OngoingAsyncAction.java @@ -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(); + } +} diff --git a/source/java/org/alfresco/repo/action/test-action-services-context.xml b/source/java/org/alfresco/repo/action/test-action-services-context.xml new file mode 100644 index 0000000000..8e9797a97c --- /dev/null +++ b/source/java/org/alfresco/repo/action/test-action-services-context.xml @@ -0,0 +1,22 @@ + + + + + + + false + + + 1000 + + + + + + preventMultipleSleepActions + + + sleep-action + + + diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java index 00ce42f20e..c765b392fa 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java @@ -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" + diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index ef3c80e266..261d98caa7 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -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"; }; }); }