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";
};
});
}