From d6586351c17701c3a0c2addc848df9f67488f25a Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Thu, 9 Apr 2009 14:48:02 +0000 Subject: [PATCH] Fixed MOB-426: Refactor CopyService: Apply new pattern to existing policy handlers Fallout: 1. Policy handling for 'onCopy' has been changed to 'getCopyCallback' 2. All existing policy usage was refactored to control behaviour more closely 3. The default child association behaviour has changed: 3.1 Types and aspects control their own child association behaviour 3.2 cm:folder recurses into primary children, but merely copies the secondary association 3.3 cm:rule recurses into primary children 3.4 unless behaviour is defined for a child association, there is no recursion or copying 4. Node association behavior has changed 4.1 There is no copying of node associations. Each type and aspect must handle this by recording nodes and fixing up the required associations in the onCopyComplete. 4.2 If there is a requirement, this can be added to the callback later See 'org.alfresco.repo.copy.AbstractCopyBehaviourCallback' and derived classes for examples. Areas to test with particular attention: 1. Normal copy behaviour 2. Copy of documents with discussions 3. Check-in check-out 4. Check-in, check-out of documents where a discussion was added to working copy 5. Copies of documents with thumbnails 6. Copies of documents with rules 7. Copying of hierarchies that contain rules to copy to another location within the hierarchy 8. Copying into folders where named children already exist git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13915 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/action-services-context.xml | 14 +- config/alfresco/core-services-context.xml | 95 +- .../messages/discussion-messages.properties | 3 + config/alfresco/rule-services-context.xml | 97 +- .../repo/action/ActionServiceImpl.java | 3269 +++++++++-------- .../alfresco/repo/action/ActionsAspect.java | 223 +- .../alfresco/repo/coci/WorkingCopyAspect.java | 99 +- .../copy/AbstractCopyBehaviourCallback.java | 185 + .../copy/CompoundCopyBehaviourCallback.java | 251 ++ .../repo/copy/CopyBehaviourCallback.java | 246 ++ .../org/alfresco/repo/copy/CopyDetails.java | 166 + .../alfresco/repo/copy/CopyServiceImpl.java | 1826 ++++----- .../repo/copy/CopyServiceImplTest.java | 1094 +++--- .../repo/copy/CopyServicePolicies.java | 156 +- .../copy/DefaultCopyBehaviourCallback.java | 107 + .../copy/DoNothingCopyBehaviourCallback.java | 82 + .../repo/forum/DiscussableAspect.java | 430 ++- .../alfresco/repo/lock/LockServiceImpl.java | 46 +- .../repo/model/ml/EmptyTranslationAspect.java | 44 +- .../model/ml/MultilingualDocumentAspect.java | 19 +- .../repo/node/ReferenceableAspect.java | 23 +- .../alfresco/repo/node/TemporaryAspect.java | 24 +- .../alfresco/repo/policy/BehaviourFilter.java | 25 +- .../repo/policy/BehaviourFilterImpl.java | 159 +- .../alfresco/repo/rule/RuleServiceImpl.java | 796 ++-- .../org/alfresco/repo/rule/RulesAspect.java | 254 +- .../repo/thumbnail/ThumbnailedAspect.java | 92 +- .../repo/version/VersionableAspect.java | 271 +- .../service/cmr/repository/CopyService.java | 46 +- 29 files changed, 5920 insertions(+), 4222 deletions(-) create mode 100644 config/alfresco/messages/discussion-messages.properties create mode 100644 source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java create mode 100644 source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java create mode 100644 source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java create mode 100644 source/java/org/alfresco/repo/copy/CopyDetails.java create mode 100644 source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java create mode 100644 source/java/org/alfresco/repo/copy/DoNothingCopyBehaviourCallback.java diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index bf2442e62e..2a62c14ef5 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -71,7 +71,10 @@ - + + + + @@ -102,12 +105,15 @@ - - - + + + + + + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index e5f4d42089..346ba768f9 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -308,6 +308,7 @@ alfresco.messages.permissions-service alfresco.messages.content-service alfresco.messages.coci-service + alfresco.messages.discussion-messages alfresco.messages.template-service alfresco.messages.lock-service alfresco.messages.patch-service @@ -385,13 +386,13 @@ - + - - + + @@ -418,7 +419,7 @@ - + @@ -450,9 +451,9 @@ - - indexThread - + + indexThread + 10 @@ -513,32 +514,32 @@ ${lucene.lock.poll.interval} - + EXACT_LANGUAGE_AND_ALL - + EXACT_LANGUAGE_AND_ALL - - - + + + - - - - - true - + + + + + true + - - - + + + - + @@ -580,18 +581,18 @@ ${lucene.lock.poll.interval} - + EXACT_LANGUAGE_AND_ALL - + EXACT_LANGUAGE_AND_ALL - - - + + + - + @@ -612,13 +613,13 @@ - - - - + + + + - + @@ -655,12 +656,12 @@ EXACT_LANGUAGE_AND_ALL - - - - - true - + + + + + true + @@ -669,7 +670,7 @@ - + @@ -716,7 +717,7 @@ - + @@ -895,7 +896,7 @@ org/alfresco/repo/version/version_model.xml - org/alfresco/repo/version/version2_model.xml + org/alfresco/repo/version/version2_model.xml alfresco/model/emailServerModel.xml @@ -1016,7 +1017,7 @@ - + @@ -1040,7 +1041,7 @@ - + @@ -1053,9 +1054,9 @@ - - - + + + diff --git a/config/alfresco/messages/discussion-messages.properties b/config/alfresco/messages/discussion-messages.properties new file mode 100644 index 0000000000..a6a67f5feb --- /dev/null +++ b/config/alfresco/messages/discussion-messages.properties @@ -0,0 +1,3 @@ +# Discussion-related messages + +discussion.discussion_for={0} discussion \ No newline at end of file diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml index bdc6cf9e95..6718f04ceb 100644 --- a/config/alfresco/rule-services-context.xml +++ b/config/alfresco/rule-services-context.xml @@ -2,74 +2,77 @@ - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - false - + + + + + + + + + + false + + + + + + + - - - - - - - - + + + + + - - - alfresco.messages.rule-config - - + + + alfresco.messages.rule-config + + - - - - - + + + + + - - + + @@ -86,8 +89,8 @@ - - + + @@ -114,10 +117,10 @@ - + - true + true @@ -135,7 +138,7 @@ - true + true @@ -144,7 +147,7 @@ beforeDeleteNode - true + true diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index 4582be8b44..c3a98bdb23 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -1,1597 +1,1672 @@ -/* - * Copyright (C) 2005-2007 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.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.evaluator.ActionConditionEvaluator; -import org.alfresco.repo.action.executer.ActionExecuter; -import org.alfresco.repo.security.authentication.AuthenticationContext; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionCondition; -import org.alfresco.service.cmr.action.ActionConditionDefinition; -import org.alfresco.service.cmr.action.ActionDefinition; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.action.CompositeAction; -import org.alfresco.service.cmr.action.CompositeActionCondition; -import org.alfresco.service.cmr.action.ParameterizedItem; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.GUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Action service implementation - * - * @author Roy Wetherall - */ -public class ActionServiceImpl implements ActionService, RuntimeActionService, ApplicationContextAware -{ - /** - * Transaction resource name - */ - private static final String POST_TRANSACTION_PENDING_ACTIONS = "postTransactionPendingActions"; - - /** - * Error message - */ - private static final String ERR_FAIL = "The action failed to execute due to an error."; - - /** - * The logger - */ - private static Log logger = LogFactory.getLog(ActionServiceImpl.class); - - /** - * Thread local containing the current action chain - */ - ThreadLocal> currentActionChain = new ThreadLocal>(); - - /** - * The application context - */ - private ApplicationContext applicationContext; - - /** - * The node service - */ - private NodeService nodeService; - - /** - * The search service - */ - private SearchService searchService; - - /** The dictionary service */ - private DictionaryService dictionaryService; - - /** The authentication component */ - private AuthenticationContext authenticationContext; - - /** - * The asynchronous action execution queues - * map of name, queue - */ - private Map asynchronousActionExecutionQueues; - - /** - * Action transaction listener - */ - private ActionTransactionListener transactionListener = new ActionTransactionListener(this); - - /** - * All the condition definitions currently registered - */ - private Map conditionDefinitions = new HashMap(); - - /** - * All the action definitions currently registered - */ - private Map actionDefinitions = new HashMap(); - - /** - * Set the application context - * - * @param applicationContext the application context - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.applicationContext = applicationContext; - } - - /** - * Set the node service - * - * @param nodeService the node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the search service - * - * @param searchService the search service - */ - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - /** - * Set the authentication component - * - * @param authenticationContext the authentication component - */ - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - this.authenticationContext = authenticationContext; - } - - /** - * Set the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Set the asynchronous action execution queues - * - * @param asynchronousActionExecutionQueue the asynchronous action execution queues - */ - public void setAsynchronousActionExecutionQueues( - Map asynchronousActionExecutionQueues) - { - this.asynchronousActionExecutionQueues = asynchronousActionExecutionQueues; - } - -// /** -// * Get the asynchronous action execution queue -// * -// * @return the asynchronous action execution queue -// */ -// public AsynchronousActionExecutionQueue getAsynchronousActionExecutionQueue() -// { -// return asynchronousActionExecutionQueue; -// } -// - /** - * Gets the saved action folder reference - * - * @param nodeRef the node reference - * @return the node reference - */ - private NodeRef getSavedActionFolderRef(NodeRef nodeRef) - { - List assocs = this.nodeService.getChildAssocs( - nodeRef, - RegexQNamePattern.MATCH_ALL, - ActionModel.ASSOC_ACTION_FOLDER); - if (assocs.size() != 1) - { - throw new ActionServiceException("Unable to retrieve the saved action folder reference."); - } - - return assocs.get(0).getChildRef(); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionDefinition(java.lang.String) - */ - public ActionDefinition getActionDefinition(String name) - { - // get direct access to action definition (i.e. ignoring public flag of executer) - ActionDefinition definition = null; - Object bean = this.applicationContext.getBean(name); - if (bean != null && bean instanceof ActionExecuter) - { - ActionExecuter executer = (ActionExecuter)bean; - definition = executer.getActionDefinition(); - } - return definition; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions() - */ - public List getActionDefinitions() - { - return new ArrayList(this.actionDefinitions.values()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions(org.alfresco.service.cmr.repository.NodeRef) - */ - public List getActionDefinitions(NodeRef nodeRef) - { - if (nodeRef == null) - { - return getActionDefinitions(); - } - else - { - // TODO for now we will only filter by type, we will introduce filtering by aspect later - QName nodeType = this.nodeService.getType(nodeRef); - List result = new ArrayList(); - for (ActionDefinition actionDefinition : getActionDefinitions()) - { - List appliciableTypes = actionDefinition.getApplicableTypes(); - if (appliciableTypes != null && appliciableTypes.isEmpty() == false) - { - for (QName applicableType : actionDefinition.getApplicableTypes()) - { - if (this.dictionaryService.isSubClass(nodeType, applicableType)) - { - result.add(actionDefinition); - break; - } - } - } - else - { - result.add(actionDefinition); - } - } - - return result; - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinition(java.lang.String) - */ - public ActionConditionDefinition getActionConditionDefinition(String name) - { - return this.conditionDefinitions.get(name); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinitions() - */ - public List getActionConditionDefinitions() - { - return new ArrayList(this.conditionDefinitions.values()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String) - */ - public ActionCondition createActionCondition(String name) - { - if (logger.isDebugEnabled()) - logger.debug("Creating Action Condition - [" + name + "]"); - - if (CompositeActionCondition.COMPOSITE_CONDITION.equals(name)) - { - return new CompositeActionConditionImpl(GUID.generate()); - } - - return new ActionConditionImpl(GUID.generate(), name); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String, java.util.Map) - */ - public ActionCondition createActionCondition(String name, Map params) - { - ActionCondition condition = createActionCondition(name); - condition.setParameterValues(params); - return condition; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createAction() - */ - public Action createAction(String name) - { - return new ActionImpl(null, GUID.generate(),name, null); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createAction(java.lang.String, java.util.Map) - */ - public Action createAction(String name, Map params) - { - Action action = createAction(name); - action.setParameterValues(params); - return action; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createCompositeAction() - */ - public CompositeAction createCompositeAction() - { - return new CompositeActionImpl(null, GUID.generate()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#createCompositeActionCondition() - */ - public CompositeActionCondition createCompositeActionCondition() - { - return new CompositeActionConditionImpl(GUID.generate()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#evaluateAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateAction(Action action, NodeRef actionedUponNodeRef) - { - boolean result = true; - - if (action.hasActionConditions() == true) - { - List actionConditions = action.getActionConditions(); - for (ActionCondition condition : actionConditions) - { - boolean tempresult = evaluateActionCondition(condition, actionedUponNodeRef); - - if (logger.isDebugEnabled()) - logger.debug("\tCondition " + condition.getActionConditionDefinitionName() + " Result - " + tempresult); - - result = result && tempresult; - } - } - - if (logger.isDebugEnabled()) - logger.debug("\tAll Condition Evaluation Result - " + result); - - return result; - } - - /** - * Evaluates the actions by finding corresponding actionEvaluators in applicationContext (registered through Spring). - * Composite conditions are evaluated here as well. It is also possible to have composite actions inside composite actions. - * - * @see org.alfresco.service.cmr.action.ActionService#evaluateActionCondition(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean evaluateActionCondition(ActionCondition condition, NodeRef actionedUponNodeRef) - { - if (condition instanceof CompositeActionCondition) - { - CompositeActionCondition compositeCondition = (CompositeActionCondition) condition; - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating Composite Condition - BOOLEAN CONDITION IS " + (compositeCondition.isORCondition()?"OR":"AND")); - } - - if (!compositeCondition.hasActionConditions()) - { - throw new IllegalStateException("CompositeActionCondition has no subconditions."); - } - - boolean result ; - if (compositeCondition.isORCondition()) - { - result = false; - } - else - { - result = true; - } - - for (ActionCondition simplecondition : compositeCondition.getActionConditions()) - { - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating composite condition " + simplecondition.getActionConditionDefinitionName()); - } - - if (compositeCondition.isORCondition()) - { - result = result || evaluateSimpleCondition(simplecondition, actionedUponNodeRef); - - //Short circuit for the OR condition - if (result) break; - } - else - { - result = result && evaluateSimpleCondition(simplecondition, actionedUponNodeRef); - //Short circuit for the AND condition - if (!result) break; - } - } - - if (compositeCondition.getInvertCondition()) - { - return !result; - } - else - { - return result; - } - } - else - { - return evaluateSimpleCondition(condition, actionedUponNodeRef); - } - } - - private boolean evaluateSimpleCondition(ActionCondition condition, NodeRef actionedUponNodeRef) - { - if (logger.isDebugEnabled()) - { - logger.debug("Evaluating simple condition " + condition.getActionConditionDefinitionName()); - } - // Evaluate the condition - ActionConditionEvaluator evaluator = (ActionConditionEvaluator)this.applicationContext.getBean(condition.getActionConditionDefinitionName()); - return evaluator.evaluate(condition, actionedUponNodeRef); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) - */ - public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions) - { - executeAction(action, actionedUponNodeRef, checkConditions, action.getExecuteAsychronously()); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) - */ - public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously) - { - Set actionChain = this.currentActionChain.get(); - - if (executeAsychronously == false) - { - executeActionImpl(action, actionedUponNodeRef, checkConditions, false, actionChain); - } - else - { - // Add to the post transaction pending action list - addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain); - } - } - - /** - * called by transaction service. - */ - public void postCommit() - { - for (PendingAction pendingAction : getPostTransactionPendingActions()) - { - queueAction(pendingAction); - } - } - - /** - * - */ - private void queueAction(PendingAction action) - { - // Get the right queue - AsynchronousActionExecutionQueue queue = getQueue(action.action); - - // Queue the action for execution - queue.executeAction( - this, - action.getAction(), - action.getActionedUponNodeRef(), - action.getCheckConditions(), - action.getActionChain()); - } - - /** - * - * @param compensatingAction - * @param actionedUponNodeRef - */ - private void queueAction(Action compensatingAction, NodeRef actionedUponNodeRef) - { - // Get the right queue - AsynchronousActionExecutionQueue queue = getQueue(compensatingAction); - - // Queue the action for execution - queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); - } - - private AsynchronousActionExecutionQueue getQueue(Action action) - { - ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); - AsynchronousActionExecutionQueue queue = null; - - String queueName = executer.getQueueName(); - if(queueName == null) - { - queue = asynchronousActionExecutionQueues.get(""); - } - else - { - queue = asynchronousActionExecutionQueues.get(queueName); - } - if(queue == null) - { - // can't get queue - throw new ActionServiceException("Unable to get AsynchronousActionExecutionQueue name: "+ queueName); - } - - return queue; - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean, org.alfresco.service.cmr.repository.NodeRef) - */ - public void executeActionImpl( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - boolean executedAsynchronously, - Set actionChain) - { - if (logger.isDebugEnabled() == true) - { - StringBuilder builder = new StringBuilder("Execute action impl action chain = "); - if (actionChain == null) - { - builder.append("null"); - } - else - { - for (String value : actionChain) - { - builder.append(value).append(" "); - } - } - logger.debug(builder.toString()); - logger.debug("Current action = " + action.getId()); - } - - // get the current user early in case the process fails and we are unable to do it later - String currentUserName = this.authenticationContext.getCurrentUserName(); - - if (actionChain == null || actionChain.contains(action.getId()) == false) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Doing executeActionImpl"); - } - - try - { - Set origActionChain = null; - - if (actionChain == null) - { - actionChain = new HashSet(); - } - else - { - origActionChain = new HashSet(actionChain); - } - actionChain.add(action.getId()); - this.currentActionChain.set(actionChain); - - if (logger.isDebugEnabled() == true) - { - logger.debug("Adding " + action.getId() + " to action chain."); - } - - try - { - // Check and execute now - if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true) - { - // Execute the action - directActionExecution(action, actionedUponNodeRef); - } - } - finally - { - if (origActionChain == null) - { - this.currentActionChain.remove(); - } - else - { - this.currentActionChain.set(origActionChain); - } - - if (logger.isDebugEnabled() == true) - { - logger.debug("Resetting the action chain."); - } - } - } - catch (Throwable exception) - { - // DH: No logging of the exception. Leave the logging decision to the client code, - // which can handle the rethrown exception. - if (executedAsynchronously == true) - { - // If one is specified, queue the compensating action ready for execution - Action compensatingAction = action.getCompensatingAction(); - if (compensatingAction != null) - { - // Set the current user - ((ActionImpl)compensatingAction).setRunAsUser(currentUserName); - queueAction(compensatingAction, actionedUponNodeRef); - } - } - - // Rethrow the exception - if (exception instanceof RuntimeException) - { - throw (RuntimeException)exception; - } - else - { - throw new ActionServiceException(ERR_FAIL, exception); - } - - } - } - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) - */ - public void directActionExecution(Action action, NodeRef actionedUponNodeRef) - { - // Debug output - if (logger.isDebugEnabled() == true) - { - logger.debug("The action is being executed as the user: " + this.authenticationContext.getCurrentUserName()); - } - - // Get the action executer and execute - ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); - executer.execute(action, actionedUponNodeRef); - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, NodeRef) - */ - public void executeAction(Action action, NodeRef actionedUponNodeRef) - { - executeAction(action, actionedUponNodeRef, true); - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#registerActionConditionEvaluator(org.alfresco.repo.action.evaluator.ActionConditionEvaluator) - */ - public void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator) - { - ActionConditionDefinition cond = actionConditionEvaluator.getActionConditionDefintion(); - this.conditionDefinitions.put(cond.getName(), cond); - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#registerActionExecuter(org.alfresco.repo.action.executer.ActionExecuter) - */ - public void registerActionExecuter(ActionExecuter actionExecuter) - { - ActionDefinition action = actionExecuter.getActionDefinition(); - this.actionDefinitions.put(action.getName(), action); - } - - /** - * Gets the action node ref from the action id - * - * @param nodeRef the node reference - * @param actionId the action id - * @return the action node reference - */ - private NodeRef getActionNodeRefFromId(NodeRef nodeRef, String actionId) - { - NodeRef result = null; - - if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(); - namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); - - List nodeRefs = searchService.selectNodes( - getSavedActionFolderRef(nodeRef), - "*[@sys:" + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + actionId + "']", - null, - namespacePrefixResolver, - false); - if (nodeRefs.size() != 0) - { - result = nodeRefs.get(0); - } - } - - return result; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#saveAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) - */ - public void saveAction(NodeRef nodeRef, Action action) - { - NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); - if (actionNodeRef == null) - { - if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == false) - { - // Apply the actionable aspect - this.nodeService.addAspect(nodeRef, ActionModel.ASPECT_ACTIONS, null); - } - - // Create the action and reference - actionNodeRef = createActionNodeRef(action, - getSavedActionFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - ActionModel.ASSOC_NAME_ACTIONS); - } - saveActionImpl(actionNodeRef, action); - } - - public NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName) - { - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, action.getId()); - - QName actionType = ActionModel.TYPE_ACTION; - if(action instanceof CompositeAction) - { - actionType = ActionModel.TYPE_COMPOSITE_ACTION; - } - - // Create the action node - NodeRef actionNodeRef = this.nodeService.createNode( - parentNodeRef, - assocTypeName, - assocName, - actionType, - props).getChildRef(); - - // Update the created details and the node reference - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); - ((ActionImpl)action).setNodeRef(actionNodeRef); - - return actionNodeRef; - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) - */ - public void saveActionImpl(NodeRef actionNodeRef, Action action) - { - // Save action properties - saveActionProperties(actionNodeRef, action); - - // Update the parameters of the action - saveParameters(actionNodeRef, action); - - // Update the conditions of the action - saveConditions(actionNodeRef, action); - - if (action instanceof CompositeAction) - { - // Update composite action - saveCompositeActions(actionNodeRef, (CompositeAction)action); - } - - // Update the modified details - ((ActionImpl)action).setModifier((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIER)); - ((ActionImpl)action).setModifiedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIED)); - } - - /** - * Save the action property values - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void saveActionProperties(NodeRef actionNodeRef, Action action) - { - // Update the action property values - Map props = this.nodeService.getProperties(actionNodeRef); - props.put(ActionModel.PROP_ACTION_TITLE, action.getTitle()); - props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); - props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); - this.nodeService.setProperties(actionNodeRef, props); - - // Update the compensating action (model should enforce the singularity of this association) - Action compensatingAction = action.getCompensatingAction(); - List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); - if (assocs.size() == 0) - { - if (compensatingAction != null) - { - //Map props2 = new HashMap(2); - //props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); - //props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); - - //NodeRef compensatingActionNodeRef = this.nodeService.createNode( - /// actionNodeRef, - // ActionModel.ASSOC_COMPENSATING_ACTION, - // ActionModel.ASSOC_COMPENSATING_ACTION, - // ActionModel.TYPE_ACTION, - // props2).getChildRef(); - - // Create the compensating node reference - NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); - saveActionImpl(compensatingActionNodeRef, compensatingAction); - } - } - else - { - ChildAssociationRef assoc = assocs.get(0); - if (compensatingAction == null) - { - this.nodeService.removeChild(actionNodeRef, assoc.getChildRef()); - } - else - { - saveActionImpl(assoc.getChildRef(), compensatingAction); - } - } - } - - /** - * Save the actions of a composite action - * - * @param compositeActionNodeRef the node reference of the composite action - * @param compositeAction the composite action - */ - private void saveCompositeActions(NodeRef compositeActionNodeRef, CompositeAction compositeAction) - { - // TODO Need a way of sorting the order of the actions - - Map idToAction = new HashMap(); - List orderedIds = new ArrayList(); - for (Action action : compositeAction.getActions()) - { - idToAction.put(action.getId(), action); - orderedIds.add(action.getId()); - } - - List actionRefs = this.nodeService.getChildAssocs(compositeActionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); - for (ChildAssociationRef actionRef : actionRefs) - { - NodeRef actionNodeRef = actionRef.getChildRef(); - if (idToAction.containsKey(actionNodeRef.getId()) == false) - { - // Delete the action - this.nodeService.removeChild(compositeActionNodeRef, actionNodeRef); - } - else - { - // Update the action - Action action = idToAction.get(actionNodeRef.getId()); - saveActionImpl(actionNodeRef, action); - orderedIds.remove(actionNodeRef.getId()); - } - - } - - // Create the actions remaining - for (String actionId : orderedIds) - { - Action action = idToAction.get(actionId); - - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, action.getId()); - - NodeRef actionNodeRef = this.nodeService.createNode( - compositeActionNodeRef, - ActionModel.ASSOC_ACTIONS, - ActionModel.ASSOC_ACTIONS, - ActionModel.TYPE_ACTION, - props).getChildRef(); - - // Update the created details and the node reference - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); - ((ActionImpl)action).setNodeRef(actionNodeRef); - - saveActionImpl(actionNodeRef, action); - } - } - - /** - * Saves the conditions associated with an action. - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void saveConditions(NodeRef actionNodeRef, Action action) - { - // TODO Need a way of sorting out the order of the conditions - List actionConditionsList = action.getActionConditions(); - saveActionConditionList(actionNodeRef, actionConditionsList, false); - } - - private void saveActionConditionList(NodeRef actionNodeRef, - List actionConditionsList, boolean isComposite) - { - if (logger.isDebugEnabled()) - logger.debug("SaveActionCondition list, "+ actionConditionsList.size() + - (isComposite?" Composite":"") + " conditions to be saved"); - - Map idToCondition = new HashMap(); - List orderedIds = new ArrayList(); - - for (ActionCondition actionCondition : actionConditionsList) - { - idToCondition.put(actionCondition.getId(), actionCondition); - orderedIds.add(actionCondition.getId()); - } - - List conditionRefs = this.nodeService.getChildAssocs( - actionNodeRef, RegexQNamePattern.MATCH_ALL, - !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); - - for (ChildAssociationRef conditionRef : conditionRefs) - { - NodeRef conditionNodeRef = conditionRef.getChildRef(); - if (idToCondition.containsKey(conditionNodeRef.getId()) == false) - { - // Delete the condition - this.nodeService.removeChild(actionNodeRef, conditionNodeRef); - } - else - { - saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - // Update the conditions parameters - saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - orderedIds.remove(conditionNodeRef.getId()); - } - } - - // Create the conditions remaining - for (String nextId : orderedIds) - { - ActionCondition actionCondition = idToCondition.get(nextId); - - if (!isComposite && actionCondition instanceof CompositeActionCondition) - { - if (logger.isDebugEnabled()) - logger.debug("Saving Composite Condition"); - - NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, - ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_COMPOSITE_ACTION_CONDITION); - saveActionConditionList(conditionNodeRef, ((CompositeActionCondition) actionCondition).getActionConditions(), true); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("Saving Condition " + actionCondition.getActionConditionDefinitionName()); - - NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, - !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION, - ActionModel.TYPE_ACTION_CONDITION); - } - } - } - - /* - private void saveCompositeActionConditionList(NodeRef compositeConditionRef, - List actionConditionsList) - { - if (logger.isDebugEnabled()) - logger.debug("SaveActionCondition list Composite, "+ actionConditionsList.size() + " conditions to be saved"); - - Map idToCondition = new HashMap(); - List orderedIds = new ArrayList(); - - for (ActionCondition actionCondition : actionConditionsList) - { - idToCondition.put(actionCondition.getId(), actionCondition); - orderedIds.add(actionCondition.getId()); - } - - List conditionRefs = this.nodeService.getChildAssocs(compositeConditionRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); - for (ChildAssociationRef conditionRef : conditionRefs) - { - NodeRef conditionNodeRef = conditionRef.getChildRef(); - if (idToCondition.containsKey(conditionNodeRef.getId()) == false) - { - // Delete the condition - this.nodeService.removeChild(compositeConditionRef, conditionNodeRef); - } - else - { - saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - // Update the conditions parameters - saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - orderedIds.remove(conditionNodeRef.getId()); - } - } - - // Create the conditions remaining - for (String nextId : orderedIds) - { - ActionCondition actionCondition = idToCondition.get(nextId); - NodeRef conditionNodeRef = saveActionCondition(compositeConditionRef, actionCondition, ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_ACTION_CONDITION); - - } - }*/ - - - private NodeRef saveActionCondition(NodeRef actionNodeRef, - ActionCondition actionCondition, QName AssociationQName, QName typeName) - { - Map props = new HashMap(2); - props.put(ActionModel.PROP_DEFINITION_NAME, actionCondition.getActionConditionDefinitionName()); - props.put(ContentModel.PROP_NODE_UUID, actionCondition.getId()); - - NodeRef conditionNodeRef = this.nodeService.createNode( - actionNodeRef, - AssociationQName, - AssociationQName, - typeName, - props).getChildRef(); - - saveConditionProperties(conditionNodeRef, actionCondition); - saveParameters(conditionNodeRef, actionCondition); - return conditionNodeRef; - } - - /** - * Save the condition properties - * - * @param conditionNodeRef - * @param condition - */ - private void saveConditionProperties(NodeRef conditionNodeRef, ActionCondition condition) - { - this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT, condition.getInvertCondition()); - - if (condition instanceof CompositeActionCondition) - { - if (logger.isDebugEnabled()) - { - logger.debug("SAVING OR = " + ((CompositeActionCondition)condition).isORCondition()); - } - this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_ANDOR, new Boolean(((CompositeActionCondition)condition).isORCondition())); - } - } - - /** - * Saves the parameters associated with an action or condition - * - * @param parameterizedNodeRef the parameterized item node reference - * @param item the parameterized item - */ - private void saveParameters(NodeRef parameterizedNodeRef, ParameterizedItem item) - { - Map parameterMap = new HashMap(); - parameterMap.putAll(item.getParameterValues()); - - List parameters = this.nodeService.getChildAssocs(parameterizedNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); - for (ChildAssociationRef ref : parameters) - { - NodeRef paramNodeRef = ref.getChildRef(); - Map nodeRefParameterMap = this.nodeService.getProperties(paramNodeRef); - String paramName = (String)nodeRefParameterMap.get(ActionModel.PROP_PARAMETER_NAME); - if (parameterMap.containsKey(paramName) == false) - { - // Delete parameter from node ref - this.nodeService.removeChild(parameterizedNodeRef, paramNodeRef); - } - else - { - // Update the parameter value - nodeRefParameterMap.put(ActionModel.PROP_PARAMETER_VALUE, parameterMap.get(paramName)); - this.nodeService.setProperties(paramNodeRef, nodeRefParameterMap); - parameterMap.remove(paramName); - } - } - - // Add any remaining parameters - for (Map.Entry entry : parameterMap.entrySet()) - { - Map nodeRefProperties = new HashMap(2); - nodeRefProperties.put(ActionModel.PROP_PARAMETER_NAME, entry.getKey()); - nodeRefProperties.put(ActionModel.PROP_PARAMETER_VALUE, entry.getValue()); - - this.nodeService.createNode( - parameterizedNodeRef, - ActionModel.ASSOC_PARAMETERS, - ActionModel.ASSOC_PARAMETERS, - ActionModel.TYPE_ACTION_PARAMETER, - nodeRefProperties); - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getActions(org.alfresco.service.cmr.repository.NodeRef) - */ - public List getActions(NodeRef nodeRef) - { - List result = new ArrayList(); - - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - List actions = this.nodeService.getChildAssocs( - getSavedActionFolderRef(nodeRef), - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS); - for (ChildAssociationRef action : actions) - { - NodeRef actionNodeRef = action.getChildRef(); - result.add(createAction(actionNodeRef)); - } - } - - return result; - } - - /** - * Create an action from the action node reference - * - * @param actionNodeRef the action node reference - * @return the action - */ - public Action createAction(NodeRef actionNodeRef) - { - Action result = null; - - Map properties = this.nodeService.getProperties(actionNodeRef); - - QName actionType = this.nodeService.getType(actionNodeRef); - if (ActionModel.TYPE_COMPOSITE_ACTION.equals(actionType) == true) - { - // Create a composite action - result = new CompositeActionImpl(actionNodeRef, actionNodeRef.getId()); - populateCompositeAction(actionNodeRef, (CompositeAction)result); - } - else - { - // Create an action - result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); - populateAction(actionNodeRef, result); - } - - return result; - } - - /** - * Populate the details of the action from the node reference - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void populateAction(NodeRef actionNodeRef, Action action) - { - // Populate the action properties - populateActionProperties(actionNodeRef, action); - - // Set the parameters - populateParameters(actionNodeRef, action); - - // Set the conditions - List conditions = this.nodeService.getChildAssocs(actionNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); - - if (logger.isDebugEnabled()) - logger.debug("Retrieving " + (conditions==null ? " null" : conditions.size()) + " conditions"); - - for (ChildAssociationRef condition : conditions) - { - NodeRef conditionNodeRef = condition.getChildRef(); - action.addActionCondition(createActionCondition(conditionNodeRef)); - } - } - - /** - * Populates the action properties from the node reference - * - * @param actionNodeRef the action node reference - * @param action the action - */ - private void populateActionProperties(NodeRef actionNodeRef, Action action) - { - Map props = this.nodeService.getProperties(actionNodeRef); - - action.setTitle((String)props.get(ActionModel.PROP_ACTION_TITLE)); - action.setDescription((String)props.get(ActionModel.PROP_ACTION_DESCRIPTION)); - - boolean value = false; - Boolean executeAsynchronously = (Boolean)props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); - if (executeAsynchronously != null) - { - value = executeAsynchronously.booleanValue(); - } - action.setExecuteAsynchronously(value); - - ((ActionImpl)action).setCreator((String)props.get(ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)props.get(ContentModel.PROP_CREATED)); - ((ActionImpl)action).setModifier((String)props.get(ContentModel.PROP_MODIFIER)); - ((ActionImpl)action).setModifiedDate((Date)props.get(ContentModel.PROP_MODIFIED)); - - // Get the compensating action - List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); - if (assocs.size() != 0) - { - Action compensatingAction = createAction(assocs.get(0).getChildRef()); - action.setCompensatingAction(compensatingAction); - } - } - - /** - * Populate the parameters of a parameterized item from the parameterized item node reference - * - * @param parameterizedItemNodeRef the parameterized item node reference - * @param parameterizedItem the parameterized item - */ - private void populateParameters(NodeRef parameterizedItemNodeRef, ParameterizedItem parameterizedItem) - { - List parameters = this.nodeService.getChildAssocs(parameterizedItemNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); - for (ChildAssociationRef parameter : parameters) - { - NodeRef parameterNodeRef = parameter.getChildRef(); - Map properties = this.nodeService.getProperties(parameterNodeRef); - parameterizedItem.setParameterValue( - (String)properties.get(ActionModel.PROP_PARAMETER_NAME), - properties.get(ActionModel.PROP_PARAMETER_VALUE)); - } - } - - /** - * Creates an action condition from an action condition node reference - * - * @param conditionNodeRef the condition node reference - * @return the action condition - */ - private ActionCondition createActionCondition(NodeRef conditionNodeRef) - { - if (logger.isDebugEnabled()) - logger.debug("\tCreateActionCondition: Retrieving Conditions from repository"); - - Map properties = this.nodeService.getProperties(conditionNodeRef); - QName conditionType = this.nodeService.getType(conditionNodeRef); - - ActionCondition condition = null; - if (ActionModel.TYPE_COMPOSITE_ACTION_CONDITION.equals(conditionType) == false) - { - condition = new ActionConditionImpl(conditionNodeRef.getId(), - (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("\tRetrieving Composite Condition from repository"); - } - - // Create a composite condition - CompositeActionCondition compositeCondition = new CompositeActionConditionImpl(GUID.generate()); - populateCompositeActionCondition(conditionNodeRef, compositeCondition); - - condition = compositeCondition; - } - - Boolean invert = (Boolean)this.nodeService.getProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT); - condition.setInvertCondition(invert == null? false: invert.booleanValue()); - - populateParameters(conditionNodeRef, condition); - return condition; - } - - private void populateCompositeActionCondition(NodeRef compositeNodeRef, CompositeActionCondition compositeActionCondition) - { - List conditions = this.nodeService.getChildAssocs(compositeNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); - - Boolean OR = (Boolean) this.nodeService.getProperty(compositeNodeRef, ActionModel.PROP_CONDITION_ANDOR); - - if (logger.isDebugEnabled()) - { - logger.debug("\tPopulating Composite Condition with subconditions, Condition OR = " + OR); - } - - compositeActionCondition.setORCondition(OR == null? false: OR.booleanValue()); - - for (ChildAssociationRef conditionNodeRef : conditions) - { - NodeRef actionNodeRef = conditionNodeRef.getChildRef(); - ActionCondition currentCondition = createActionCondition(actionNodeRef); - - if (logger.isDebugEnabled()) - logger.debug("\t\tAdding subcondition " + currentCondition.getActionConditionDefinitionName()); - - compositeActionCondition.addActionCondition(currentCondition); - } - } - - - /** - * Populates a composite action from a composite action node reference - * - * @param compositeNodeRef the composite action node reference - * @param compositeAction the composite action - */ - public void populateCompositeAction(NodeRef compositeNodeRef, CompositeAction compositeAction) - { - populateAction(compositeNodeRef, compositeAction); - - List actions = this.nodeService.getChildAssocs(compositeNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); - for (ChildAssociationRef action : actions) - { - NodeRef actionNodeRef = action.getChildRef(); - compositeAction.addAction(createAction(actionNodeRef)); - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#getAction(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - public Action getAction(NodeRef nodeRef, String actionId) - { - Action result = null; - - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, actionId); - if (actionNodeRef != null) - { - result = createAction(actionNodeRef); - } - } - - return result; - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#removeAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) - */ - public void removeAction(NodeRef nodeRef, Action action) - { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); - if (actionNodeRef != null) - { - this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), actionNodeRef); - } - } - } - - /** - * @see org.alfresco.service.cmr.action.ActionService#removeAllActions(org.alfresco.service.cmr.repository.NodeRef) - */ - public void removeAllActions(NodeRef nodeRef) - { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) - { - List actions = new ArrayList(this.nodeService.getChildAssocs(getSavedActionFolderRef(nodeRef), RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS)); - for (ChildAssociationRef action : actions) - { - this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), action.getChildRef()); - } - } - } - - /** - * Add a pending action to the list to be queued for execution once the transaction is completed. - * - * @param action the action - * @param actionedUponNodeRef the actioned upon node reference - * @param checkConditions indicates whether to check the conditions before execution - */ - @SuppressWarnings("unchecked") - private void addPostTransactionPendingAction( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - Set actionChain) - { - if (logger.isDebugEnabled() == true) - { - StringBuilder builder = new StringBuilder("addPostTransactionPendingAction action chain = "); - if (actionChain == null) - { - builder.append("null"); - } - else - { - for (String value : actionChain) - { - builder.append(value).append(" "); - } - } - logger.debug(builder.toString()); - logger.debug("Current action = " + action.getId()); - } - - // Don't continue if the action is already in the action chain - if (actionChain == null || actionChain.contains(action.getId()) == false) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Doing addPostTransactionPendingAction"); - } - - // Set the run as user to the current user - if (logger.isDebugEnabled() == true) - { - logger.debug("The current user is: " + this.authenticationContext.getCurrentUserName()); - } - ((ActionImpl)action).setRunAsUser(this.authenticationContext.getCurrentUserName()); - - // Ensure that the transaction listener is bound to the transaction - AlfrescoTransactionSupport.bindListener(this.transactionListener); - - // Add the pending action to the transaction resource - List pendingActions = (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); - if (pendingActions == null) - { - pendingActions = new ArrayList(); - AlfrescoTransactionSupport.bindResource(POST_TRANSACTION_PENDING_ACTIONS, pendingActions); - } - - // Check that action has only been added to the list once - PendingAction pendingAction = new PendingAction(action, actionedUponNodeRef, checkConditions, actionChain); - if (pendingActions.contains(pendingAction) == false) - { - pendingActions.add(pendingAction); - } - } - } - - /** - * @see org.alfresco.repo.action.RuntimeActionService#getPostTransactionPendingActions() - */ - @SuppressWarnings("unchecked") - private List getPostTransactionPendingActions() - { - return (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); - } - - /** - * Pending action details class - */ - private class PendingAction - { - /** - * The action - */ - private Action action; - - /** - * The actioned upon node reference - */ - private NodeRef actionedUponNodeRef; - - /** - * Indicates whether the conditions should be checked before the action is executed - */ - private boolean checkConditions; - - private Set actionChain; - - /** - * Constructor - * - * @param action the action - * @param actionedUponNodeRef the actioned upon node reference - * @param checkConditions indicated whether the conditions need to be checked - */ - public PendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set actionChain) - { - this.action = action; - this.actionedUponNodeRef = actionedUponNodeRef; - this.checkConditions = checkConditions; - this.actionChain = actionChain; - } - - /** - * Get the action - * - * @return the action - */ - public Action getAction() - { - return action; - } - - /** - * Get the actioned upon node reference - * - * @return the actioned upon node reference - */ - public NodeRef getActionedUponNodeRef() - { - return actionedUponNodeRef; - } - - /** - * Get the check conditions value - * - * @return indicates whether the condition should be checked - */ - public boolean getCheckConditions() - { - return this.checkConditions; - } - - public Set getActionChain() - { - return this.actionChain; - } - - /** - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() - { - int hashCode = 37 * this.actionedUponNodeRef.hashCode(); - hashCode += 37 * this.action.hashCode(); - return hashCode; - } - - /** - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj instanceof PendingAction) - { - PendingAction that = (PendingAction) obj; - return (this.action.equals(that.action) && this.actionedUponNodeRef.equals(that.actionedUponNodeRef)); - } - else - { - return false; - } - } - } -} +/* + * 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.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.ActionConditionEvaluator; +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionConditionDefinition; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.action.CompositeActionCondition; +import org.alfresco.service.cmr.action.ParameterizedItem; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.GUID; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Action service implementation + * + * @author Roy Wetherall + */ +public class ActionServiceImpl implements + ActionService, RuntimeActionService, ApplicationContextAware, + CopyServicePolicies.OnCopyNodePolicy, + CopyServicePolicies.OnCopyCompletePolicy +{ + /** + * Transaction resource name + */ + private static final String POST_TRANSACTION_PENDING_ACTIONS = "postTransactionPendingActions"; + + /** + * Error message + */ + private static final String ERR_FAIL = "The action failed to execute due to an error."; + + /** + * The logger + */ + private static Log logger = LogFactory.getLog(ActionServiceImpl.class); + + /** + * Thread local containing the current action chain + */ + ThreadLocal> currentActionChain = new ThreadLocal>(); + + /** + * The application context used to retrieve defined actions + */ + private ApplicationContext applicationContext; + private NodeService nodeService; + private SearchService searchService; + private DictionaryService dictionaryService; + private AuthenticationContext authenticationContext; + private PolicyComponent policyComponent; + + /** + * The asynchronous action execution queues + * map of name, queue + */ + private Map asynchronousActionExecutionQueues; + + /** + * Action transaction listener + */ + private ActionTransactionListener transactionListener = new ActionTransactionListener(this); + + /** + * All the condition definitions currently registered + */ + private Map conditionDefinitions = new HashMap(); + + /** + * All the action definitions currently registered + */ + private Map actionDefinitions = new HashMap(); + + /** + * Set the application context + * + * @param applicationContext the application context + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + } + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the search service + * + * @param searchService the search service + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Set the authentication component + * + * @param authenticationContext the authentication component + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * Set the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * + * @param policyComponent used to set up the action-based policy behaviour + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Set the asynchronous action execution queues + * + * @param asynchronousActionExecutionQueue the asynchronous action execution queues + */ + public void setAsynchronousActionExecutionQueues( + Map asynchronousActionExecutionQueues) + { + this.asynchronousActionExecutionQueues = asynchronousActionExecutionQueues; + } + +// /** +// * Get the asynchronous action execution queue +// * +// * @return the asynchronous action execution queue +// */ +// public AsynchronousActionExecutionQueue getAsynchronousActionExecutionQueue() +// { +// return asynchronousActionExecutionQueue; +// } +// + + public void init() + { + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ActionModel.TYPE_ACTION_PARAMETER, + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), + ActionModel.TYPE_ACTION_PARAMETER, + new JavaBehaviour(this, "onCopyComplete")); + } + + /** + * Gets the saved action folder reference + * + * @param nodeRef the node reference + * @return the node reference + */ + private NodeRef getSavedActionFolderRef(NodeRef nodeRef) + { + List assocs = this.nodeService.getChildAssocs( + nodeRef, + RegexQNamePattern.MATCH_ALL, + ActionModel.ASSOC_ACTION_FOLDER); + if (assocs.size() != 1) + { + throw new ActionServiceException("Unable to retrieve the saved action folder reference."); + } + + return assocs.get(0).getChildRef(); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionDefinition(java.lang.String) + */ + public ActionDefinition getActionDefinition(String name) + { + // get direct access to action definition (i.e. ignoring public flag of executer) + ActionDefinition definition = null; + Object bean = this.applicationContext.getBean(name); + if (bean != null && bean instanceof ActionExecuter) + { + ActionExecuter executer = (ActionExecuter)bean; + definition = executer.getActionDefinition(); + } + return definition; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions() + */ + public List getActionDefinitions() + { + return new ArrayList(this.actionDefinitions.values()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions(org.alfresco.service.cmr.repository.NodeRef) + */ + public List getActionDefinitions(NodeRef nodeRef) + { + if (nodeRef == null) + { + return getActionDefinitions(); + } + else + { + // TODO for now we will only filter by type, we will introduce filtering by aspect later + QName nodeType = this.nodeService.getType(nodeRef); + List result = new ArrayList(); + for (ActionDefinition actionDefinition : getActionDefinitions()) + { + List appliciableTypes = actionDefinition.getApplicableTypes(); + if (appliciableTypes != null && appliciableTypes.isEmpty() == false) + { + for (QName applicableType : actionDefinition.getApplicableTypes()) + { + if (this.dictionaryService.isSubClass(nodeType, applicableType)) + { + result.add(actionDefinition); + break; + } + } + } + else + { + result.add(actionDefinition); + } + } + + return result; + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinition(java.lang.String) + */ + public ActionConditionDefinition getActionConditionDefinition(String name) + { + return this.conditionDefinitions.get(name); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActionConditionDefinitions() + */ + public List getActionConditionDefinitions() + { + return new ArrayList(this.conditionDefinitions.values()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String) + */ + public ActionCondition createActionCondition(String name) + { + if (logger.isDebugEnabled()) + logger.debug("Creating Action Condition - [" + name + "]"); + + if (CompositeActionCondition.COMPOSITE_CONDITION.equals(name)) + { + return new CompositeActionConditionImpl(GUID.generate()); + } + + return new ActionConditionImpl(GUID.generate(), name); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String, java.util.Map) + */ + public ActionCondition createActionCondition(String name, Map params) + { + ActionCondition condition = createActionCondition(name); + condition.setParameterValues(params); + return condition; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createAction() + */ + public Action createAction(String name) + { + return new ActionImpl(null, GUID.generate(),name, null); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createAction(java.lang.String, java.util.Map) + */ + public Action createAction(String name, Map params) + { + Action action = createAction(name); + action.setParameterValues(params); + return action; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createCompositeAction() + */ + public CompositeAction createCompositeAction() + { + return new CompositeActionImpl(null, GUID.generate()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#createCompositeActionCondition() + */ + public CompositeActionCondition createCompositeActionCondition() + { + return new CompositeActionConditionImpl(GUID.generate()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#evaluateAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateAction(Action action, NodeRef actionedUponNodeRef) + { + boolean result = true; + + if (action.hasActionConditions() == true) + { + List actionConditions = action.getActionConditions(); + for (ActionCondition condition : actionConditions) + { + boolean tempresult = evaluateActionCondition(condition, actionedUponNodeRef); + + if (logger.isDebugEnabled()) + logger.debug("\tCondition " + condition.getActionConditionDefinitionName() + " Result - " + tempresult); + + result = result && tempresult; + } + } + + if (logger.isDebugEnabled()) + logger.debug("\tAll Condition Evaluation Result - " + result); + + return result; + } + + /** + * Evaluates the actions by finding corresponding actionEvaluators in applicationContext (registered through Spring). + * Composite conditions are evaluated here as well. It is also possible to have composite actions inside composite actions. + * + * @see org.alfresco.service.cmr.action.ActionService#evaluateActionCondition(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean evaluateActionCondition(ActionCondition condition, NodeRef actionedUponNodeRef) + { + if (condition instanceof CompositeActionCondition) + { + CompositeActionCondition compositeCondition = (CompositeActionCondition) condition; + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating Composite Condition - BOOLEAN CONDITION IS " + (compositeCondition.isORCondition()?"OR":"AND")); + } + + if (!compositeCondition.hasActionConditions()) + { + throw new IllegalStateException("CompositeActionCondition has no subconditions."); + } + + boolean result ; + if (compositeCondition.isORCondition()) + { + result = false; + } + else + { + result = true; + } + + for (ActionCondition simplecondition : compositeCondition.getActionConditions()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating composite condition " + simplecondition.getActionConditionDefinitionName()); + } + + if (compositeCondition.isORCondition()) + { + result = result || evaluateSimpleCondition(simplecondition, actionedUponNodeRef); + + //Short circuit for the OR condition + if (result) break; + } + else + { + result = result && evaluateSimpleCondition(simplecondition, actionedUponNodeRef); + //Short circuit for the AND condition + if (!result) break; + } + } + + if (compositeCondition.getInvertCondition()) + { + return !result; + } + else + { + return result; + } + } + else + { + return evaluateSimpleCondition(condition, actionedUponNodeRef); + } + } + + private boolean evaluateSimpleCondition(ActionCondition condition, NodeRef actionedUponNodeRef) + { + if (logger.isDebugEnabled()) + { + logger.debug("Evaluating simple condition " + condition.getActionConditionDefinitionName()); + } + // Evaluate the condition + ActionConditionEvaluator evaluator = (ActionConditionEvaluator)this.applicationContext.getBean(condition.getActionConditionDefinitionName()); + return evaluator.evaluate(condition, actionedUponNodeRef); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) + */ + public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions) + { + executeAction(action, actionedUponNodeRef, checkConditions, action.getExecuteAsychronously()); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) + */ + public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously) + { + Set actionChain = this.currentActionChain.get(); + + if (executeAsychronously == false) + { + executeActionImpl(action, actionedUponNodeRef, checkConditions, false, actionChain); + } + else + { + // Add to the post transaction pending action list + addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain); + } + } + + /** + * called by transaction service. + */ + public void postCommit() + { + for (PendingAction pendingAction : getPostTransactionPendingActions()) + { + queueAction(pendingAction); + } + } + + /** + * + */ + private void queueAction(PendingAction action) + { + // Get the right queue + AsynchronousActionExecutionQueue queue = getQueue(action.action); + + // Queue the action for execution + queue.executeAction( + this, + action.getAction(), + action.getActionedUponNodeRef(), + action.getCheckConditions(), + action.getActionChain()); + } + + /** + * + * @param compensatingAction + * @param actionedUponNodeRef + */ + private void queueAction(Action compensatingAction, NodeRef actionedUponNodeRef) + { + // Get the right queue + AsynchronousActionExecutionQueue queue = getQueue(compensatingAction); + + // Queue the action for execution + queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); + } + + private AsynchronousActionExecutionQueue getQueue(Action action) + { + ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); + AsynchronousActionExecutionQueue queue = null; + + String queueName = executer.getQueueName(); + if(queueName == null) + { + queue = asynchronousActionExecutionQueues.get(""); + } + else + { + queue = asynchronousActionExecutionQueues.get(queueName); + } + if(queue == null) + { + // can't get queue + throw new ActionServiceException("Unable to get AsynchronousActionExecutionQueue name: "+ queueName); + } + + return queue; + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean, org.alfresco.service.cmr.repository.NodeRef) + */ + public void executeActionImpl( + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + boolean executedAsynchronously, + Set actionChain) + { + if (logger.isDebugEnabled() == true) + { + StringBuilder builder = new StringBuilder("Execute action impl action chain = "); + if (actionChain == null) + { + builder.append("null"); + } + else + { + for (String value : actionChain) + { + builder.append(value).append(" "); + } + } + logger.debug(builder.toString()); + logger.debug("Current action = " + action.getId()); + } + + // get the current user early in case the process fails and we are unable to do it later + String currentUserName = this.authenticationContext.getCurrentUserName(); + + if (actionChain == null || actionChain.contains(action.getId()) == false) + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Doing executeActionImpl"); + } + + try + { + Set origActionChain = null; + + if (actionChain == null) + { + actionChain = new HashSet(); + } + else + { + origActionChain = new HashSet(actionChain); + } + actionChain.add(action.getId()); + this.currentActionChain.set(actionChain); + + if (logger.isDebugEnabled() == true) + { + logger.debug("Adding " + action.getId() + " to action chain."); + } + + try + { + // Check and execute now + if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true) + { + // Execute the action + directActionExecution(action, actionedUponNodeRef); + } + } + finally + { + if (origActionChain == null) + { + this.currentActionChain.remove(); + } + else + { + this.currentActionChain.set(origActionChain); + } + + if (logger.isDebugEnabled() == true) + { + logger.debug("Resetting the action chain."); + } + } + } + catch (Throwable exception) + { + // DH: No logging of the exception. Leave the logging decision to the client code, + // which can handle the rethrown exception. + if (executedAsynchronously == true) + { + // If one is specified, queue the compensating action ready for execution + Action compensatingAction = action.getCompensatingAction(); + if (compensatingAction != null) + { + // Set the current user + ((ActionImpl)compensatingAction).setRunAsUser(currentUserName); + queueAction(compensatingAction, actionedUponNodeRef); + } + } + + // Rethrow the exception + if (exception instanceof RuntimeException) + { + throw (RuntimeException)exception; + } + else + { + throw new ActionServiceException(ERR_FAIL, exception); + } + + } + } + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + public void directActionExecution(Action action, NodeRef actionedUponNodeRef) + { + // Debug output + if (logger.isDebugEnabled() == true) + { + logger.debug("The action is being executed as the user: " + this.authenticationContext.getCurrentUserName()); + } + + // Get the action executer and execute + ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); + executer.execute(action, actionedUponNodeRef); + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, NodeRef) + */ + public void executeAction(Action action, NodeRef actionedUponNodeRef) + { + executeAction(action, actionedUponNodeRef, true); + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#registerActionConditionEvaluator(org.alfresco.repo.action.evaluator.ActionConditionEvaluator) + */ + public void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator) + { + ActionConditionDefinition cond = actionConditionEvaluator.getActionConditionDefintion(); + this.conditionDefinitions.put(cond.getName(), cond); + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#registerActionExecuter(org.alfresco.repo.action.executer.ActionExecuter) + */ + public void registerActionExecuter(ActionExecuter actionExecuter) + { + ActionDefinition action = actionExecuter.getActionDefinition(); + this.actionDefinitions.put(action.getName(), action); + } + + /** + * Gets the action node ref from the action id + * + * @param nodeRef the node reference + * @param actionId the action id + * @return the action node reference + */ + private NodeRef getActionNodeRefFromId(NodeRef nodeRef, String actionId) + { + NodeRef result = null; + + if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(); + namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); + + List nodeRefs = searchService.selectNodes( + getSavedActionFolderRef(nodeRef), + "*[@sys:" + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + actionId + "']", + null, + namespacePrefixResolver, + false); + if (nodeRefs.size() != 0) + { + result = nodeRefs.get(0); + } + } + + return result; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#saveAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + */ + public void saveAction(NodeRef nodeRef, Action action) + { + NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); + if (actionNodeRef == null) + { + if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == false) + { + // Apply the actionable aspect + this.nodeService.addAspect(nodeRef, ActionModel.ASPECT_ACTIONS, null); + } + + // Create the action and reference + actionNodeRef = createActionNodeRef(action, + getSavedActionFolderRef(nodeRef), + ContentModel.ASSOC_CONTAINS, + ActionModel.ASSOC_NAME_ACTIONS); + } + saveActionImpl(actionNodeRef, action); + } + + public NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName) + { + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, action.getId()); + + QName actionType = ActionModel.TYPE_ACTION; + if(action instanceof CompositeAction) + { + actionType = ActionModel.TYPE_COMPOSITE_ACTION; + } + + // Create the action node + NodeRef actionNodeRef = this.nodeService.createNode( + parentNodeRef, + assocTypeName, + assocName, + actionType, + props).getChildRef(); + + // Update the created details and the node reference + ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); + ((ActionImpl)action).setNodeRef(actionNodeRef); + + return actionNodeRef; + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + */ + public void saveActionImpl(NodeRef actionNodeRef, Action action) + { + // Save action properties + saveActionProperties(actionNodeRef, action); + + // Update the parameters of the action + saveParameters(actionNodeRef, action); + + // Update the conditions of the action + saveConditions(actionNodeRef, action); + + if (action instanceof CompositeAction) + { + // Update composite action + saveCompositeActions(actionNodeRef, (CompositeAction)action); + } + + // Update the modified details + ((ActionImpl)action).setModifier((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIER)); + ((ActionImpl)action).setModifiedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIED)); + } + + /** + * Save the action property values + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void saveActionProperties(NodeRef actionNodeRef, Action action) + { + // Update the action property values + Map props = this.nodeService.getProperties(actionNodeRef); + props.put(ActionModel.PROP_ACTION_TITLE, action.getTitle()); + props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); + props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); + this.nodeService.setProperties(actionNodeRef, props); + + // Update the compensating action (model should enforce the singularity of this association) + Action compensatingAction = action.getCompensatingAction(); + List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); + if (assocs.size() == 0) + { + if (compensatingAction != null) + { + //Map props2 = new HashMap(2); + //props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); + //props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); + + //NodeRef compensatingActionNodeRef = this.nodeService.createNode( + /// actionNodeRef, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.TYPE_ACTION, + // props2).getChildRef(); + + // Create the compensating node reference + NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); + saveActionImpl(compensatingActionNodeRef, compensatingAction); + } + } + else + { + ChildAssociationRef assoc = assocs.get(0); + if (compensatingAction == null) + { + this.nodeService.removeChild(actionNodeRef, assoc.getChildRef()); + } + else + { + saveActionImpl(assoc.getChildRef(), compensatingAction); + } + } + } + + /** + * Save the actions of a composite action + * + * @param compositeActionNodeRef the node reference of the composite action + * @param compositeAction the composite action + */ + private void saveCompositeActions(NodeRef compositeActionNodeRef, CompositeAction compositeAction) + { + // TODO Need a way of sorting the order of the actions + + Map idToAction = new HashMap(); + List orderedIds = new ArrayList(); + for (Action action : compositeAction.getActions()) + { + idToAction.put(action.getId(), action); + orderedIds.add(action.getId()); + } + + List actionRefs = this.nodeService.getChildAssocs(compositeActionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); + for (ChildAssociationRef actionRef : actionRefs) + { + NodeRef actionNodeRef = actionRef.getChildRef(); + if (idToAction.containsKey(actionNodeRef.getId()) == false) + { + // Delete the action + this.nodeService.removeChild(compositeActionNodeRef, actionNodeRef); + } + else + { + // Update the action + Action action = idToAction.get(actionNodeRef.getId()); + saveActionImpl(actionNodeRef, action); + orderedIds.remove(actionNodeRef.getId()); + } + + } + + // Create the actions remaining + for (String actionId : orderedIds) + { + Action action = idToAction.get(actionId); + + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, action.getId()); + + NodeRef actionNodeRef = this.nodeService.createNode( + compositeActionNodeRef, + ActionModel.ASSOC_ACTIONS, + ActionModel.ASSOC_ACTIONS, + ActionModel.TYPE_ACTION, + props).getChildRef(); + + // Update the created details and the node reference + ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); + ((ActionImpl)action).setNodeRef(actionNodeRef); + + saveActionImpl(actionNodeRef, action); + } + } + + /** + * Saves the conditions associated with an action. + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void saveConditions(NodeRef actionNodeRef, Action action) + { + // TODO Need a way of sorting out the order of the conditions + List actionConditionsList = action.getActionConditions(); + saveActionConditionList(actionNodeRef, actionConditionsList, false); + } + + private void saveActionConditionList(NodeRef actionNodeRef, + List actionConditionsList, boolean isComposite) + { + if (logger.isDebugEnabled()) + logger.debug("SaveActionCondition list, "+ actionConditionsList.size() + + (isComposite?" Composite":"") + " conditions to be saved"); + + Map idToCondition = new HashMap(); + List orderedIds = new ArrayList(); + + for (ActionCondition actionCondition : actionConditionsList) + { + idToCondition.put(actionCondition.getId(), actionCondition); + orderedIds.add(actionCondition.getId()); + } + + List conditionRefs = this.nodeService.getChildAssocs( + actionNodeRef, RegexQNamePattern.MATCH_ALL, + !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); + + for (ChildAssociationRef conditionRef : conditionRefs) + { + NodeRef conditionNodeRef = conditionRef.getChildRef(); + if (idToCondition.containsKey(conditionNodeRef.getId()) == false) + { + // Delete the condition + this.nodeService.removeChild(actionNodeRef, conditionNodeRef); + } + else + { + saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + // Update the conditions parameters + saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + orderedIds.remove(conditionNodeRef.getId()); + } + } + + // Create the conditions remaining + for (String nextId : orderedIds) + { + ActionCondition actionCondition = idToCondition.get(nextId); + + if (!isComposite && actionCondition instanceof CompositeActionCondition) + { + if (logger.isDebugEnabled()) + logger.debug("Saving Composite Condition"); + + NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, + ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_COMPOSITE_ACTION_CONDITION); + saveActionConditionList(conditionNodeRef, ((CompositeActionCondition) actionCondition).getActionConditions(), true); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Saving Condition " + actionCondition.getActionConditionDefinitionName()); + + @SuppressWarnings("unused") + NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, + !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION, + ActionModel.TYPE_ACTION_CONDITION); + } + } + } + + /* + private void saveCompositeActionConditionList(NodeRef compositeConditionRef, + List actionConditionsList) + { + if (logger.isDebugEnabled()) + logger.debug("SaveActionCondition list Composite, "+ actionConditionsList.size() + " conditions to be saved"); + + Map idToCondition = new HashMap(); + List orderedIds = new ArrayList(); + + for (ActionCondition actionCondition : actionConditionsList) + { + idToCondition.put(actionCondition.getId(), actionCondition); + orderedIds.add(actionCondition.getId()); + } + + List conditionRefs = this.nodeService.getChildAssocs(compositeConditionRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); + for (ChildAssociationRef conditionRef : conditionRefs) + { + NodeRef conditionNodeRef = conditionRef.getChildRef(); + if (idToCondition.containsKey(conditionNodeRef.getId()) == false) + { + // Delete the condition + this.nodeService.removeChild(compositeConditionRef, conditionNodeRef); + } + else + { + saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + // Update the conditions parameters + saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); + orderedIds.remove(conditionNodeRef.getId()); + } + } + + // Create the conditions remaining + for (String nextId : orderedIds) + { + ActionCondition actionCondition = idToCondition.get(nextId); + NodeRef conditionNodeRef = saveActionCondition(compositeConditionRef, actionCondition, ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_ACTION_CONDITION); + + } + }*/ + + + private NodeRef saveActionCondition(NodeRef actionNodeRef, + ActionCondition actionCondition, QName AssociationQName, QName typeName) + { + Map props = new HashMap(2); + props.put(ActionModel.PROP_DEFINITION_NAME, actionCondition.getActionConditionDefinitionName()); + props.put(ContentModel.PROP_NODE_UUID, actionCondition.getId()); + + NodeRef conditionNodeRef = this.nodeService.createNode( + actionNodeRef, + AssociationQName, + AssociationQName, + typeName, + props).getChildRef(); + + saveConditionProperties(conditionNodeRef, actionCondition); + saveParameters(conditionNodeRef, actionCondition); + return conditionNodeRef; + } + + /** + * Save the condition properties + * + * @param conditionNodeRef + * @param condition + */ + private void saveConditionProperties(NodeRef conditionNodeRef, ActionCondition condition) + { + this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT, condition.getInvertCondition()); + + if (condition instanceof CompositeActionCondition) + { + if (logger.isDebugEnabled()) + { + logger.debug("SAVING OR = " + ((CompositeActionCondition)condition).isORCondition()); + } + this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_ANDOR, new Boolean(((CompositeActionCondition)condition).isORCondition())); + } + } + + /** + * Saves the parameters associated with an action or condition + * + * @param parameterizedNodeRef the parameterized item node reference + * @param item the parameterized item + */ + private void saveParameters(NodeRef parameterizedNodeRef, ParameterizedItem item) + { + Map parameterMap = new HashMap(); + parameterMap.putAll(item.getParameterValues()); + + List parameters = this.nodeService.getChildAssocs(parameterizedNodeRef, + ActionModel.ASSOC_PARAMETERS, ActionModel.ASSOC_PARAMETERS); + for (ChildAssociationRef ref : parameters) + { + NodeRef paramNodeRef = ref.getChildRef(); + Map nodeRefParameterMap = this.nodeService.getProperties(paramNodeRef); + String paramName = (String)nodeRefParameterMap.get(ActionModel.PROP_PARAMETER_NAME); + if (parameterMap.containsKey(paramName) == false) + { + // Delete parameter from node ref + this.nodeService.removeChild(parameterizedNodeRef, paramNodeRef); + } + else + { + // Update the parameter value + nodeRefParameterMap.put(ActionModel.PROP_PARAMETER_VALUE, parameterMap.get(paramName)); + this.nodeService.setProperties(paramNodeRef, nodeRefParameterMap); + parameterMap.remove(paramName); + } + } + + // Add any remaining parameters + for (Map.Entry entry : parameterMap.entrySet()) + { + Map nodeRefProperties = new HashMap(2); + nodeRefProperties.put(ActionModel.PROP_PARAMETER_NAME, entry.getKey()); + nodeRefProperties.put(ActionModel.PROP_PARAMETER_VALUE, entry.getValue()); + + this.nodeService.createNode( + parameterizedNodeRef, + ActionModel.ASSOC_PARAMETERS, + ActionModel.ASSOC_PARAMETERS, + ActionModel.TYPE_ACTION_PARAMETER, + nodeRefProperties); + } + } + + // TODO: Add copy behaviour + + /** + * @see org.alfresco.service.cmr.action.ActionService#getActions(org.alfresco.service.cmr.repository.NodeRef) + */ + public List getActions(NodeRef nodeRef) + { + List result = new ArrayList(); + + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + List actions = this.nodeService.getChildAssocs( + getSavedActionFolderRef(nodeRef), + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS); + for (ChildAssociationRef action : actions) + { + NodeRef actionNodeRef = action.getChildRef(); + result.add(createAction(actionNodeRef)); + } + } + + return result; + } + + /** + * Create an action from the action node reference + * + * @param actionNodeRef the action node reference + * @return the action + */ + public Action createAction(NodeRef actionNodeRef) + { + Action result = null; + + Map properties = this.nodeService.getProperties(actionNodeRef); + + QName actionType = this.nodeService.getType(actionNodeRef); + if (ActionModel.TYPE_COMPOSITE_ACTION.equals(actionType) == true) + { + // Create a composite action + result = new CompositeActionImpl(actionNodeRef, actionNodeRef.getId()); + populateCompositeAction(actionNodeRef, (CompositeAction)result); + } + else + { + // Create an action + result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); + populateAction(actionNodeRef, result); + } + + return result; + } + + /** + * Populate the details of the action from the node reference + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void populateAction(NodeRef actionNodeRef, Action action) + { + // Populate the action properties + populateActionProperties(actionNodeRef, action); + + // Set the parameters + populateParameters(actionNodeRef, action); + + // Set the conditions + List conditions = this.nodeService.getChildAssocs(actionNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); + + if (logger.isDebugEnabled()) + logger.debug("Retrieving " + (conditions==null ? " null" : conditions.size()) + " conditions"); + + for (ChildAssociationRef condition : conditions) + { + NodeRef conditionNodeRef = condition.getChildRef(); + action.addActionCondition(createActionCondition(conditionNodeRef)); + } + } + + /** + * Populates the action properties from the node reference + * + * @param actionNodeRef the action node reference + * @param action the action + */ + private void populateActionProperties(NodeRef actionNodeRef, Action action) + { + Map props = this.nodeService.getProperties(actionNodeRef); + + action.setTitle((String)props.get(ActionModel.PROP_ACTION_TITLE)); + action.setDescription((String)props.get(ActionModel.PROP_ACTION_DESCRIPTION)); + + boolean value = false; + Boolean executeAsynchronously = (Boolean)props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + if (executeAsynchronously != null) + { + value = executeAsynchronously.booleanValue(); + } + action.setExecuteAsynchronously(value); + + ((ActionImpl)action).setCreator((String)props.get(ContentModel.PROP_CREATOR)); + ((ActionImpl)action).setCreatedDate((Date)props.get(ContentModel.PROP_CREATED)); + ((ActionImpl)action).setModifier((String)props.get(ContentModel.PROP_MODIFIER)); + ((ActionImpl)action).setModifiedDate((Date)props.get(ContentModel.PROP_MODIFIED)); + + // Get the compensating action + List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); + if (assocs.size() != 0) + { + Action compensatingAction = createAction(assocs.get(0).getChildRef()); + action.setCompensatingAction(compensatingAction); + } + } + + /** + * Populate the parameters of a parameterized item from the parameterized item node reference + * + * @param parameterizedItemNodeRef the parameterized item node reference + * @param parameterizedItem the parameterized item + */ + private void populateParameters(NodeRef parameterizedItemNodeRef, ParameterizedItem parameterizedItem) + { + List parameters = this.nodeService.getChildAssocs(parameterizedItemNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); + for (ChildAssociationRef parameter : parameters) + { + NodeRef parameterNodeRef = parameter.getChildRef(); + Map properties = this.nodeService.getProperties(parameterNodeRef); + parameterizedItem.setParameterValue( + (String)properties.get(ActionModel.PROP_PARAMETER_NAME), + properties.get(ActionModel.PROP_PARAMETER_VALUE)); + } + } + + /** + * Creates an action condition from an action condition node reference + * + * @param conditionNodeRef the condition node reference + * @return the action condition + */ + private ActionCondition createActionCondition(NodeRef conditionNodeRef) + { + if (logger.isDebugEnabled()) + logger.debug("\tCreateActionCondition: Retrieving Conditions from repository"); + + Map properties = this.nodeService.getProperties(conditionNodeRef); + QName conditionType = this.nodeService.getType(conditionNodeRef); + + ActionCondition condition = null; + if (ActionModel.TYPE_COMPOSITE_ACTION_CONDITION.equals(conditionType) == false) + { + condition = new ActionConditionImpl(conditionNodeRef.getId(), + (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("\tRetrieving Composite Condition from repository"); + } + + // Create a composite condition + CompositeActionCondition compositeCondition = new CompositeActionConditionImpl(GUID.generate()); + populateCompositeActionCondition(conditionNodeRef, compositeCondition); + + condition = compositeCondition; + } + + Boolean invert = (Boolean)this.nodeService.getProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT); + condition.setInvertCondition(invert == null? false: invert.booleanValue()); + + populateParameters(conditionNodeRef, condition); + return condition; + } + + private void populateCompositeActionCondition(NodeRef compositeNodeRef, CompositeActionCondition compositeActionCondition) + { + List conditions = this.nodeService.getChildAssocs(compositeNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); + + Boolean OR = (Boolean) this.nodeService.getProperty(compositeNodeRef, ActionModel.PROP_CONDITION_ANDOR); + + if (logger.isDebugEnabled()) + { + logger.debug("\tPopulating Composite Condition with subconditions, Condition OR = " + OR); + } + + compositeActionCondition.setORCondition(OR == null? false: OR.booleanValue()); + + for (ChildAssociationRef conditionNodeRef : conditions) + { + NodeRef actionNodeRef = conditionNodeRef.getChildRef(); + ActionCondition currentCondition = createActionCondition(actionNodeRef); + + if (logger.isDebugEnabled()) + logger.debug("\t\tAdding subcondition " + currentCondition.getActionConditionDefinitionName()); + + compositeActionCondition.addActionCondition(currentCondition); + } + } + + + /** + * Populates a composite action from a composite action node reference + * + * @param compositeNodeRef the composite action node reference + * @param compositeAction the composite action + */ + public void populateCompositeAction(NodeRef compositeNodeRef, CompositeAction compositeAction) + { + populateAction(compositeNodeRef, compositeAction); + + List actions = this.nodeService.getChildAssocs(compositeNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); + for (ChildAssociationRef action : actions) + { + NodeRef actionNodeRef = action.getChildRef(); + compositeAction.addAction(createAction(actionNodeRef)); + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#getAction(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public Action getAction(NodeRef nodeRef, String actionId) + { + Action result = null; + + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, actionId); + if (actionNodeRef != null) + { + result = createAction(actionNodeRef); + } + } + + return result; + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#removeAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + */ + public void removeAction(NodeRef nodeRef, Action action) + { + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); + if (actionNodeRef != null) + { + this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), actionNodeRef); + } + } + } + + /** + * @see org.alfresco.service.cmr.action.ActionService#removeAllActions(org.alfresco.service.cmr.repository.NodeRef) + */ + public void removeAllActions(NodeRef nodeRef) + { + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + { + List actions = new ArrayList(this.nodeService.getChildAssocs(getSavedActionFolderRef(nodeRef), RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS)); + for (ChildAssociationRef action : actions) + { + this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), action.getChildRef()); + } + } + } + + /** + * Add a pending action to the list to be queued for execution once the transaction is completed. + * + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + * @param checkConditions indicates whether to check the conditions before execution + */ + @SuppressWarnings("unchecked") + private void addPostTransactionPendingAction( + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + Set actionChain) + { + if (logger.isDebugEnabled() == true) + { + StringBuilder builder = new StringBuilder("addPostTransactionPendingAction action chain = "); + if (actionChain == null) + { + builder.append("null"); + } + else + { + for (String value : actionChain) + { + builder.append(value).append(" "); + } + } + logger.debug(builder.toString()); + logger.debug("Current action = " + action.getId()); + } + + // Don't continue if the action is already in the action chain + if (actionChain == null || actionChain.contains(action.getId()) == false) + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Doing addPostTransactionPendingAction"); + } + + // Set the run as user to the current user + if (logger.isDebugEnabled() == true) + { + logger.debug("The current user is: " + this.authenticationContext.getCurrentUserName()); + } + ((ActionImpl)action).setRunAsUser(this.authenticationContext.getCurrentUserName()); + + // Ensure that the transaction listener is bound to the transaction + AlfrescoTransactionSupport.bindListener(this.transactionListener); + + // Add the pending action to the transaction resource + List pendingActions = (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); + if (pendingActions == null) + { + pendingActions = new ArrayList(); + AlfrescoTransactionSupport.bindResource(POST_TRANSACTION_PENDING_ACTIONS, pendingActions); + } + + // Check that action has only been added to the list once + PendingAction pendingAction = new PendingAction(action, actionedUponNodeRef, checkConditions, actionChain); + if (pendingActions.contains(pendingAction) == false) + { + pendingActions.add(pendingAction); + } + } + } + + /** + * @see org.alfresco.repo.action.RuntimeActionService#getPostTransactionPendingActions() + */ + @SuppressWarnings("unchecked") + private List getPostTransactionPendingActions() + { + return (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); + } + + /** + * Pending action details class + */ + private class PendingAction + { + /** + * The action + */ + private Action action; + + /** + * The actioned upon node reference + */ + private NodeRef actionedUponNodeRef; + + /** + * Indicates whether the conditions should be checked before the action is executed + */ + private boolean checkConditions; + + private Set actionChain; + + /** + * Constructor + * + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + * @param checkConditions indicated whether the conditions need to be checked + */ + public PendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set actionChain) + { + this.action = action; + this.actionedUponNodeRef = actionedUponNodeRef; + this.checkConditions = checkConditions; + this.actionChain = actionChain; + } + + /** + * Get the action + * + * @return the action + */ + public Action getAction() + { + return action; + } + + /** + * Get the actioned upon node reference + * + * @return the actioned upon node reference + */ + public NodeRef getActionedUponNodeRef() + { + return actionedUponNodeRef; + } + + /** + * Get the check conditions value + * + * @return indicates whether the condition should be checked + */ + public boolean getCheckConditions() + { + return this.checkConditions; + } + + public Set getActionChain() + { + return this.actionChain; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() + { + int hashCode = 37 * this.actionedUponNodeRef.hashCode(); + hashCode += 37 * this.action.hashCode(); + return hashCode; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj instanceof PendingAction) + { + PendingAction that = (PendingAction) obj; + return (this.action.equals(that.action) && this.actionedUponNodeRef.equals(that.actionedUponNodeRef)); + } + else + { + return false; + } + } + } + /** + * @return Returns {@link AdctionParameterTypeCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return AdctionParameterTypeCopyBehaviourCallback.INSTANCE; + } + + /** + * Records any potential d:noderef properties for once the copy is done. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class AdctionParameterTypeCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final AdctionParameterTypeCopyBehaviourCallback INSTANCE = + new AdctionParameterTypeCopyBehaviourCallback(); + + @Override + public Map getCopyProperties( + QName classQName, CopyDetails copyDetails, Map properties) + { + NodeRef sourceNodeRef = copyDetails.getSourceNodeRef(); + recordNodeRefsForRepointing(sourceNodeRef, properties, ActionModel.PROP_PARAMETER_VALUE); + // Don't modify the properties + return properties; + } + } + + /** + * Ensures that d:noderef properties are repointed if the target was also copied as part of the + * hierarchy. + */ + @SuppressWarnings("unchecked") + public void onCopyComplete( + QName classRef, + NodeRef sourceNodeRef, + NodeRef targetNodeRef, + boolean copyToNewNode, + Map copyMap) + { + AdctionParameterTypeCopyBehaviourCallback.INSTANCE.repointNodeRefs( + sourceNodeRef, + targetNodeRef, + ActionModel.PROP_PARAMETER_VALUE, + copyMap, + nodeService); + } +} diff --git a/source/java/org/alfresco/repo/action/ActionsAspect.java b/source/java/org/alfresco/repo/action/ActionsAspect.java index 619686f226..a8e03b4e43 100644 --- a/source/java/org/alfresco/repo/action/ActionsAspect.java +++ b/source/java/org/alfresco/repo/action/ActionsAspect.java @@ -24,147 +24,172 @@ */ package org.alfresco.repo.action; -import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.repo.policy.Behaviour; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.PropertyCheck; /** * Class containing behaviour for the actions aspect * * @author Roy Wetherall */ -public class ActionsAspect +public class ActionsAspect implements CopyServicePolicies.OnCopyNodePolicy, CopyServicePolicies.OnCopyCompletePolicy { - private Behaviour onAddAspectBehaviour; - - private PolicyComponent policyComponent; - + private PolicyComponent policyComponent; + private BehaviourFilter behaviourFilter; private RuleService ruleService; + private NodeService nodeService; - private NodeService nodeService; - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } - public void setRuleService(RuleService ruleService) + public void setRuleService(RuleService ruleService) { this.ruleService = ruleService; } - public void init() - { - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), - ActionModel.ASPECT_ACTIONS, - new JavaBehaviour(this, "onCopyNode")); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), + public void init() + { + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); + PropertyCheck.mandatory(this, "ruleService", ruleService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ActionModel.ASPECT_ACTIONS, - new JavaBehaviour(this, "onCopyComplete")); - - this.onAddAspectBehaviour = new JavaBehaviour(this, "onAddAspect"); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), + ActionModel.ASPECT_ACTIONS, + new JavaBehaviour(this, "onCopyComplete")); + + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ActionModel.ASPECT_ACTIONS, - onAddAspectBehaviour); - } - - - - /** - * Helper to diable the on add aspect policy behaviour. Helpful when importing, - * copying and other bulk respstorative operations. - * - * TODO will eventually be redundant when policies can be enabled/diabled in the - * policy componenet - */ - public void disbleOnAddAspect() - { - this.onAddAspectBehaviour.disable(); + new JavaBehaviour(this, "onAddAspect")); } /** - * Helper to enable the on add aspect policy behaviour. Helpful when importing, - * copying and other bulk respstorative operations. + * On add aspect policy behaviour * - * TODO will eventually be redundant when policies can be enabled/diabled in the - * policy componenet + * @param nodeRef + * @param aspectTypeQName */ - public void enableOnAddAspect() + public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { - this.onAddAspectBehaviour.enable(); - } - - /** - * On add aspect policy behaviour - * @param nodeRef - * @param aspectTypeQName - */ - public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) - { this.ruleService.disableRules(nodeRef); try { - this.nodeService.createNode( + this.nodeService.createNode( nodeRef, - ActionModel.ASSOC_ACTION_FOLDER, ActionModel.ASSOC_ACTION_FOLDER, - ContentModel.TYPE_SYSTEM_FOLDER); + ActionModel.ASSOC_ACTION_FOLDER, + ContentModel.TYPE_SYSTEM_FOLDER); } finally { this.ruleService.enableRules(nodeRef); } - } - - public void onCopyNode( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, + } + + /** + * @return Returns {@link ActionsAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return new ActionsAspectCopyBehaviourCallback(behaviourFilter); + } + + /** + * Extends the default copy behaviour to include cascading to action folders. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class ActionsAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private final BehaviourFilter behaviourFilter; + private ActionsAspectCopyBehaviourCallback(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * Disables the aspect behaviour for this node + * + * @return Returns true + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + NodeRef targetNodeRef = copyDetails.getTargetNodeRef(); + behaviourFilter.disableBehaviour(targetNodeRef, ActionModel.ASPECT_ACTIONS); + // Always copy + return true; + } + + /** + * Always cascades to the action folders + */ + @Override + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + if (childAssocRef.getTypeQName().equals(ActionModel.ASSOC_ACTION_FOLDER)) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + throw new IllegalStateException( + "Behaviour should have been invoked: \n" + + " Aspect: " + this.getClass().getName() + "\n" + + " " + childAssocCopyDetails + "\n" + + " " + copyDetails); + } + } + } + + /** + * Re-enable aspect behaviour for the source node + */ + public void onCopyComplete( + QName classRef, + NodeRef sourceNodeRef, + NodeRef destinationRef, boolean copyToNewNode, - PolicyScope copyDetails) - { - copyDetails.addAspect(ActionModel.ASPECT_ACTIONS); - - List assocs = this.nodeService.getChildAssocs( - sourceNodeRef, - RegexQNamePattern.MATCH_ALL, - ActionModel.ASSOC_ACTION_FOLDER); - for (ChildAssociationRef assoc : assocs) - { - copyDetails.addChildAssociation(classRef, assoc, true); - } - - this.onAddAspectBehaviour.disable(); - } - - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef destinationRef, - boolean copyToNew, - Map copyMap) - { - this.onAddAspectBehaviour.enable(); - } + Map copyMap) + { + behaviourFilter.enableBehaviour(sourceNodeRef, ActionModel.ASPECT_ACTIONS); + } } diff --git a/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java b/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java index 1d8bbad6ab..d89eb4726d 100644 --- a/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java +++ b/source/java/org/alfresco/repo/coci/WorkingCopyAspect.java @@ -25,19 +25,25 @@ package org.alfresco.repo.coci; +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -public class WorkingCopyAspect +public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy { /** * Policy component @@ -91,9 +97,13 @@ public class WorkingCopyAspect { // Register copy behaviour for the working copy aspect this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ContentModel.TYPE_CMOBJECT, + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_WORKING_COPY, - new JavaBehaviour(this, "onCopy")); + new JavaBehaviour(this, "getCopyCallback")); // register onBeforeDelete class behaviour for the working copy aspect this.policyComponent.bindClassBehaviour( @@ -102,27 +112,6 @@ public class WorkingCopyAspect new JavaBehaviour(this, "beforeDeleteNode")); } - /** - * onCopy policy behaviour - * - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(QName, NodeRef, StoreRef, boolean, PolicyScope) - */ - public void onCopy( - QName sourceClassRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) - { - if (copyToNewNode == false) - { - // Make sure that the name of the node is not updated with the working copy name - copyDetails.removeProperty(ContentModel.PROP_NAME); - } - - // NOTE: the working copy aspect is not added since it should not be copyied - } - /** * beforeDeleteNode policy behaviour * @@ -147,5 +136,63 @@ public class WorkingCopyAspect } } } + + /** + * @return Returns {@link WorkingCopyAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return WorkingCopyAspectCopyBehaviourCallback.instance; + } + /** + * Dual behaviour to ensure that cm:name is not copied if the source node has the + * cm:workingCopy aspect, and to prevent the cm:workingCopy aspect from + * being carried to the new node. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class WorkingCopyAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static WorkingCopyAspectCopyBehaviourCallback instance = new WorkingCopyAspectCopyBehaviourCallback(); + + /** + * Disallows copying of the {@link ContentModel#ASPECT_WORKING_COPY cm:workingCopy} aspect. + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + if (classQName.equals(ContentModel.ASPECT_WORKING_COPY)) + { + return false; + } + else + { + return true; + } + } + + /** + * Prevents copying off the {@link ContentModel#PROP_NAME cm:name} property. + */ + @Override + public Map getCopyProperties( + QName classQName, CopyDetails copyDetails, Map properties) + { + if (classQName.equals(ContentModel.ASPECT_WORKING_COPY)) + { + return Collections.emptyMap(); + } + else if (copyDetails.getSourceNodeAspectQNames().contains(ContentModel.ASPECT_WORKING_COPY)) + { + properties.remove(ContentModel.PROP_NAME); + return properties; + } + else + { + return properties; + } + } + } } diff --git a/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java new file mode 100644 index 0000000000..1a4c756cf9 --- /dev/null +++ b/source/java/org/alfresco/repo/copy/AbstractCopyBehaviourCallback.java @@ -0,0 +1,185 @@ +/* + * 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.copy; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.transaction.TransactionalResourceHelper; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; + +/** + * Abstract implementation to allow for easier migration if the interface changes. + * + * @author Derek Hulley + * @since 3.2 + */ +public abstract class AbstractCopyBehaviourCallback implements CopyBehaviourCallback +{ + private static final String KEY_NODEREF_REPOINTING_PREFIX = "recordNodeRefPropertiesForRepointing-"; + + /** + * @return Returns {@link ChildAssocRecurseAction#RESPECT_RECURSE_FLAG} + */ + public ChildAssocRecurseAction getChildAssociationRecurseAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + return ChildAssocRecurseAction.RESPECT_RECURSE_FLAG; + } + + /** + * @throws IllegalStateException always + */ + protected void throwExceptionForUnexpectedBehaviour(CopyDetails copyDetails, String ... otherDetails) + { + StringBuilder sb = new StringBuilder(512); + sb.append("Behaviour should have been invoked: \n" + + " Aspect: " + this.getClass().getName() + "\n" + + " " + copyDetails + "\n"); + for (String otherDetail : otherDetails) + { + sb.append(" ").append(otherDetail).append("\n"); + } + throw new IllegalStateException(sb.toString()); + } + + /** + * Helper method to transactionally record NodeRef properties so that they + * can later be fixed up to point to the relative, after-copy locations. + *

+ * When the copy has been completed, the second stage of the process can be applied. + * + * @param sourceNodeRef the node that is being copied + * @param properties the node properties being copied + * @param propertyQName the qualified name of the property to check + * + * @see #repointNodeRefs(NodeRef, QName, Map, NodeService) + */ + public void recordNodeRefsForRepointing( + NodeRef sourceNodeRef, + Map properties, + QName propertyQName) + { + Serializable parameterValue = properties.get(propertyQName); + if (parameterValue != null && + (parameterValue instanceof Collection || parameterValue instanceof NodeRef)) + { + String key = KEY_NODEREF_REPOINTING_PREFIX + propertyQName.toString(); + // Store it for later + Map map = TransactionalResourceHelper.getMap(key); + map.put(sourceNodeRef, parameterValue); + } + } + + /** + * The second stage of the NodeRef repointing. Call this method to have + * any NodeRef properties readjusted to reflect the copied node hierarchy. + * Only use this method if it a requirement for the particular type or aspect that you + * are coding for. + * + * @param sourceNodeRef the source node + * @param propertyQName the target node i.e. the copy of the source node + * @param copyMap the full hierarchy copy map of source to copies + * + * @see #recordNodeRefsForRepointing(NodeRef, Map, QName) + */ + @SuppressWarnings("unchecked") + public void repointNodeRefs( + NodeRef sourceNodeRef, + NodeRef targetNodeRef, + QName propertyQName, + Map copyMap, + NodeService nodeService) + { + String key = KEY_NODEREF_REPOINTING_PREFIX + propertyQName.toString(); + Map map = TransactionalResourceHelper.getMap(key); + Serializable value = map.get(sourceNodeRef); + if (value == null) + { + // Don't bother. The source node did not have a NodeRef property + return; + } + Serializable newValue = null; + if (value instanceof Collection) + { + Collection oldList = (Collection) value; + List newList = new ArrayList(oldList.size()); + for (Serializable oldListValue : oldList) + { + Serializable newListValue = oldListValue; + if (oldListValue instanceof NodeRef) + { + newListValue = repointNodeRef(copyMap, (NodeRef) value); + } + // Put the value in the new list even though the new list might be discarded + newList.add(newListValue); + // Check if the value changed + if (!newListValue.equals(oldListValue)) + { + // The value changed, so the new list will have to be set onto the target node + newValue = newListValue; + } + } + } + else if (value instanceof NodeRef) + { + NodeRef newNodeRef = repointNodeRef(copyMap, (NodeRef) value); + if (!newNodeRef.equals(value)) + { + // The value changed, so the new list will have to be set onto the target node + newValue = newNodeRef; + } + } + else + { + throw new IllegalStateException("Should only have Collections and NodeRef values"); + } + // Fix the node property on the target, if necessary + if (newValue != null) + { + nodeService.setProperty(targetNodeRef, propertyQName, newValue); + } + } + + private NodeRef repointNodeRef(Map copyMap, NodeRef pointerNodeRef) + { + NodeRef copiedPointerNodeRef = copyMap.get(pointerNodeRef); + if (copiedPointerNodeRef == null) + { + return pointerNodeRef; + } + else + { + return copiedPointerNodeRef; + } + } +} diff --git a/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java new file mode 100644 index 0000000000..6e6f74b8e4 --- /dev/null +++ b/source/java/org/alfresco/repo/copy/CompoundCopyBehaviourCallback.java @@ -0,0 +1,251 @@ +/* + * 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.copy; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Handles compound behavioural callbacks for the same dictionary class (type or aspect). + *

+ * When multiple policy handlers register callback for the same model class, an instance + * of this class is used to resolve the calls. The behaviour is sometimes able to resolve + * conflicts and sometimes not. Look at the individual callback methods to see how + * conflicts are handled. + * + * @author Derek Hulley + * @since 3.2 + */ +public class CompoundCopyBehaviourCallback extends AbstractCopyBehaviourCallback +{ + private static Log logger = LogFactory.getLog(CompoundCopyBehaviourCallback.class); + + private QName classQName; + private List callbacks; + + /** + * + * @param classQName the + */ + public CompoundCopyBehaviourCallback(QName classQName) + { + this.classQName = classQName; + callbacks = new ArrayList(2); + } + + public void addBehaviour(CopyBehaviourCallback callback) + { + callbacks.add(callback); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(1024); + sb.append("\n") + .append("CompoundCopyBehaviourCallback: \n") + .append(" Model Class: ").append(classQName); + boolean first = true; + for (CopyBehaviourCallback callback : callbacks) + { + if (first) + { + first = false; + sb.append("\n"); + } + sb.append(" ").append(callback.getClass().getName()); + } + return sb.toString(); + } + + /** + * Individual callbacks effectively have a veto on the copy i.e. if one of the + * callbacks returns false for {@link CopyBehaviourCallback#mustCopy(NodeRef)}, + * then the copy will NOT proceed. However, a warning is generated indicating that + * there is a conflict in the defined behaviour. + * + * @return Returns true if all registered callbacks return true + * or false if any of the registered callbacks return false. + */ + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + CopyBehaviourCallback firstVeto = null; + for (CopyBehaviourCallback callback : callbacks) + { + boolean mustCopyLocal = callback.getMustCopy(classQName, copyDetails); + if (firstVeto == null && !mustCopyLocal) + { + firstVeto = callback; + } + if (mustCopyLocal && firstVeto != null) + { + // The callback says 'copy' but there is already a veto in place + logger.warn( + "CopyBehaviourCallback '" + callback.getClass().getName() + "' " + + "is attempting to induce a copy when callback '" + firstVeto.getClass().getName() + + "' has already vetoed it. Copying of '" + copyDetails.getSourceNodeRef() + + "' will not occur."); + } + } + // Done + if (firstVeto == null) + { + // Allowed by all + if (logger.isDebugEnabled()) + { + logger.debug( + "All copy behaviours voted for a copy of node \n" + + " " + copyDetails + "\n" + + " " + this); + } + return true; + } + else + { + // Vetoed + if (logger.isDebugEnabled()) + { + logger.debug( + "Copy behaviour vetoed for node " + copyDetails.getSourceNodeRef() + "\n" + + " First veto: " + firstVeto.getClass().getName() + "\n" + + " " + copyDetails + "\n" + + " " + this); + } + return false; + } + } + + /** + * Uses the {@link ChildAssocCopyAction} ordering to drive priority i.e. a vote + * to copy will override a vote NOT to copy. + * + * @return Returns the most lively choice of action + */ + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssocCopyAction bestAction = ChildAssocCopyAction.IGNORE; + for (CopyBehaviourCallback callback : callbacks) + { + ChildAssocCopyAction action = callback.getChildAssociationCopyAction( + classQName, + copyDetails, + childAssocCopyDetails); + if (action.compareTo(bestAction) > 0) + { + // We've trumped the last best one + bestAction = action; + } + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Child association copy behaviour: " + bestAction + "\n" + + " " + childAssocCopyDetails + "\n" + + " " + copyDetails + "\n" + + " " + this); + } + return bestAction; + } + + /** + * Uses the {@link ChildAssocRecurseAction} ordering to drive recursive copy behaviour. + * + * @return Returns the most lively choice of action + */ + @Override + public ChildAssocRecurseAction getChildAssociationRecurseAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssocRecurseAction bestAction = ChildAssocRecurseAction.RESPECT_RECURSE_FLAG; + for (CopyBehaviourCallback callback : callbacks) + { + ChildAssocRecurseAction action = callback.getChildAssociationRecurseAction( + classQName, + copyDetails, + childAssocCopyDetails); + if (action.compareTo(bestAction) > 0) + { + // We've trumped the last best one + bestAction = action; + } + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Child association recursion behaviour: " + bestAction + "\n" + + " " + childAssocCopyDetails + "\n" + + " " + copyDetails + "\n" + + " " + this); + } + return bestAction; + } + + /** + * The lowest common denominator applies here. The properties are passed to each + * callback in turn. The resulting map is then passed to the next callback and so + * on. If any callback removes or alters properties, these will not be recoverable. + * + * @return Returns the least properties assigned for the copy by any individual + * callback handler + */ + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + Map copyProperties = new HashMap(properties); + for (CopyBehaviourCallback callback : callbacks) + { + copyProperties = callback.getCopyProperties(classQName, copyDetails, properties); + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Copy properties: \n" + + " " + copyDetails + "\n" + + " " + this + "\n" + + " Before: " + properties + "\n" + + " After: " + copyProperties); + } + return copyProperties; + } +} diff --git a/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java new file mode 100644 index 0000000000..5ec614a9f8 --- /dev/null +++ b/source/java/org/alfresco/repo/copy/CopyBehaviourCallback.java @@ -0,0 +1,246 @@ +/* + * 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.copy; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * A callback to modify copy behaviour associated with a given type or aspect. This + * callback is called per type and per aspect. + * + * @author Derek Hulley + * @since 3.2 + */ +public interface CopyBehaviourCallback +{ + /** + * Description of how the copy process should traverse a child association. + * The order of this enum denotes the priority when mixing behaviour as well; + * that is to say that a 'forced recursion' will occur even if an 'ignore' is + * also provided by the registered behaviour callbacks. + * + * @author Derek Hulley + * @since 3.2 + */ + public enum ChildAssocCopyAction implements Comparable + { + /** + * Ignore the child association + */ + IGNORE + { + }, + /** + * Copy the association only, keeping the existing child node + */ + COPY_ASSOC + { + }, + /** + * Traverse the child association and copy the child node + */ + COPY_CHILD + { + }; + } + + /** + * Description of how the copy process should behave when copying an association. + * + * @author Derek Hulley + * @since 3.2 + */ + public enum ChildAssocRecurseAction implements Comparable + { + /** + * Respect the client's recursion decision + */ + RESPECT_RECURSE_FLAG + { + }, + /** + * Force all further copies of the source hierarchy to recurse into children. + * This allows behaviour to force a copy of a subtree that it expects to + * exist. + *

+ * NOTE: Any part of the source subtree can still terminate the recursion, + * so this is mainly useful where the subtree contains the default + * behaviour. + */ + FORCE_RECURSE + { + }, + } + + /** + * A simple bean class to convey information to the callback methods dealing with + * copying of child associations. + * + * @see CopyBehaviourCallback#getChildAssociationCopyAction(QName, CopyDetails, ChildAssociationRef, NodeRef, boolean) + * @see CopyBehaviourCallback#getChildAssociationRecurseAction(QName, CopyDetails, ChildAssociationRef, boolean) + * + * @author Derek Hulley + * @since 3.2 + */ + public static final class CopyChildAssociationDetails + { + private final ChildAssociationRef childAssocRef; + private final NodeRef targetNodeRef; + private final boolean targetNodeIsNew; + private final boolean copyChildren; + + public CopyChildAssociationDetails( + ChildAssociationRef childAssocRef, + NodeRef targetNodeRef, + boolean targetNodeIsNew, + boolean copyChildren) + { + this.childAssocRef = childAssocRef; + this.targetNodeRef = targetNodeRef; + this.targetNodeIsNew = targetNodeIsNew; + this.copyChildren = copyChildren; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(256); + sb.append("CopyChildAssociationDetails ") + .append("[ childAssocRef=").append(childAssocRef) + .append(", targetNodeRef=").append(targetNodeRef) + .append(", targetNodeIsNew=").append(targetNodeIsNew) + .append(", copyChildren=").append(copyChildren) + .append("]"); + return sb.toString(); + } + + /** + * @return Returns the association being examined + */ + public final ChildAssociationRef getChildAssocRef() + { + return childAssocRef; + } + + /** + * @return Returns the target node that will be the + * new parent if the association is copied + */ + public final NodeRef getTargetNodeRef() + { + return targetNodeRef; + } + + /** + * + * @return Returns true if the {@link #getTargetNodeRef() target node} + * has been newly created by the copy process or false if it + * is a node that existed prior to the copy + */ + public final boolean isTargetNodeIsNew() + { + return targetNodeIsNew; + } + + /** + * Get the current recursion behaviour. This can be ignored and even altered, if required. + * + * @return Returns true if the copy process is currently recursing to + * child associations or false if not. + */ + public final boolean isCopyChildren() + { + return copyChildren; + } + } + + /** + * Determine if this type or aspect must be copied. If the callback is for a type + * (not aspect) then this determines if the node is copied at all. If the callback + * is for an aspect, then this determines if the aspect is copied. + * + * @param classQName the name of the class that this is being invoked for + * @param copyDetails the source node's copy details for quick reference + * @return true if the type or aspect that this behaviour + * represents must be copied. + */ + boolean getMustCopy(QName classQName, CopyDetails copyDetails); + + /** + * Determine if a copy should copy the child, the association only or do nothing with + * the given association. + *

+ * This is called regardless of whether 'cascade' copy has been selected by the client + * of the copy. Some type and aspect behaviour will mandate a copy of the child + * associations regardless of whether recursion is on. + * + * @param classQName the name of the class that this is being invoked for + * @param copyDetails the source node's copy details for quick reference + * @param childAssocCopyDetails all other details relating to the child association + * @return Returns the copy {@link ChildAssocCopyAction action} to take + * with the given child association + */ + ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails); + + /** + * Once the child association copy action has been chosen, the policy callback can + * dictate whether or not to force further recursion. This cannot prevent + * behaviour further down the hierarchy from stopping the copy. + * + * @param classQName the name of the class that this is being invoked for + * @param copyDetails the source node's copy details for quick reference + * @param childAssocCopyDetails all other details relating to the child association + * @return Returns the type of {@link ChildAssocRecurseAction recursion} + * to perform after having copied the child association + * + * @see #getChildAssociationCopyAction(QName, CopyDetails, ChildAssociationRef, boolean) + */ + ChildAssocRecurseAction getChildAssociationRecurseAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails); + + /** + * Modify the properties that are copied across. + * + * @param classQName the name of the class that this is being invoked for + * @param copyDetails the source node's copy details for quick reference + * @param properties the type- or aspect-specific properties that can be copied. + * The map can be manipulated and returned as required. + * @return Returns the type or aspect properties that should be copied. + */ + Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties); +} diff --git a/source/java/org/alfresco/repo/copy/CopyDetails.java b/source/java/org/alfresco/repo/copy/CopyDetails.java new file mode 100644 index 0000000000..eb0218407c --- /dev/null +++ b/source/java/org/alfresco/repo/copy/CopyDetails.java @@ -0,0 +1,166 @@ +/* + * 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.copy; + +import java.io.Serializable; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * Simple Java bean that contains the details of a copy process underway. + * + * @see CopyServicePolicies + * + * @author Derek Hulley + * @since 3.2 + */ +public class CopyDetails +{ + private final NodeRef sourceNodeRef; + private final QName sourceNodeTypeQName; + private final Set sourceNodeAspectQNames; + private final Map sourceNodeProperties; + private final NodeRef targetParentNodeRef; + private final NodeRef targetNodeRef; + private final boolean targetNodeIsNew; + private final QName assocTypeQName; + private final QName assocQName; + + public CopyDetails( + NodeRef sourceNodeRef, + QName sourceNodeTypeQName, + Set sourceNodeAspectQNames, + Map sourceNodeProperties, + NodeRef targetParentNodeRef, + NodeRef targetNodeRef, + boolean targetNodeIsNew, + QName assocTypeQName, + QName assocQName) + { + this.sourceNodeRef = sourceNodeRef; + this.sourceNodeTypeQName = sourceNodeTypeQName; + this.sourceNodeAspectQNames = sourceNodeAspectQNames; + this.sourceNodeProperties = sourceNodeProperties; + this.targetParentNodeRef = targetParentNodeRef; + this.targetNodeRef = targetNodeRef; + this.targetNodeIsNew = targetNodeIsNew; + this.assocTypeQName = assocTypeQName; + this.assocQName = assocQName; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(512); + sb.append("CopyDetails") + .append(" [source=").append(sourceNodeRef) + .append(", targetParent=").append(targetParentNodeRef) + .append(", targetNode=").append(targetNodeRef) + .append(", targetNodeIsNew=").append(targetNodeIsNew) + .append(", assocTypeQName=").append(assocTypeQName) + .append(", assocQName=").append(assocQName) + .append("]"); + return sb.toString(); + } + + /** + * Get the source node + */ + public final NodeRef getSourceNodeRef() + { + return sourceNodeRef; + } + + /** + * Get the type of the source node + */ + public final QName getSourceNodeTypeQName() + { + return this.sourceNodeTypeQName; + } + + /** + * Get the aspects associated with the source node + */ + public final Set getSourceNodeAspectQNames() + { + return sourceNodeAspectQNames; + } + + /** + * Get the properties associated with the source node + */ + public final Map getSourceNodeProperties() + { + return sourceNodeProperties; + } + + /** + * Get the node under which the new/existing copy will be placed + */ + public final NodeRef getTargetParentNodeRef() + { + return targetParentNodeRef; + } + + /** + * Get the node to which the copy will occur. The node may not + * yet exist. + */ + public final NodeRef getTargetNodeRef() + { + return targetNodeRef; + } + + /** + * Determine if the {@link #getTargetNodeRef() target node} was newly-created + * for the copy or if it pre-existed. + * + * @return true if the node was created by the copy + */ + public final boolean isTargetNodeIsNew() + { + return targetNodeIsNew; + } + + /** + * Get the new association type qualified name + */ + public final QName getAssocTypeQName() + { + return assocTypeQName; + } + + /** + * Get the association path qualified name + */ + public final QName getAssocQName() + { + return assocQName; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java index 9718cb645f..62dc6d0927 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java @@ -27,36 +27,35 @@ package org.alfresco.repo.copy; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +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 org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; -import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionServiceImpl; +import org.alfresco.repo.copy.CopyBehaviourCallback.ChildAssocCopyAction; +import org.alfresco.repo.copy.CopyBehaviourCallback.ChildAssocRecurseAction; +import org.alfresco.repo.copy.CopyBehaviourCallback.CopyChildAssociationDetails; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.CopyServiceException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchService; @@ -67,6 +66,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.GUID; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -78,26 +78,26 @@ import org.apache.commons.logging.LogFactory; */ public class CopyServiceImpl implements CopyService { - /** + /** * The logger */ - private static Log logger = LogFactory.getLog(ActionServiceImpl.class); - - /** I18N labels */ - private String COPY_OF_LABEL = "copy_service.copy_of_label"; - + private static Log logger = LogFactory.getLog(ActionServiceImpl.class); + + /** I18N labels */ + private String COPY_OF_LABEL = "copy_service.copy_of_label"; + /** The node service */ private NodeService nodeService; private NodeService internalNodeService; - - /** The dictionary service*/ - private DictionaryService dictionaryService; - + + /** The dictionary service*/ + private DictionaryService dictionaryService; + /** The search service */ private SearchService searchService; - /** Policy component */ - private PolicyComponent policyComponent; + /** Policy component */ + private PolicyComponent policyComponent; /** Rule service */ private RuleService ruleService; @@ -108,9 +108,9 @@ public class CopyServiceImpl implements CopyService /** Authentication service */ private AuthenticationService authenticationService; - /** Policy delegates */ - private ClassPolicyDelegate onCopyNodeDelegate; - private ClassPolicyDelegate onCopyCompleteDelegate; + /** Policy delegates */ + private ClassPolicyDelegate onCopyNodeDelegate; + private ClassPolicyDelegate onCopyCompleteDelegate; /** * Set the node service @@ -125,32 +125,32 @@ public class CopyServiceImpl implements CopyService /** * Sets the internal node service * - * @param internalNodeService the internal node service + * @param internalNodeService the internal node service */ public void setInternalNodeService(NodeService internalNodeService) { - this.internalNodeService = internalNodeService; - } - - /** - * Sets the dictionary service - * - * @param dictionaryService the dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Sets the policy component - * - * @param policyComponent the policy component - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } + this.internalNodeService = internalNodeService; + } + + /** + * Sets the dictionary service + * + * @param dictionaryService the dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Sets the policy component + * + * @param policyComponent the policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } /** * Sets the search service @@ -175,836 +175,196 @@ public class CopyServiceImpl implements CopyService /** * Set the permission service * - * @param permissionService the permission service + * @param permissionService the permission service */ public void setPermissionService(PermissionService permissionService) { - this.permissionService = permissionService; - } + this.permissionService = permissionService; + } /** * Sets the authentication service * - * @param authenticationService the authentication service + * @param authenticationService the authentication service */ public void setAuthenticationService(AuthenticationService authenticationService) { - this.authenticationService = authenticationService; - } + this.authenticationService = authenticationService; + } - /** - * Initialise method - */ - public void init() - { - // Register the policies - this.onCopyNodeDelegate = this.policyComponent.registerClassPolicy(CopyServicePolicies.OnCopyNodePolicy.class); - this.onCopyCompleteDelegate = this.policyComponent.registerClassPolicy(CopyServicePolicies.OnCopyCompletePolicy.class); - - // Register policy behaviours - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), - ContentModel.ASPECT_COPIEDFROM, - new JavaBehaviour(this, "copyAspectOnCopy")); + /** + * Initialise method + */ + public void init() + { + // Register the policies + onCopyNodeDelegate = policyComponent.registerClassPolicy(CopyServicePolicies.OnCopyNodePolicy.class); + onCopyCompleteDelegate = policyComponent.registerClassPolicy(CopyServicePolicies.OnCopyCompletePolicy.class); + + // Register policy behaviours this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ContentModel.ASPECT_COPIEDFROM, + new JavaBehaviour(this, "getCallbackForCopiedFromAspect")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ContentModel.TYPE_FOLDER, + new JavaBehaviour(this, "getCallbackForFolderType")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_OWNABLE, - new JavaBehaviour(this, "onCopyOwnable")); + new JavaBehaviour(this, "getCallbackForOwnableAspect")); this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_AUTHOR, - new JavaBehaviour(this, "onCopyAuthor")); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), - ContentModel.ASPECT_COPIEDFROM, - new JavaBehaviour(this, "onCopyComplete")); - } - - /** - * @see org.alfresco.service.cmr.repository.CopyService#copy(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, boolean) - */ + new JavaBehaviour(this, "getCallbackForAuthorAspect")); + } + public NodeRef copy( NodeRef sourceNodeRef, - NodeRef destinationParentRef, - QName destinationAssocTypeQName, - QName destinationQName, + NodeRef targetParentRef, + QName assocTypeQName, + QName assocQName, boolean copyChildren) { - // Check that all the passed values are not null - ParameterCheck.mandatory("Source Node", sourceNodeRef); - ParameterCheck.mandatory("Destination Parent", destinationParentRef); - ParameterCheck.mandatory("Destination Association Name", destinationQName); + // Check that all the passed values are not null + ParameterCheck.mandatory("sourceNodeRef", sourceNodeRef); + ParameterCheck.mandatory("targetParentRef", targetParentRef); + ParameterCheck.mandatory("assocTypeQName", assocTypeQName); + ParameterCheck.mandatory("assocQName", assocQName); - if (sourceNodeRef.getStoreRef().equals(destinationParentRef.getStoreRef()) == false) + if (sourceNodeRef.getStoreRef().equals(targetParentRef.getStoreRef()) == false) { // TODO We need to create a new node in the other store with the same id as the source // Error - since at the moment we do not support cross store copying throw new UnsupportedOperationException("Copying nodes across stores is not currently supported."); } + + // Keep track of copied children + Map copiesByOriginals = new HashMap(17); + Set copies = new HashSet(17); - // Get the original parent reference - NodeRef sourceParentRef = nodeService.getPrimaryParent(sourceNodeRef).getParentRef(); - // Recursively copy node - Map copiedChildren = new HashMap(); - NodeRef copy = recursiveCopy( - sourceNodeRef, - sourceParentRef, - destinationParentRef, - destinationAssocTypeQName, - destinationQName, - copyChildren, - true, // top-level copy drops the name, if the parent is different - copiedChildren); + NodeRef copiedNodeRef = copyImpl( + sourceNodeRef, targetParentRef, + assocTypeQName, assocQName, + copyChildren, true, // Drop cm:name for top-level node + copiesByOriginals, copies); + // Check if the node was copied + if (copiedNodeRef == null) + { + CopyDetails copyDetails = getCopyDetails(sourceNodeRef, targetParentRef, null, assocTypeQName, assocQName); + // Denied! + throw new CopyServiceException( + "A bound policy denied copy: \n" + + " " + copyDetails); + } // Foreach of the newly created copies call the copy complete policy - for (Map.Entry entry : copiedChildren.entrySet()) - { - invokeCopyComplete(entry.getKey(), entry.getValue(), true, copiedChildren); - } + for (Map.Entry entry : copiesByOriginals.entrySet()) + { + NodeRef mappedSourceNodeRef = entry.getKey(); + NodeRef mappedTargetNodeRef = entry.getValue(); + invokeCopyComplete(mappedSourceNodeRef, mappedTargetNodeRef, true, copiesByOriginals); + } - return copy; + // Done + return copiedNodeRef; } - - /** - * @see org.alfresco.service.cmr.repository.CopyService#copyAndRename(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, boolean) - */ - public NodeRef copyAndRename(NodeRef sourceNodeRef, NodeRef destinationParent, QName destinationAssocTypeQName, QName destinationQName, boolean copyChildren) - { - // To fix ETWOONE-224 issue it is necessary to change a QName of the new node accordingly to its name. - NodeRef result = null; - String sourceName = (String)this.internalNodeService.getProperty(sourceNodeRef, ContentModel.PROP_NAME); - - // Find a non-duplicate name - String newName = sourceName; - while (this.internalNodeService.getChildByName(destinationParent, destinationAssocTypeQName, newName) != null) - { - newName = I18NUtil.getMessage(COPY_OF_LABEL, newName); - } - - if (destinationQName == null) - { - // Change a QName of the new node accordingly to its name - destinationQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(newName)); - } - - // Make a copy - result = copy(sourceNodeRef, destinationParent, destinationAssocTypeQName, destinationQName, copyChildren); - - // Set name property - this.internalNodeService.setProperty(result, ContentModel.PROP_NAME, newName); - - // Return new NodeRef - return result; - } - - /** - * Invokes the copy complete policy for the node reference provided - * - * @param sourceNodeRef the source node reference - * @param destinationNodeRef the destination node reference - * @param copiedNodeRefs the map of copied node references - */ - private void invokeCopyComplete( - NodeRef sourceNodeRef, - NodeRef destinationNodeRef, - boolean copyToNewNode, - Map copiedNodeRefs) - { - // Get the destination and target associations up-front for performance reasons - List destinationChildAssocs = this.nodeService.getChildAssocs(destinationNodeRef); - List destinationAssocs = this.nodeService.getTargetAssocs(destinationNodeRef, RegexQNamePattern.MATCH_ALL); - - QName sourceClassRef = this.nodeService.getType(sourceNodeRef); - invokeCopyComplete(sourceClassRef, sourceNodeRef, destinationNodeRef, destinationChildAssocs, destinationAssocs, copyToNewNode, copiedNodeRefs); - - // Get the source aspects - Set sourceAspects = this.nodeService.getAspects(sourceNodeRef); - for (QName sourceAspect : sourceAspects) - { - invokeCopyComplete(sourceAspect, sourceNodeRef, destinationNodeRef, destinationChildAssocs, destinationAssocs, copyToNewNode, copiedNodeRefs); - } - } - - /** - * - * @param typeQName - * @param sourceNodeRef - * @param destinationNodeRef - * @param copiedNodeRefs - */ - private void invokeCopyComplete( - QName typeQName, - NodeRef sourceNodeRef, - NodeRef destinationNodeRef, - List destinationChildAssocs, - List destinationAssocs, - boolean copyToNewNode, - Map copiedNodeRefs) - { - Collection policies = this.onCopyCompleteDelegate.getList(typeQName); - if (policies.isEmpty() == true) - { - defaultOnCopyComplete(typeQName, sourceNodeRef, destinationNodeRef, destinationChildAssocs, destinationAssocs, copiedNodeRefs); - } - else - { - for (CopyServicePolicies.OnCopyCompletePolicy policy : policies) - { - policy.onCopyComplete(typeQName, sourceNodeRef, destinationNodeRef, copyToNewNode, copiedNodeRefs); - } - } - } - - /** - * - * @param typeQName - * @param sourceNodeRef - * @param destinationNodeRef - * @param copiedNodeRefs - */ - private void defaultOnCopyComplete( - QName typeQName, - NodeRef sourceNodeRef, - NodeRef destinationNodeRef, - List destinationChildAssocs, - List destinationAssocs, - Map copiedNodeRefs) - { - ClassDefinition classDefinition = this.dictionaryService.getClass(typeQName); - if (classDefinition != null) - { - // Check the properties - Map propertyDefinitions = classDefinition.getProperties(); - for (Map.Entry entry : propertyDefinitions.entrySet()) - { - QName propertyTypeDefinition = entry.getValue().getDataType().getName(); - if (DataTypeDefinition.NODE_REF.equals(propertyTypeDefinition) == true || - DataTypeDefinition.ANY.equals(propertyTypeDefinition) == true) - { - // Re-set the node ref so that it is still relative (if appropriate) - Serializable value = this.nodeService.getProperty(destinationNodeRef, entry.getKey()); - if (value != null && value instanceof NodeRef) - { - NodeRef nodeRef = (NodeRef)value; - if (copiedNodeRefs.containsKey(nodeRef) == true) - { - NodeRef copiedNodeRef = copiedNodeRefs.get(nodeRef); - this.nodeService.setProperty(destinationNodeRef, entry.getKey(), copiedNodeRef); - } - } - } - } - - // Copy the associations (child and target) - Map assocDefs = classDefinition.getAssociations(); - - // TODO: Need way of getting child assocs of a given type - //List childAssocRefs = this.nodeService.getChildAssocs(destinationNodeRef); - for (ChildAssociationRef childAssocRef : destinationChildAssocs) - { - if (assocDefs.containsKey(childAssocRef.getTypeQName()) && - childAssocRef.isPrimary() == false && - copiedNodeRefs.containsKey(childAssocRef.getChildRef()) == true) - { - // Remove the assoc and re-point to the new node - this.nodeService.removeChild(destinationNodeRef, childAssocRef.getChildRef()); - this.nodeService.addChild( - destinationNodeRef, - copiedNodeRefs.get(childAssocRef.getChildRef()) , - childAssocRef.getTypeQName(), - childAssocRef.getQName()); - } - } - - // TODO: Need way of getting assocs of a given type - //List nodeAssocRefs = this.nodeService.getTargetAssocs(destinationNodeRef, RegexQNamePattern.MATCH_ALL); - for (AssociationRef nodeAssocRef : destinationAssocs) - { - if (assocDefs.containsKey(nodeAssocRef.getTypeQName()) && - copiedNodeRefs.containsKey(nodeAssocRef.getTargetRef()) == true) - { - // Remove the assoc and re-point to the new node - this.nodeService.removeAssociation( - destinationNodeRef, - nodeAssocRef.getTargetRef(), - nodeAssocRef.getTypeQName()); - this.nodeService.createAssociation( - destinationNodeRef, - copiedNodeRefs.get(nodeAssocRef.getTargetRef()), - nodeAssocRef.getTypeQName()); - } - } - } - - } - - /** - * Recursive copy algorithm - * - * @param dropName drop the name property when associations don't allow duplicately named children - */ - private NodeRef recursiveCopy( - NodeRef sourceNodeRef, - NodeRef sourceParentRef, - NodeRef destinationParentRef, - QName destinationAssocTypeQName, - QName destinationQName, - boolean copyChildren, - boolean dropName, - Map copiedChildren) - { - // Extract Type Definition - QName sourceTypeRef = this.nodeService.getType(sourceNodeRef); - TypeDefinition typeDef = dictionaryService.getType(sourceTypeRef); - if (typeDef == null) - { - throw new InvalidTypeException(sourceTypeRef); - } - - // Establish the scope of the copy - PolicyScope copyDetails = getCopyDetails(sourceNodeRef, destinationParentRef.getStoreRef(), true); - - // Create collection of properties for type and mandatory aspects - Map typeProps = copyDetails.getProperties(); - Map properties = new HashMap(); - if (typeProps != null) - { - properties.putAll(typeProps); - } - for (AspectDefinition aspectDef : typeDef.getDefaultAspects()) - { - Map aspectProps = copyDetails.getProperties(aspectDef.getName()); - if (aspectProps != null) - { - properties.putAll(aspectProps); - } - } - - // Drop the name property, if required. This prevents duplicate names and leaves it up to the client - // to assign a new name. - AssociationDefinition assocDef = dictionaryService.getAssociation(destinationAssocTypeQName); - if (!assocDef.isChild()) - { - throw new AlfrescoRuntimeException("Association is not a child association: " + destinationAssocTypeQName); - } - else - { - ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; - if (dropName && !childAssocDef.getDuplicateChildNamesAllowed()) - { - // duplicate children are not allowed. - properties.remove(ContentModel.PROP_NAME); - } - } - - // Create the new node - ChildAssociationRef destinationChildAssocRef = this.nodeService.createNode( - destinationParentRef, - destinationAssocTypeQName, - destinationQName, - sourceTypeRef, - properties); - NodeRef destinationNodeRef = destinationChildAssocRef.getChildRef(); - copiedChildren.put(sourceNodeRef, destinationNodeRef); - - // Prevent any rules being fired on the new destination node - this.ruleService.disableRules(destinationNodeRef); - try - { - // Apply the copy aspect to the new node - Map copyProperties = new HashMap(); - copyProperties.put(ContentModel.PROP_COPY_REFERENCE, sourceNodeRef); - this.nodeService.addAspect(destinationNodeRef, ContentModel.ASPECT_COPIEDFROM, copyProperties); - - // Copy the aspects - copyAspects(destinationNodeRef, copyDetails); - - // Copy the associations - copyAssociations(destinationNodeRef, copyDetails, copyChildren, copiedChildren); - - // Copy permissions - copyPermissions(sourceNodeRef, destinationNodeRef); - } - finally - { - this.ruleService.enableRules(destinationNodeRef); - } - - return destinationNodeRef; - } - - /** - * Copies the permissions of the source node reference onto the destination node reference - * - * @param sourceNodeRef the source node reference - * @param destinationNodeRef the destination node reference - */ - private void copyPermissions(NodeRef sourceNodeRef, NodeRef destinationNodeRef) - { - if(this.permissionService.hasPermission(sourceNodeRef, PermissionService.READ_PERMISSIONS) == AccessStatus.ALLOWED) - { - // Get the permission details of the source node reference - Set permissions = this.permissionService.getAllSetPermissions(sourceNodeRef); - boolean includeInherited = this.permissionService.getInheritParentPermissions(sourceNodeRef); - - AccessStatus writePermission = permissionService.hasPermission(destinationNodeRef, PermissionService.CHANGE_PERMISSIONS); - if (writePermission.equals(AccessStatus.ALLOWED) || this.authenticationService.isCurrentUserTheSystemUser() ) - { - // Set the permission values on the destination node - for (AccessPermission permission : permissions) - { - if(permission.isSetDirectly()) - { - this.permissionService.setPermission( - destinationNodeRef, - permission.getAuthority(), - permission.getPermission(), - permission.getAccessStatus().equals(AccessStatus.ALLOWED)); - } - } - this.permissionService.setInheritParentPermissions(destinationNodeRef, includeInherited); - } - } - } - - /** - * Gets the copy details. This calls the appropriate policies that have been registered - * against the node and aspect types in order to pick-up any type specific copy behaviour. - *

- * If no policies for a type are registered then the default copy takes place which will - * copy all properties and associations in the ususal manner. - * - * @param sourceNodeRef the source node reference - * @return the copy details - */ - private PolicyScope getCopyDetails(NodeRef sourceNodeRef, StoreRef destinationStoreRef, boolean copyToNewNode) - { - QName sourceClassRef = this.nodeService.getType(sourceNodeRef); - PolicyScope copyDetails = new PolicyScope(sourceClassRef); - - // Invoke the onCopy behaviour - invokeOnCopy(sourceClassRef, sourceNodeRef, destinationStoreRef, copyToNewNode, copyDetails); - - // TODO What do we do aboout props and assocs that are on the node node but not part of the type definition? - - // Get the source aspects - Set sourceAspects = this.nodeService.getAspects(sourceNodeRef); - for (QName sourceAspect : sourceAspects) - { - // Invoke the onCopy behaviour - invokeOnCopy(sourceAspect, sourceNodeRef, destinationStoreRef, copyToNewNode, copyDetails); - } - - return copyDetails; - } - - /** - * Invoke the correct onCopy behaviour - * - * @param sourceClassRef source class reference - * @param sourceNodeRef source node reference - * @param copyDetails the copy details - */ - private void invokeOnCopy( - QName sourceClassRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) - { - Collection policies = this.onCopyNodeDelegate.getList(sourceClassRef); - if (policies.isEmpty() == true) - { - Map sourceNodeProperties = this.nodeService.getProperties(sourceNodeRef); - defaultOnCopy(sourceClassRef, sourceNodeRef, sourceNodeProperties, copyDetails); - } - else - { - for (CopyServicePolicies.OnCopyNodePolicy policy : policies) - { - policy.onCopyNode(sourceClassRef, sourceNodeRef, destinationStoreRef, copyToNewNode, copyDetails); - } - } - } - - /** - * Default implementation of on copy, used when there is no policy specified for a class. - * - * @param classRef the class reference of the node being copied - * @param sourceNodeRef the source node reference - * @param copyDetails details of the state being copied - */ - private void defaultOnCopy( - QName classRef, - NodeRef sourceNodeRef, - Map sourceNodeProperties, - PolicyScope copyDetails) - { - ClassDefinition classDefinition = this.dictionaryService.getClass(classRef); - if (classDefinition != null) - { - if (classDefinition.isAspect() == true) - { - // make sure any aspects without any properties or associations are copied - copyDetails.addAspect(classRef); - } - - // Copy the properties - Map propertyDefinitions = classDefinition.getProperties(); - for (QName propertyName : propertyDefinitions.keySet()) - { - Serializable propValue = sourceNodeProperties.get(propertyName); - if (propValue != null) - { - copyDetails.addProperty(classDefinition.getName(), propertyName, propValue); - } - } - - // Copy the associations (child and target) - Map assocDefs = classDefinition.getAssociations(); - - // TODO: Need way of getting child assocs of a given type - if (classDefinition.isContainer()) - { - List childAssocRefs = this.nodeService.getChildAssocs(sourceNodeRef); - for (ChildAssociationRef childAssocRef : childAssocRefs) - { - if (assocDefs.containsKey(childAssocRef.getTypeQName())) - { - copyDetails.addChildAssociation(classDefinition.getName(), childAssocRef); - } - } - } - - // TODO: Need way of getting assocs of a given type - List nodeAssocRefs = this.nodeService.getTargetAssocs(sourceNodeRef, RegexQNamePattern.MATCH_ALL); - for (AssociationRef nodeAssocRef : nodeAssocRefs) - { - if (assocDefs.containsKey(nodeAssocRef.getTypeQName())) - { - copyDetails.addAssociation(classDefinition.getName(), nodeAssocRef); - } - } - } - } - /** - * Copies the properties for the node type onto the destination node. - * - * @param destinationNodeRef the destintaion node reference - * @param copyDetails the copy details - */ - private void copyProperties(NodeRef destinationNodeRef, PolicyScope copyDetails) - { - Map props = copyDetails.getProperties(); - if (props != null) - { - Map copyProps = new HashMap(props); - for (QName propName : props.keySet()) - { - Serializable value = props.get(propName); - if (value == null) - { - copyProps.remove(propName); - } - } - this.nodeService.addProperties(destinationNodeRef, copyProps); - } - } - - /** - * Applies the aspects (thus copying the associated properties) onto the destination node - * - * @param destinationNodeRef the destination node reference - * @param copyDetails the copy details - */ - private void copyAspects(NodeRef destinationNodeRef, PolicyScope copyDetails) - { - Set apects = copyDetails.getAspects(); - for (QName aspect : apects) - { - if (this.nodeService.hasAspect(destinationNodeRef, aspect) == false) - { - // Add the aspect to the node - this.nodeService.addAspect( - destinationNodeRef, - aspect, - copyDetails.getProperties(aspect)); - } - else - { - // Set each property on the destination node since the aspect has already been applied - Map aspectProps = copyDetails.getProperties(aspect); - if (aspectProps != null) - { - for (Map.Entry entry : aspectProps.entrySet()) - { - this.nodeService.setProperty(destinationNodeRef, entry.getKey(), entry.getValue()); - } - } - } - } - } - - /** - * Copies the associations (child and target) for the node type and aspects onto the - * destination node. - *

- * If copyChildren is true then all child nodes of primary child associations are copied - * before they are associatied with the destination node. - * - * @param destinationNodeRef the destination node reference - * @param copyDetails the copy details - * @param copyChildren indicates whether the primary children are copied or not - * @param copiedChildren set of children already copied - */ - private void copyAssociations( - NodeRef destinationNodeRef, - PolicyScope copyDetails, - boolean copyChildren, - Map copiedChildren) - { - QName classRef = this.nodeService.getType(destinationNodeRef); - copyChildAssociations(classRef, destinationNodeRef, copyDetails, copyChildren, copiedChildren); - copyTargetAssociations(classRef, destinationNodeRef, copyDetails); - - Set apects = copyDetails.getAspects(); - for (QName aspect : apects) - { - if (this.nodeService.hasAspect(destinationNodeRef, aspect) == false) - { - // Error since the aspect has not been added to the destination node (should never happen) - if (logger.isWarnEnabled() == true) - { - logger.warn("WARNING: the aspect " + aspect.toString() + " could not be found on node " + destinationNodeRef.toString() + "during copy. It has been reapplied."); - } - - // For some reason the aspect has not been added, so re-add it - this.nodeService.addAspect( - destinationNodeRef, - aspect, - copyDetails.getProperties(aspect)); - } - - copyChildAssociations(aspect, destinationNodeRef, copyDetails, copyChildren, copiedChildren); - copyTargetAssociations(aspect, destinationNodeRef, copyDetails); - } - } - - /** - * Copies the target associations onto the destination node reference. - * - * @param classRef the class reference - * @param destinationNodeRef the destination node reference - * @param copyDetails the copy details - */ - private void copyTargetAssociations(QName classRef, NodeRef destinationNodeRef, PolicyScope copyDetails) - { - List nodeAssocRefs = copyDetails.getAssociations(classRef); - if (nodeAssocRefs != null) - { - for (AssociationRef assocRef : nodeAssocRefs) - { - NodeRef targetRef = assocRef.getTargetRef(); - - boolean exists = false; - for (AssociationRef assocRef2 : this.nodeService.getTargetAssocs(destinationNodeRef, assocRef.getTypeQName())) - { - if (targetRef.equals(assocRef2.getTargetRef()) == true) - { - exists = true; - break; - } - } - - if (exists == false) - { - // Add the association - this.nodeService.createAssociation(destinationNodeRef, targetRef, assocRef.getTypeQName()); - } - } - } - } - - /** - * Copies the child associations onto the destiantion node reference. - *

- * If copyChildren is true then the nodes at the end of a primary assoc will be copied before they - * are associated. - * - * @param classRef the class reference - * @param destinationNodeRef the destination node reference - * @param copyDetails the copy details - * @param copyChildren indicates whether to copy the primary children - */ - private void copyChildAssociations( - QName classRef, - NodeRef destinationNodeRef, - PolicyScope copyDetails, - boolean copyChildren, - Map copiedChildren) - { - List childAssocs = copyDetails.getChildAssociations(classRef); - if (childAssocs != null) - { - for (ChildAssociationRef childAssoc : childAssocs) - { - if (copyChildren == true) - { - if (childAssoc.isPrimary() == true) - { - // Do not recurse further, if we've already copied this node - if (copiedChildren.containsKey(childAssoc.getChildRef()) == false && - copiedChildren.containsValue(childAssoc.getChildRef()) == false) - { - // Copy the child - recursiveCopy( - childAssoc.getChildRef(), - childAssoc.getParentRef(), - destinationNodeRef, - childAssoc.getTypeQName(), - childAssoc.getQName(), - copyChildren, - false, // the target and source parents can't be the same - copiedChildren); - } - } - else - { - // Add the child - NodeRef childRef = childAssoc.getChildRef(); - this.nodeService.addChild(destinationNodeRef, childRef, childAssoc.getTypeQName(), childAssoc.getQName()); - } - } - else - { - NodeRef childRef = childAssoc.getChildRef(); - QName childType = this.nodeService.getType(childRef); - - // TODO will need to remove this reference to the configurations association - if (this.dictionaryService.isSubClass(childType, ApplicationModel.TYPE_CONFIGURATIONS) == true || - copyDetails.isChildAssociationRefAlwaysTraversed(classRef, childAssoc) == true) - { - if (copiedChildren.containsKey(childRef) == false) - { - // Always recursivly copy configuration folders - recursiveCopy( - childRef, - childAssoc.getParentRef(), - destinationNodeRef, - childAssoc.getTypeQName(), - childAssoc.getQName(), - true, - false, // the target and source parents can't be the same - copiedChildren); - } - } - else - { - // Add the child (will not be primary reguardless of its origional state) - this.nodeService.addChild(destinationNodeRef, childRef, childAssoc.getTypeQName(), childAssoc.getQName()); - } - } - } - } - } - - /** - * Defer to the standard implementation with copyChildren set to false + public NodeRef copyAndRename( + NodeRef sourceNodeRef, + NodeRef destinationParent, + QName assocTypeQName, + QName assocQName, + boolean copyChildren) + { + // To fix ETWOONE-224 issue it is necessary to change a QName of the new node accordingly to its name. + NodeRef result = null; + String sourceName = (String)this.internalNodeService.getProperty(sourceNodeRef, ContentModel.PROP_NAME); + + // Find a non-duplicate name + String newName = sourceName; + while (this.internalNodeService.getChildByName(destinationParent, assocTypeQName, newName) != null) + { + newName = I18NUtil.getMessage(COPY_OF_LABEL, newName); + } + + if (assocQName == null) + { + // Change a QName of the new node accordingly to its name + assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(newName)); + } + + // Make a copy + result = copy(sourceNodeRef, destinationParent, assocTypeQName, assocQName, copyChildren); + + // Set name property + this.internalNodeService.setProperty(result, ContentModel.PROP_NAME, newName); + + // Return new NodeRef + return result; + } + + /** + * {@inheritDoc} + * + * Defer to the standard implementation with copyChildren set to false */ public NodeRef copy( NodeRef sourceNodeRef, - NodeRef destinationParent, - QName destinationAssocTypeQName, - QName destinationQName) + NodeRef targetParentNodeRef, + QName assocTypeQName, + QName assocQName) { return copy( - sourceNodeRef, - destinationParent, - destinationAssocTypeQName, - destinationQName, - false); + sourceNodeRef, + targetParentNodeRef, + assocTypeQName, + assocQName, + false); } - public void copy( - NodeRef sourceNodeRef, - NodeRef destinationNodeRef) + public void copy(NodeRef sourceNodeRef, NodeRef targetNodeRef) { - // Check that the source and destination node are the same type - if (this.nodeService.getType(sourceNodeRef).equals(this.nodeService.getType(destinationNodeRef)) == false) - { - // Error - can not copy objects that are of different types - throw new CopyServiceException("The source and destination node must be the same type."); - } - - // Get the copy details - PolicyScope copyDetails = getCopyDetails(sourceNodeRef, destinationNodeRef.getStoreRef(), false); - - // Remove the name property from the policy scope to prevent duplicate name exceptions - copyDetails.removeProperty(ContentModel.PROP_NAME); + QName sourceNodeTypeQName = nodeService.getType(sourceNodeRef); + QName targetNodeTypeQName = nodeService.getType(targetNodeRef); + // Check that the source and destination node are the same type + if (!sourceNodeTypeQName.equals(targetNodeTypeQName)) + { + // Error - can not copy objects that are of different types + throw new CopyServiceException("The source and destination node must be the same type."); + } - // Copy over the top of the destination node - copyProperties(destinationNodeRef, copyDetails); - copyAspects(destinationNodeRef, copyDetails); - copyAssociations(destinationNodeRef, copyDetails, false, new HashMap()); + Map sourceNodeProperties = nodeService.getProperties(sourceNodeRef); + + // Get the destinations node's details + ChildAssociationRef destinationPrimaryAssocRef = nodeService.getPrimaryParent(targetNodeRef); + NodeRef destinationParentNodeRef = destinationPrimaryAssocRef.getParentRef(); + QName assocTypeQName = destinationPrimaryAssocRef.getTypeQName(); + QName assocQName = destinationPrimaryAssocRef.getQName(); + + // Get the copy details + CopyDetails copyDetails = getCopyDetails( + sourceNodeRef, destinationParentNodeRef, targetNodeRef, + assocTypeQName, assocQName); + + // Get callbacks + Map callbacks = getCallbacks(copyDetails); + + // Remove the name property from the source properties to avoid having it copied + sourceNodeProperties.remove(ContentModel.PROP_NAME); + + // Copy + copyProperties(copyDetails, targetNodeRef, sourceNodeTypeQName, callbacks); + copyAspects(copyDetails, targetNodeRef, Collections.emptySet(), callbacks); + copyResidualProperties(copyDetails, targetNodeRef); // invoke the copy complete policy - Map copiedNodes = new HashMap(1); - copiedNodes.put(sourceNodeRef, destinationNodeRef); - invokeCopyComplete(sourceNodeRef, destinationNodeRef, false, copiedNodes); + Map copiedNodeRefs = new HashMap(1); + copiedNodeRefs.put(sourceNodeRef, targetNodeRef); + invokeCopyComplete(sourceNodeRef, targetNodeRef, false, copiedNodeRefs); } - - /** - * OnCopy behaviour registered for the copy aspect. - *

- * Doing nothing in this behaviour ensures that the copy aspect found on the source node does not get - * copied onto the destination node. - * - * @param sourceClassRef the source class reference - * @param sourceNodeRef the source node reference - * @param copyDetails the copy details - */ - public void copyAspectOnCopy( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) - { - // Do nothing. This will ensure that copy aspect on the source node does not get copied onto - // the destination node. - } - - public void onCopyOwnable( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) - { - // Do nothing since the ownable aspect should not be copied - } - - public void onCopyAuthor( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) - { - // Do nothing since the author aspect should not be copied - } - - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef destinationRef, - boolean copyToNew, - Map copyMap) - { - // Do nothing since we do not want the copy from aspect to be relative to the copied nodes - } public List getCopies(NodeRef nodeRef) { @@ -1034,4 +394,810 @@ public class CopyServiceImpl implements CopyService return copies; } + + /** + * @return Returns null if the node was denied a copy + */ + private NodeRef copyImpl( + NodeRef sourceNodeRef, + NodeRef targetParentRef, + QName assocTypeQName, + QName assocQName, + boolean copyChildren, + boolean dropName, + Map copiesByOriginals, + Set copies) + { + // Build the top-level node's copy details + CopyDetails copyDetails = getCopyDetails(sourceNodeRef, targetParentRef, null, assocTypeQName, assocQName); + + // Get the callbacks that will determine the copy behaviour + Map callbacks = getCallbacks(copyDetails); + + // Check that the primary (type) callback allows copy + QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName(); + CopyBehaviourCallback callback = callbacks.get(sourceNodeTypeQName); + if (callback == null) + { + throw new IllegalStateException("Source node type has no callback: " + sourceNodeTypeQName); + } + if (!callback.getMustCopy(sourceNodeTypeQName, copyDetails)) + { + // Denied! + return null; + } + + // Recursive copies cannot have name conflicts, therefore copy names verbatim. + NodeRef copiedNodeRef = recursiveCopy( + copyDetails, copyChildren, dropName, copiesByOriginals, copies, callbacks); + + return copiedNodeRef; + } + + /** + * Recursive copy algorithm + * + * @param dropName drop the name property when associations don't allow duplicately named children + */ + private NodeRef recursiveCopy( + CopyDetails copyDetails, + boolean copyChildren, + boolean dropName, + Map copiesByOriginal, + Set copies, + Map callbacks) + { + NodeRef sourceNodeRef = copyDetails.getSourceNodeRef(); + Set sourceNodeAspectQNames = copyDetails.getSourceNodeAspectQNames(); + NodeRef targetParentNodeRef = copyDetails.getTargetParentNodeRef(); + QName assocTypeQName = copyDetails.getAssocTypeQName(); + QName assocQName = copyDetails.getAssocQName(); + + // Avoid duplicate and cyclic copies + if (copies.contains(sourceNodeRef)) + { + throw new IllegalStateException( + "Nested copy prevention has failed: \n" + + " " + copyDetails + "\n" + + " Copies by original: " + copiesByOriginal); + } + else if (copiesByOriginal.containsKey(sourceNodeRef)) + { + throw new IllegalStateException( + "Multiple child assocs between same two nodes detected: \n" + + " " + copyDetails + "\n" + + " Copies by original: " + copiesByOriginal); + } + + // Extract Type Definition + QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName(); + + // Does this node get copied at all? + // The source node's type-bound behaviour has an effective veto. + CopyBehaviourCallback sourceTypeBehaviour = callbacks.get(sourceNodeTypeQName); + if (sourceTypeBehaviour == null) + { + throw new IllegalStateException("Source node type has no callback: " + sourceNodeTypeQName); + } + if (!sourceTypeBehaviour.getMustCopy(sourceNodeTypeQName, copyDetails)) + { + // Nothing to do + return null; + } + + // Get the type properties to copy + Map targetNodeProperties = buildCopyProperties( + copyDetails, + Collections.singleton(sourceNodeTypeQName), + callbacks); + + // Some aspects are going to be applied automatically. For efficiency, the initial node properties + // for these aspects should be provided. + Set defaultAspectQNames = getDefaultAspects(sourceNodeTypeQName); + Map defaultAspectsProperties = buildCopyProperties( + copyDetails, + defaultAspectQNames, + callbacks); + targetNodeProperties.putAll(defaultAspectsProperties); + + // Drop the name property, if required. This prevents duplicate names and leaves it up to the client + // to assign a new name. + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (!assocDef.isChild()) + { + throw new AlfrescoRuntimeException("Association is not a child association: " + assocTypeQName); + } + else + { + ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; + if (dropName && !childAssocDef.getDuplicateChildNamesAllowed()) + { + // duplicate children are not allowed. + targetNodeProperties.remove(ContentModel.PROP_NAME); + } + } + + // Lastly, make sure the the Node UUID is set correctly; after all, the contract + // of the CopyDetails says that the targetNodeRef was already determined. + String targetNodeUuid = copyDetails.getTargetNodeRef().getId(); + targetNodeProperties.put(ContentModel.PROP_NODE_UUID, targetNodeUuid); + + // The initial node copy is good to go + ChildAssociationRef targetChildAssocRef = this.nodeService.createNode( + targetParentNodeRef, + assocTypeQName, + assocQName, + sourceNodeTypeQName, + targetNodeProperties); + NodeRef targetNodeRef = targetChildAssocRef.getChildRef(); + // Save the mapping for later + copiesByOriginal.put(sourceNodeRef, targetNodeRef); + copies.add(targetNodeRef); + + // Work out which aspects still need copying. The source aspects less the default aspects + // will give this set. + Set remainingAspectQNames = new HashSet(sourceNodeAspectQNames); + remainingAspectQNames.removeAll(defaultAspectQNames); + + // Prevent any rules being fired on the new destination node + this.ruleService.disableRules(targetNodeRef); + try + { + // Apply the remaining aspects and properties + for (QName remainingAspectQName : remainingAspectQNames) + { + copyProperties(copyDetails, targetNodeRef, remainingAspectQName, callbacks); + } + + // Copy residual properties + copyResidualProperties(copyDetails, targetNodeRef); + + // Apply the copy aspect to the new node + Map copyProperties = new HashMap(); + copyProperties.put(ContentModel.PROP_COPY_REFERENCE, sourceNodeRef); + internalNodeService.addAspect(targetNodeRef, ContentModel.ASPECT_COPIEDFROM, copyProperties); + + // Copy permissions + copyPermissions(sourceNodeRef, targetNodeRef); + + // We present the recursion option regardless of what the client chooses + copyChildren( + copyDetails, + targetNodeRef, + true, // We know that the node has been created + copyChildren, + copiesByOriginal, + copies, + callbacks); + } + finally + { + this.ruleService.enableRules(targetNodeRef); + } + + return targetNodeRef; + } + + private Set getDefaultAspects(QName sourceNodeTypeQName) + { + TypeDefinition sourceNodeTypeDef = dictionaryService.getType(sourceNodeTypeQName); + if (sourceNodeTypeDef == null) + { + return Collections.emptySet(); + } + Set defaultAspectQNames = new HashSet(7); + for (AspectDefinition aspectDef : sourceNodeTypeDef.getDefaultAspects()) + { + defaultAspectQNames.add(aspectDef.getName()); + } + // Done + return defaultAspectQNames; + } + + /** + * Constructs the properties to copy that apply to the type and default aspects + */ + private Map buildCopyProperties( + CopyDetails copyDetails, + Set classQNames, + Map callbacks) + { + Map sourceNodeProperties = copyDetails.getSourceNodeProperties(); + Map copyProperties = new HashMap(sourceNodeProperties.size(), 1.0F); + Map scratchProperties = new HashMap(11); + // Each defined callback gets a chance to say which properties get copied + // Only model-defined properties are considered + for (QName classQName : classQNames) + { + CopyBehaviourCallback callback = callbacks.get(classQName); + if (callback == null) + { + throw new IllegalStateException("Source node class has no callback: " + classQName); + } + // Ignore if not present or if not scheduled for a copy + if (!callback.getMustCopy(classQName, copyDetails)) + { + continue; + } + // Get the dictionary definition + ClassDefinition classDef = dictionaryService.getClass(classQName); + if (classDef == null) + { + continue; + } + // Get the defined properties + Map propertyDefs = classDef.getProperties(); + // Extract these from the source nodes properties and store in a safe (modifiable) map + scratchProperties.clear(); + for (QName propertyQName : propertyDefs.keySet()) + { + Serializable value = sourceNodeProperties.get(propertyQName); + if (value == null) + { + continue; + } + scratchProperties.put(propertyQName, value); + } + // What does the behaviour do with properties? + scratchProperties = callback.getCopyProperties(classQName, copyDetails, scratchProperties); + // Add to the final properties + copyProperties.putAll(scratchProperties); + } + // Done + return copyProperties; + } + + /** + * Invokes the copy complete policy for the node reference provided + * + * @param sourceNodeRef the source node reference + * @param targetNodeRef the destination node reference + * @param copiedNodeRefs the map of copied node references + */ + private void invokeCopyComplete( + NodeRef sourceNodeRef, + NodeRef targetNodeRef, + boolean copyToNewNode, + Map copiedNodeRefs) + { + QName sourceClassRef = internalNodeService.getType(sourceNodeRef); + invokeCopyComplete(sourceClassRef, sourceNodeRef, targetNodeRef, copyToNewNode, copiedNodeRefs); + + // Get the source aspects + Set sourceAspects = this.nodeService.getAspects(sourceNodeRef); + for (QName sourceAspect : sourceAspects) + { + invokeCopyComplete(sourceAspect, sourceNodeRef, targetNodeRef, copyToNewNode, copiedNodeRefs); + } + } + + private void invokeCopyComplete( + QName typeQName, + NodeRef sourceNodeRef, + NodeRef targetNodeRef, + boolean copyToNewNode, + Map copiedNodeRefs) + { + Collection policies = onCopyCompleteDelegate.getList(typeQName); + for (CopyServicePolicies.OnCopyCompletePolicy policy : policies) + { + policy.onCopyComplete(typeQName, sourceNodeRef, targetNodeRef, copyToNewNode, copiedNodeRefs); + } + } + + /** + * Copies the permissions of the source node reference onto the destination node reference + * + * @param sourceNodeRef the source node reference + * @param destinationNodeRef the destination node reference + */ + private void copyPermissions(NodeRef sourceNodeRef, NodeRef destinationNodeRef) + { + if(this.permissionService.hasPermission(sourceNodeRef, PermissionService.READ_PERMISSIONS) == AccessStatus.ALLOWED) + { + // Get the permission details of the source node reference + Set permissions = this.permissionService.getAllSetPermissions(sourceNodeRef); + boolean includeInherited = this.permissionService.getInheritParentPermissions(sourceNodeRef); + + AccessStatus writePermission = permissionService.hasPermission(destinationNodeRef, PermissionService.CHANGE_PERMISSIONS); + if (writePermission.equals(AccessStatus.ALLOWED) || this.authenticationService.isCurrentUserTheSystemUser() ) + { + // Set the permission values on the destination node + for (AccessPermission permission : permissions) + { + if(permission.isSetDirectly()) + { + this.permissionService.setPermission( + destinationNodeRef, + permission.getAuthority(), + permission.getPermission(), + permission.getAccessStatus().equals(AccessStatus.ALLOWED)); + } + } + this.permissionService.setInheritParentPermissions(destinationNodeRef, includeInherited); + } + } + } + + /** + * Gets the copy details. This calls the appropriate policies that have been registered + * against the node and aspect types in order to pick-up any type specific copy behaviour. + *

+ * The full {@link NodeService} is used for property retrieval. After this, read permission + * can be assumed to have passed on the source node - at least w.r.t. properties and aspects. + *

+ * NOTE: If a target node is not supplied, then one is created in the same store as the + * target parent node. This allows behavioural code always know which node will + * be copied to, even if the node does not exist. + */ + private CopyDetails getCopyDetails( + NodeRef sourceNodeRef, + NodeRef targetParentNodeRef, + NodeRef targetNodeRef, + QName assocTypeQName, + QName assocQName) + { + // The first call will fail permissions, so there is no point doing permission checks with + // the other calls + Map sourceNodeProperties = nodeService.getProperties(sourceNodeRef); + QName sourceNodeTypeQName = internalNodeService.getType(sourceNodeRef); + Set sourceNodeAspectQNames = internalNodeService.getAspects(sourceNodeRef); + + // Create a target node, if necessary + boolean targetNodeIsNew = false; + if (targetNodeRef == null) + { + targetNodeRef = new NodeRef(targetParentNodeRef.getStoreRef(), GUID.generate()); + targetNodeIsNew = true; + } + + CopyDetails copyDetails = new CopyDetails( + sourceNodeRef, + sourceNodeTypeQName, + sourceNodeAspectQNames, + sourceNodeProperties, + targetParentNodeRef, + targetNodeRef, + targetNodeIsNew, + assocTypeQName, + assocQName); + + // Done + return copyDetails; + } + + /** + * @return Returns a map of all the copy behaviours keyed by type and aspect qualified names + */ + private Map getCallbacks(CopyDetails copyDetails) + { + QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName(); + + Map callbacks = new HashMap(11); + // Get the type-specific behaviour + CopyBehaviourCallback callback = getCallback(sourceNodeTypeQName, copyDetails); + callbacks.put(sourceNodeTypeQName, callback); + + // Get the source aspects + for (QName sourceNodeAspectQName : copyDetails.getSourceNodeAspectQNames()) + { + callback = getCallback(sourceNodeAspectQName, copyDetails); + callbacks.put(sourceNodeAspectQName, callback); + } + + return callbacks; + } + + /** + * @return Returns the copy callback for the given criteria + */ + private CopyBehaviourCallback getCallback(QName sourceClassQName, CopyDetails copyDetails) + { + Collection policies = this.onCopyNodeDelegate.getList(sourceClassQName); + ClassDefinition sourceClassDef = dictionaryService.getClass(sourceClassQName); + CopyBehaviourCallback callback = null; + if (sourceClassDef == null) + { + // Do nothing as the type is not in the dictionary + callback = DoNothingCopyBehaviourCallback.getInstance(); + } + if (policies.isEmpty()) + { + // Default behaviour + callback = DefaultCopyBehaviourCallback.getInstance(); + } + else if (policies.size() == 1) + { + callback = policies.iterator().next().getCopyCallback(sourceClassQName, copyDetails); + } + else + { + // There are multiple + CompoundCopyBehaviourCallback compoundCallback = new CompoundCopyBehaviourCallback(sourceClassQName); + for (CopyServicePolicies.OnCopyNodePolicy policy : policies) + { + CopyBehaviourCallback nestedCallback = policy.getCopyCallback(sourceClassQName, copyDetails); + compoundCallback.addBehaviour(nestedCallback); + } + callback = compoundCallback; + } + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Fetched copy callback: \n" + + " Class: " + sourceClassQName + "\n" + + " Details: " + copyDetails + "\n" + + " Callback: " + callback); + } + return callback; + } + + /** + * Copies the properties for the node type or aspect onto the destination node. + */ + private void copyProperties( + CopyDetails copyDetails, + NodeRef targetNodeRef, + QName classQName, + Map callbacks) + { + ClassDefinition targetClassDef = dictionaryService.getClass(classQName); + if (targetClassDef == null) + { + return; // Ignore unknown types + } + // First check if the aspect must be copied at all + CopyBehaviourCallback callback = callbacks.get(classQName); + if (callback == null) + { + throw new IllegalStateException("Source node class has no callback: " + classQName); + } + // Ignore if not present or if not scheduled for a copy + if (!callback.getMustCopy(classQName, copyDetails)) + { + // Do nothing with this + return; + } + // Compile the properties to copy, even if they are empty + Map classProperties = buildCopyProperties( + copyDetails, + Collections.singleton(classQName), + callbacks); + // We don't need permissions as we've just created the node + if (targetClassDef.isAspect()) + { + internalNodeService.addAspect(targetNodeRef, classQName, classProperties); + } + else + { + internalNodeService.addProperties(targetNodeRef, classProperties); + } + } + + /** + * Copy properties that do not belong to the source node's type or any of the aspects. + */ + private void copyResidualProperties( + CopyDetails copyDetails, + NodeRef targetNodeRef) + { + Map residualProperties = new HashMap(); + // Start with the full set + residualProperties.putAll(copyDetails.getSourceNodeProperties()); + + QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName(); + Set knownClassQNames = new HashSet(13); + // We add the default aspects, source-applied aspects and the source node type + knownClassQNames.addAll(getDefaultAspects(sourceNodeTypeQName)); + knownClassQNames.addAll(copyDetails.getSourceNodeAspectQNames()); + knownClassQNames.add(sourceNodeTypeQName); + + for (QName knownClassQName : knownClassQNames) + { + ClassDefinition classDef = dictionaryService.getClass(knownClassQName); + if (classDef == null) + { + continue; + } + // Remove defined properties form the residual list + for (QName definedPropQName : classDef.getProperties().keySet()) + { + residualProperties.remove(definedPropQName); + // We've removed them all, so shortcut out + if (residualProperties.size() == 0) + { + break; + } + } + } + // Add the residual properties to the node + if (residualProperties.size() > 0) + { + internalNodeService.addProperties(targetNodeRef, residualProperties); + } + } + + /** + * Copies aspects from the source to the target node. + */ + private void copyAspects( + CopyDetails copyDetails, + NodeRef targetNodeRef, + Set aspectsToIgnore, + Map callbacks) + { + Set sourceAspectQNames = copyDetails.getSourceNodeAspectQNames(); + for (QName aspectQName : sourceAspectQNames) + { + if (aspectsToIgnore.contains(aspectQName)) + { + continue; + } + + // Double check that the aspect must be copied at all + CopyBehaviourCallback callback = callbacks.get(aspectQName); + if (callback == null) + { + throw new IllegalStateException("Source aspect class has no callback: " + aspectQName); + } + if (!callback.getMustCopy(aspectQName, copyDetails)) + { + continue; + } + copyProperties(copyDetails, targetNodeRef, aspectQName, callbacks); + } + } + + /** + * @param copyChildren false if the client selected not to recurse + */ + private void copyChildren( + CopyDetails copyDetails, + NodeRef targetNodeRef, + boolean targetNodeIsNew, + boolean copyChildren, + Map copiesByOriginals, + Set copies, + Map callbacks) + { + QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName(); + Set sourceNodeAspectQNames = copyDetails.getSourceNodeAspectQNames(); + // First check associations on the type + copyChildren( + copyDetails, + sourceNodeTypeQName, + targetNodeRef, + targetNodeIsNew, + copyChildren, + copiesByOriginals, + copies, + callbacks); + // Check associations for the aspects + for (QName aspectQName : sourceNodeAspectQNames) + { + AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName); + if (aspectDef == null) + { + continue; + } + copyChildren( + copyDetails, + aspectQName, + targetNodeRef, + targetNodeIsNew, + copyChildren, + copiesByOriginals, + copies, + callbacks); + } + } + + /** + * @param copyChildren false if the client selected not to recurse + */ + private void copyChildren( + CopyDetails copyDetails, + QName classQName, + NodeRef targetNodeRef, + boolean targetNodeIsNew, + boolean copyChildren, + Map copiesByOriginals, + Set copies, + Map callbacks) + { + NodeRef sourceNodeRef = copyDetails.getSourceNodeRef(); + + ClassDefinition classDef = dictionaryService.getClass(classQName); + if (classDef == null) + { + // Ignore missing types + return; + } + // Check the behaviour + CopyBehaviourCallback callback = callbacks.get(classQName); + if (callback == null) + { + throw new IllegalStateException("Source node class has no callback: " + classQName); + } + for (Map.Entry entry : classDef.getChildAssociations().entrySet()) + { + QName assocTypeQName = entry.getKey(); + ChildAssociationDefinition assocDef = entry.getValue(); + if (!assocDef.isChild()) + { + continue; // Ignore non-child assocs + } + // Get the child associations + List childAssocRefs = nodeService.getChildAssocs( + sourceNodeRef, assocTypeQName, RegexQNamePattern.MATCH_ALL); + for (ChildAssociationRef childAssocRef : childAssocRefs) + { + NodeRef childNodeRef = childAssocRef.getChildRef(); + QName assocQName = childAssocRef.getQName(); + + CopyChildAssociationDetails childAssocCopyDetails = new CopyChildAssociationDetails( + childAssocRef, + targetNodeRef, + targetNodeIsNew, + copyChildren); + + // Handle nested copies + if (copies.contains(childNodeRef)) + { + // The node was already copied i.e. we are seeing a copy produced by some earlier + // copy process. + // The first way this can occur is if a hierarchy is copied into some lower part + // of the hierarchy. We avoid the copied part. + // The other way this could occur is if there are multiple assocs between a + // parent and child. Calls to this method are scoped by class, so the newly-created + // node will not be found because it will have been created using a different assoc + // type. + // A final edge case is where there are multiple assocs between parent and child + // of the same type. This is ignorable. + continue; + } + // Check the callbacks + ChildAssocCopyAction childAssocCopyAction = callback.getChildAssociationCopyAction( + classQName, + copyDetails, + childAssocCopyDetails); + switch (childAssocCopyAction) + { + case IGNORE: + break; + case COPY_ASSOC: + nodeService.addChild(targetNodeRef, childNodeRef, assocTypeQName, assocQName); + break; + case COPY_CHILD: + // Handle potentially cyclic relationships + if (copiesByOriginals.containsKey(childNodeRef)) + { + // This is either a cyclic relationship or there are multiple different + // types of associations between the same parent and child. + // Just hook the child up with the association. + nodeService.addChild(targetNodeRef, childNodeRef, assocTypeQName, assocQName); + } + else + { + // Find out whether to force a recursion + ChildAssocRecurseAction childAssocRecurseAction = callback.getChildAssociationRecurseAction( + classQName, + copyDetails, + childAssocCopyDetails); + switch (childAssocRecurseAction) + { + case RESPECT_RECURSE_FLAG: + // Keep child copy flag the same + break; + case FORCE_RECURSE: + // Force recurse + copyChildren = true; + break; + default: + throw new IllegalStateException("Unrecognized enum"); + } + // This copy may fail silently + copyImpl( + childNodeRef, targetNodeRef, + assocTypeQName, assocQName, + copyChildren, false, // Keep child names for deep copies + copiesByOriginals, copies); + } + break; + default: + throw new IllegalStateException("Unrecognized enum"); + } + } + } + } + + /** + * Callback behaviour retrieval for the 'copiedFrom' aspect. + * + * @return Returns {@link DoNothingCopyBehaviourCallback} always + */ + public CopyBehaviourCallback getCallbackForCopiedFromAspect(QName classRef, CopyDetails copyDetails) + { + return DoNothingCopyBehaviourCallback.getInstance(); + } + + /** + * Callback behaviour retrieval for {@link ContentModel#TYPE_FOLDER} aspect. + * + * @return Returns {@link FolderTypeCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCallbackForFolderType(QName classRef, CopyDetails copyDetails) + { + return FolderTypeCopyBehaviourCallback.INSTANCE; + } + + /** + * cm:folder behaviour + * + * @author Derek Hulley + * @since 3.2 + */ + private static class FolderTypeCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new FolderTypeCopyBehaviourCallback(); + + /** + * Respects the copyChildren flag. Child nodes are copied fully if the association + * is primary otherwise secondary associations are duplicated. + */ + @Override + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + boolean copyChildren = childAssocCopyDetails.isCopyChildren(); + if (childAssocRef.getTypeQName().equals(ContentModel.ASSOC_CONTAINS)) + { + if (!copyChildren) + { + return ChildAssocCopyAction.IGNORE; + } + if (childAssocRef.isPrimary()) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + return ChildAssocCopyAction.COPY_ASSOC; + } + } + else + { + throw new IllegalStateException( + "Behaviour should have been invoked: \n" + + " Aspect: " + this.getClass().getName() + "\n" + + " Assoc: " + childAssocRef + "\n" + + " " + copyDetails); + } + } + + } + + /** + * Callback behaviour retrieval for the 'ownable' aspect. + * + * @return Returns {@link DoNothingCopyBehaviourCallback} always + */ + public CopyBehaviourCallback getCallbackForOwnableAspect(QName classRef, CopyDetails copyDetails) + { + return DoNothingCopyBehaviourCallback.getInstance(); + } + + /** + * Callback behaviour retrieval for the 'author' aspect. + * + * @return Returns {@link DoNothingCopyBehaviourCallback} always + */ + public CopyBehaviourCallback getCallbackForAuthorAspect(QName classRef, CopyDetails copyDetails) + { + return DoNothingCopyBehaviourCallback.getInstance(); + } } diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index 8d90575bd7..a967df9ef3 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -47,7 +47,6 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; @@ -64,6 +63,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.PropertyMap; /** * Node operations service unit tests @@ -72,167 +72,170 @@ import org.alfresco.util.BaseSpringTest; */ public class CopyServiceImplTest extends BaseSpringTest { - /** - * Services used by the tests - */ - private NodeService nodeService; - private CopyService copyService; - private DictionaryDAO dictionaryDAO; - private ContentService contentService; + /** + * Services used by the tests + */ + private NodeService nodeService; + private CopyService copyService; + private DictionaryDAO dictionaryDAO; + private ContentService contentService; private RuleService ruleService; private ActionService actionService; private AuthenticationComponent authenticationComponent; - - /** - * Data used by the tests - */ - private StoreRef storeRef; - private NodeRef sourceNodeRef; - private NodeRef rootNodeRef; - private NodeRef targetNodeRef; - private NodeRef nonPrimaryChildNodeRef; - private NodeRef childNodeRef; - private NodeRef destinationNodeRef; - - /** - * Types and properties used by the tests - */ - private static final String TEST_TYPE_NAMESPACE = "testTypeNamespaceURI"; - private static final QName TEST_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testType"); - private static final QName PROP1_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop1Mandatory"); - private static final QName PROP2_QNAME_OPTIONAL = QName.createQName(TEST_TYPE_NAMESPACE, "prop2Optional"); - - private static final QName TEST_ASPECT_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testAspect"); - private static final QName PROP3_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop3Mandatory"); - private static final QName PROP4_QNAME_OPTIONAL = QName.createQName(TEST_TYPE_NAMESPACE, "prop4Optional"); - - private static final QName PROP_QNAME_MY_NODE_REF = QName.createQName(TEST_TYPE_NAMESPACE, "myNodeRef"); - private static final QName PROP_QNAME_MY_ANY = QName.createQName(TEST_TYPE_NAMESPACE, "myAny"); - + + /** + * Data used by the tests + */ + private StoreRef storeRef; + private NodeRef sourceNodeRef; + private NodeRef rootNodeRef; + private NodeRef targetNodeRef; + private NodeRef nonPrimaryChildNodeRef; + private NodeRef childNodeRef; + private NodeRef destinationNodeRef; + + /** + * Types and properties used by the tests + */ + private static final String TEST_TYPE_NAMESPACE = "testTypeNamespaceURI"; + private static final QName TEST_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testType"); + private static final QName PROP1_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop1Mandatory"); + private static final QName PROP2_QNAME_OPTIONAL = QName.createQName(TEST_TYPE_NAMESPACE, "prop2Optional"); + + private static final QName TEST_ASPECT_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testAspect"); + private static final QName PROP3_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop3Mandatory"); + private static final QName PROP4_QNAME_OPTIONAL = QName.createQName(TEST_TYPE_NAMESPACE, "prop4Optional"); + + private static final QName PROP_QNAME_MY_NODE_REF = QName.createQName(TEST_TYPE_NAMESPACE, "myNodeRef"); + private static final QName PROP_QNAME_MY_ANY = QName.createQName(TEST_TYPE_NAMESPACE, "myAny"); + + private static final QName PROP_QNAME_RESIDUAL_NODE_REF = QName.createQName(TEST_TYPE_NAMESPACE, "residualNodeRef"); + private static final QName PROP_QNAME_RESIDUAL_ANY = QName.createQName(TEST_TYPE_NAMESPACE, "residualAny"); + private static final QName TEST_MANDATORY_ASPECT_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testMandatoryAspect"); private static final QName PROP5_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop5Mandatory"); private static final String TEST_NAME = "testName"; - private static final String TEST_VALUE_1 = "testValue1"; - private static final String TEST_VALUE_2 = "testValue2"; + private static final String TEST_VALUE_1 = "testValue1"; + private static final String TEST_VALUE_2 = "testValue2"; private static final String TEST_VALUE_3 = "testValue3"; - + private static final QName TEST_CHILD_ASSOC_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "contains"); private static final QName TEST_CHILD_ASSOC_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testChildAssocName"); - private static final QName TEST_ASSOC_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testAssocName"); - private static final QName TEST_CHILD_ASSOC_QNAME2 = QName.createQName(TEST_TYPE_NAMESPACE, "testChildAssocName2"); + private static final QName TEST_ASSOC_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testAssocName"); + private static final QName TEST_CHILD_ASSOC_QNAME2 = QName.createQName(TEST_TYPE_NAMESPACE, "testChildAssocName2"); private static final ContentData CONTENT_DATA_TEXT = new ContentData(null, "text/plain", 0L, "UTF-8"); - - /** - * Test content - */ - private static final String SOME_CONTENT = "This is some content ..."; - - /** - * Sets the meta model DAO - * - * @param dictionaryDAO the meta model DAO - */ - public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + + /** + * Test content + */ + private static final String SOME_CONTENT = "This is some content ..."; + + /** + * Sets the meta model DAO + * + * @param dictionaryDAO the meta model DAO + */ + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) { this.dictionaryDAO = dictionaryDAO; } - - /** - * On setup in transaction implementation - */ - @Override - protected void onSetUpInTransaction() - throws Exception - { - // Set the services - this.nodeService = (NodeService)this.applicationContext.getBean("dbNodeService"); - this.copyService = (CopyService)this.applicationContext.getBean("copyService"); - this.contentService = (ContentService)this.applicationContext.getBean("contentService"); + + /** + * On setup in transaction implementation + */ + @Override + protected void onSetUpInTransaction() + throws Exception + { + // Set the services + this.nodeService = (NodeService)this.applicationContext.getBean("dbNodeService"); + this.copyService = (CopyService)this.applicationContext.getBean("copyService"); + this.contentService = (ContentService)this.applicationContext.getBean("contentService"); this.ruleService = (RuleService)this.applicationContext.getBean("ruleService"); this.actionService = (ActionService)this.applicationContext.getBean("actionService"); this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - + this.authenticationComponent.setSystemUserAsCurrentUser(); - // Create the test model - createTestModel(); - - // Create the store and get the root node reference - this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - this.rootNodeRef = this.nodeService.getRootNode(storeRef); - - // Create the node used for copying - ChildAssociationRef childAssocRef = this.nodeService.createNode( - rootNodeRef, + // Create the test model + createTestModel(); + + // Create the store and get the root node reference + this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + this.rootNodeRef = this.nodeService.getRootNode(storeRef); + + // Create the node used for copying + ChildAssociationRef childAssocRef = this.nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}test"), - TEST_TYPE_QNAME, - createTypePropertyBag()); - this.sourceNodeRef = childAssocRef.getChildRef(); - - // Create another bag of properties - Map aspectProperties = new HashMap(); - aspectProperties.put(PROP3_QNAME_MANDATORY, TEST_VALUE_1); - aspectProperties.put(PROP4_QNAME_OPTIONAL, TEST_VALUE_2); - - // Apply the test aspect - this.nodeService.addAspect( - this.sourceNodeRef, - TEST_ASPECT_QNAME, - aspectProperties); + QName.createQName("{test}test"), + TEST_TYPE_QNAME, + createTypePropertyBag()); + this.sourceNodeRef = childAssocRef.getChildRef(); + + // Create another bag of properties + Map aspectProperties = new HashMap(); + aspectProperties.put(PROP3_QNAME_MANDATORY, TEST_VALUE_1); + aspectProperties.put(PROP4_QNAME_OPTIONAL, TEST_VALUE_2); + + // Apply the test aspect + this.nodeService.addAspect( + this.sourceNodeRef, + TEST_ASPECT_QNAME, + aspectProperties); this.nodeService.addAspect(sourceNodeRef, ContentModel.ASPECT_TITLED, null); - - // Add a child - ChildAssociationRef temp3 =this.nodeService.createNode( - this.sourceNodeRef, - TEST_CHILD_ASSOC_TYPE_QNAME, - TEST_CHILD_ASSOC_QNAME, - TEST_TYPE_QNAME, - createTypePropertyBag()); - this.childNodeRef = temp3.getChildRef(); - - // Add a child that is primary - ChildAssociationRef temp2 = this.nodeService.createNode( - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testNonPrimaryChild"), - TEST_TYPE_QNAME, - createTypePropertyBag()); - this.nonPrimaryChildNodeRef = temp2.getChildRef(); - this.nodeService.addChild( + // Add a child + ChildAssociationRef temp3 =this.nodeService.createNode( + this.sourceNodeRef, + TEST_CHILD_ASSOC_TYPE_QNAME, + TEST_CHILD_ASSOC_QNAME, + TEST_TYPE_QNAME, + createTypePropertyBag()); + this.childNodeRef = temp3.getChildRef(); + + // Add a child that is primary + ChildAssociationRef temp2 = this.nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}testNonPrimaryChild"), + TEST_TYPE_QNAME, + createTypePropertyBag()); + + this.nonPrimaryChildNodeRef = temp2.getChildRef(); + this.nodeService.addChild( this.sourceNodeRef, this.nonPrimaryChildNodeRef, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME2); - - // Add a target assoc - ChildAssociationRef temp = this.nodeService.createNode( - rootNodeRef, + + // Add a target assoc + ChildAssociationRef temp = this.nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testAssoc"), - TEST_TYPE_QNAME, - createTypePropertyBag()); - this.targetNodeRef = temp.getChildRef(); - this.nodeService.createAssociation(this.sourceNodeRef, this.targetNodeRef, TEST_ASSOC_TYPE_QNAME); - - // Create a node we can use as the destination in a copy - Map destinationProps = new HashMap(); - destinationProps.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1); + QName.createQName("{test}testAssoc"), + TEST_TYPE_QNAME, + createTypePropertyBag()); + this.targetNodeRef = temp.getChildRef(); + this.nodeService.createAssociation(this.sourceNodeRef, this.targetNodeRef, TEST_ASSOC_TYPE_QNAME); + + // Create a node we can use as the destination in a copy + Map destinationProps = new HashMap(); + destinationProps.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1); destinationProps.put(PROP5_QNAME_MANDATORY, TEST_VALUE_3); destinationProps.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - ChildAssociationRef temp5 = this.nodeService.createNode( - this.rootNodeRef, + ChildAssociationRef temp5 = this.nodeService.createNode( + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}testDestinationNode"), - TEST_TYPE_QNAME, - destinationProps); - this.destinationNodeRef = temp5.getChildRef(); - } - + QName.createQName("{test}testDestinationNode"), + TEST_TYPE_QNAME, + destinationProps); + this.destinationNodeRef = temp5.getChildRef(); + } + @Override protected void onTearDownInTransaction() throws Exception { @@ -240,27 +243,27 @@ public class CopyServiceImplTest extends BaseSpringTest super.onTearDownInTransaction(); } - /** - * Helper method that creates a bag of properties for the test type - * - * @return bag of properties - */ - private Map createTypePropertyBag() - { - Map result = new HashMap(); - result.put(ContentModel.PROP_NAME, TEST_NAME); - result.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1); - result.put(PROP2_QNAME_OPTIONAL, TEST_VALUE_2); + /** + * Helper method that creates a bag of properties for the test type + * + * @return bag of properties + */ + private Map createTypePropertyBag() + { + Map result = new HashMap(); + result.put(ContentModel.PROP_NAME, TEST_NAME); + result.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1); + result.put(PROP2_QNAME_OPTIONAL, TEST_VALUE_2); result.put(PROP5_QNAME_MANDATORY, TEST_VALUE_3); result.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT); - return result; - } - - /** - * Creates the test model used by the tests - */ - private void createTestModel() - { + return result; + } + + /** + * Creates the test model used by the tests + */ + private void createTestModel() + { M2Model model = M2Model.createModel("test:nodeoperations"); model.createNamespace(TEST_TYPE_NAMESPACE, "test"); model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, NamespaceService.DICTIONARY_MODEL_PREFIX); @@ -275,40 +278,40 @@ public class CopyServiceImplTest extends BaseSpringTest prop1.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); prop1.setMultiValued(false); - M2Property prop2 = testType.createProperty("test:" + PROP2_QNAME_OPTIONAL.getLocalName()); - prop2.setMandatory(false); + M2Property prop2 = testType.createProperty("test:" + PROP2_QNAME_OPTIONAL.getLocalName()); + prop2.setMandatory(false); prop2.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); - prop2.setMandatory(false); - - M2Property propNodeRef = testType.createProperty("test:" + PROP_QNAME_MY_NODE_REF.getLocalName()); - propNodeRef.setMandatory(false); - propNodeRef.setType("d:" + DataTypeDefinition.NODE_REF.getLocalName()); - propNodeRef.setMandatory(false); - - M2Property propAnyNodeRef = testType.createProperty("test:" + PROP_QNAME_MY_ANY.getLocalName()); - propAnyNodeRef.setMandatory(false); - propAnyNodeRef.setType("d:" + DataTypeDefinition.ANY.getLocalName()); - propAnyNodeRef.setMandatory(false); - - M2ChildAssociation childAssoc = testType.createChildAssociation("test:" + TEST_CHILD_ASSOC_TYPE_QNAME.getLocalName()); + prop2.setMandatory(false); + + M2Property propNodeRef = testType.createProperty("test:" + PROP_QNAME_MY_NODE_REF.getLocalName()); + propNodeRef.setMandatory(false); + propNodeRef.setType("d:" + DataTypeDefinition.NODE_REF.getLocalName()); + propNodeRef.setMandatory(false); + + M2Property propAnyNodeRef = testType.createProperty("test:" + PROP_QNAME_MY_ANY.getLocalName()); + propAnyNodeRef.setMandatory(false); + propAnyNodeRef.setType("d:" + DataTypeDefinition.ANY.getLocalName()); + propAnyNodeRef.setMandatory(false); + + M2ChildAssociation childAssoc = testType.createChildAssociation("test:" + TEST_CHILD_ASSOC_TYPE_QNAME.getLocalName()); childAssoc.setTargetClassName("sys:base"); - childAssoc.setTargetMandatory(false); - - M2Association assoc = testType.createAssociation("test:" + TEST_ASSOC_TYPE_QNAME.getLocalName()); + childAssoc.setTargetMandatory(false); + + M2Association assoc = testType.createAssociation("test:" + TEST_ASSOC_TYPE_QNAME.getLocalName()); assoc.setTargetClassName("sys:base"); - assoc.setTargetMandatory(false); - - M2Aspect testAspect = model.createAspect("test:" + TEST_ASPECT_QNAME.getLocalName()); - - M2Property prop3 = testAspect.createProperty("test:" + PROP3_QNAME_MANDATORY.getLocalName()); - prop3.setMandatory(true); + assoc.setTargetMandatory(false); + + M2Aspect testAspect = model.createAspect("test:" + TEST_ASPECT_QNAME.getLocalName()); + + M2Property prop3 = testAspect.createProperty("test:" + PROP3_QNAME_MANDATORY.getLocalName()); + prop3.setMandatory(true); prop3.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); - prop3.setMultiValued(false); - - M2Property prop4 = testAspect.createProperty("test:" + PROP4_QNAME_OPTIONAL.getLocalName()); - prop4.setMandatory(false); + prop3.setMultiValued(false); + + M2Property prop4 = testAspect.createProperty("test:" + PROP4_QNAME_OPTIONAL.getLocalName()); + prop4.setMandatory(false); prop4.setType("d:" + DataTypeDefinition.TEXT.getLocalName()); - prop4.setMultiValued(false); + prop4.setMultiValued(false); M2Aspect testMandatoryAspect = model.createAspect("test:" + TEST_MANDATORY_ASPECT_QNAME.getLocalName()); M2Property prop5 = testMandatoryAspect.createProperty("test:" + PROP5_QNAME_MANDATORY.getLocalName()); @@ -318,70 +321,70 @@ public class CopyServiceImplTest extends BaseSpringTest testType.addMandatoryAspect("test:" + TEST_MANDATORY_ASPECT_QNAME.getLocalName()); dictionaryDAO.putModel(model); - } - - /** - * Test copy new node within store - */ - public void testCopyToNewNode() - { + } + + /** + * Test copy new node within store + */ + public void testCopyToNewNode() + { // Check that the node has no copies List copies = this.copyService.getCopies(this.sourceNodeRef); assertNotNull(copies); assertTrue(copies.isEmpty()); - // Copy to new node without copying children - NodeRef copy = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + // Copy to new node without copying children + NodeRef copy = this.copyService.copy( + this.sourceNodeRef, + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}copyAssoc")); - checkCopiedNode(this.sourceNodeRef, copy, true, true, false); + QName.createQName("{test}copyAssoc")); + checkCopiedNode(this.sourceNodeRef, copy, true, true, false); List copies2 = this.copyService.getCopies(this.sourceNodeRef); assertNotNull(copies2); assertEquals(1, copies2.size()); - + // Copy to new node, copying children - NodeRef copy2 = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + NodeRef copy2 = this.copyService.copy( + this.sourceNodeRef, + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}copyAssoc2"), - true); - checkCopiedNode(this.sourceNodeRef, copy2, true, true, true); + QName.createQName("{test}copyAssoc2"), + true); + checkCopiedNode(this.sourceNodeRef, copy2, true, true, true); List copies3 = this.copyService.getCopies(this.sourceNodeRef); assertNotNull(copies3); assertEquals(2, copies3.size()); - - // Check that a copy of a copy works correctly - NodeRef copyOfCopy = this.copyService.copy( - copy, - this.rootNodeRef, + + // Check that a copy of a copy works correctly + NodeRef copyOfCopy = this.copyService.copy( + copy, + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}copyOfCopy")); - checkCopiedNode(copy, copyOfCopy, true, true, false); - + QName.createQName("{test}copyOfCopy")); + checkCopiedNode(copy, copyOfCopy, true, true, false); + // TODO check copying from a versioned copy - // TODO check copying from a lockable copy - - // Check copying from a node with content - ContentWriter contentWriter = this.contentService.getWriter(this.sourceNodeRef, ContentModel.PROP_CONTENT, true); - contentWriter.putContent(SOME_CONTENT); - NodeRef copyWithContent = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, + // TODO check copying from a lockable copy + + // Check copying from a node with content + ContentWriter contentWriter = this.contentService.getWriter(this.sourceNodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.putContent(SOME_CONTENT); + NodeRef copyWithContent = this.copyService.copy( + this.sourceNodeRef, + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}copyWithContent")); - checkCopiedNode(this.sourceNodeRef, copyWithContent, true, true, false); - ContentReader contentReader = this.contentService.getReader(copyWithContent, ContentModel.PROP_CONTENT); - assertNotNull(contentReader); - assertEquals(SOME_CONTENT, contentReader.getContentString()); - - // TODO check copying to a different store + QName.createQName("{test}copyWithContent")); + checkCopiedNode(this.sourceNodeRef, copyWithContent, true, true, false); + ContentReader contentReader = this.contentService.getReader(copyWithContent, ContentModel.PROP_CONTENT); + assertNotNull(contentReader); + assertEquals(SOME_CONTENT, contentReader.getContentString()); + + // TODO check copying to a different store //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); - } + // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + } public void testCopyNodeWithRules() { @@ -435,11 +438,11 @@ public class CopyServiceImplTest extends BaseSpringTest // Now copy the node without copying the children and check that the rules have been copied NodeRef copy2 = this.copyService.copy( - this.sourceNodeRef, - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}withRuleCopyNoChildren"), - false); + this.sourceNodeRef, + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}withRuleCopyNoChildren"), + false); // System.out.println( // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); @@ -462,90 +465,121 @@ public class CopyServiceImplTest extends BaseSpringTest assertEquals(this.ruleService.getOwningNodeRef(copiedRule2), copy2); assertEquals(rule.getAction().getActionDefinitionName(), copiedRule2.getAction().getActionDefinitionName()); } - - public void testCopyToExistingNode() - { - // Copy nodes within the same store - this.copyService.copy(this.sourceNodeRef, this.destinationNodeRef); - checkCopiedNode(this.sourceNodeRef, this.destinationNodeRef, false, true, false); - - // TODO check copying from a copy - // TODO check copying from a versioned copy - // TODO check copying from a lockable copy - // TODO check copying from a node with content - - // TODO check copying nodes between stores - - //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); - } - - /** - * Test a potentially recursive copy - */ - public void testRecursiveCopy() - { - // Need to create a potentially recursive node structure - NodeRef nodeOne = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - NodeRef nodeTwo = this.nodeService.createNode( - nodeOne, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - NodeRef nodeThree = this.nodeService.createNode( - nodeTwo, - ContentModel.ASSOC_CHILDREN, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_CONTAINER).getChildRef(); - - // Issue a potentialy recursive copy - this.copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true); - - //System.out.println( + + public void testCopyToExistingNode() + { + // Copy nodes within the same store + this.copyService.copy(this.sourceNodeRef, this.destinationNodeRef); + checkCopiedNode(this.sourceNodeRef, this.destinationNodeRef, false, true, false); + + // TODO check copying from a copy + // TODO check copying from a versioned copy + // TODO check copying from a lockable copy + // TODO check copying from a node with content + + // TODO check copying nodes between stores + + //System.out.println( + // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + } + + /** + * Test a potentially recursive copy + */ + public void testRecursiveCopy() + { + PropertyMap props = new PropertyMap(); + // Need to create a potentially recursive node structure + props.put(ContentModel.PROP_NODE_UUID, "nodeOne"); + NodeRef nodeOne = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER, + props).getChildRef(); + props.put(ContentModel.PROP_NODE_UUID, "nodeTwo"); + NodeRef nodeTwo = this.nodeService.createNode( + nodeOne, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER, + props).getChildRef(); + props.put(ContentModel.PROP_NODE_UUID, "nodeThree"); + NodeRef nodeThree = this.nodeService.createNode( + nodeTwo, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_CONTAINER, + props).getChildRef(); + + // Issue a potentialy recursive copy + this.copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true); + + //System.out.println( // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); - } - - /** - * Test that realtive links between nodes are restored once the copy is completed - */ - public void testRelativeLinks() - { - QName nodeOneAssocName = QName.createQName("{test}nodeOne"); - QName nodeTwoAssocName = QName.createQName("{test}nodeTwo"); - QName nodeThreeAssocName = QName.createQName("{test}nodeThree"); - QName nodeFourAssocName = QName.createQName("{test}nodeFour"); - - NodeRef nodeOne = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - nodeOneAssocName, - TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeTwo = this.nodeService.createNode( - nodeOne, - TEST_CHILD_ASSOC_TYPE_QNAME, - nodeTwoAssocName, - TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeThree = this.nodeService.createNode( - nodeTwo, - TEST_CHILD_ASSOC_TYPE_QNAME, - nodeThreeAssocName, - TEST_TYPE_QNAME).getChildRef(); - NodeRef nodeFour = this.nodeService.createNode( - nodeOne, - TEST_CHILD_ASSOC_TYPE_QNAME, - nodeFourAssocName, - TEST_TYPE_QNAME).getChildRef(); - this.nodeService.addChild(nodeFour, nodeThree, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME); - this.nodeService.createAssociation(nodeTwo, nodeThree, TEST_ASSOC_TYPE_QNAME); - this.nodeService.setProperty(nodeOne, PROP_QNAME_MY_NODE_REF, nodeThree); - this.nodeService.setProperty(nodeOne, PROP_QNAME_MY_ANY, nodeThree); - - // Make node one actionable with a rule to copy nodes into node two - Map params = new HashMap(1); + } + + public void testCopyResidualProperties() throws Exception + { + QName nodeOneAssocName = QName.createQName("{test}nodeOne"); + + NodeRef nodeOne = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + nodeOneAssocName, + TEST_TYPE_QNAME).getChildRef(); + this.nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_NODE_REF, nodeOne); + this.nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_ANY, nodeOne); + NodeRef nodeOneCopy = copyService.copy( + nodeOne, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}copiedNodeOne")); + // Check the node ref property + Serializable propNodeRef = nodeService.getProperty(nodeOneCopy, PROP_QNAME_RESIDUAL_NODE_REF); + assertEquals("Residual d:noderef not copied", nodeOne, propNodeRef); + + // Check the any property + Serializable propAny = nodeService.getProperty(nodeOneCopy, PROP_QNAME_RESIDUAL_ANY); + assertEquals("Residual d:any not copied", nodeOne, propAny); + + } + + /** + * Test that realtive links between nodes are restored once the copy is completed + */ + public void testRelativeLinks() + { + QName nodeOneAssocName = QName.createQName("{test}nodeOne"); + QName nodeTwoAssocName = QName.createQName("{test}nodeTwo"); + QName nodeThreeAssocName = QName.createQName("{test}nodeThree"); + QName nodeFourAssocName = QName.createQName("{test}nodeFour"); + + NodeRef nodeOne = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + nodeOneAssocName, + TEST_TYPE_QNAME).getChildRef(); + NodeRef nodeTwo = this.nodeService.createNode( + nodeOne, + TEST_CHILD_ASSOC_TYPE_QNAME, + nodeTwoAssocName, + TEST_TYPE_QNAME).getChildRef(); + NodeRef nodeThree = this.nodeService.createNode( + nodeTwo, + TEST_CHILD_ASSOC_TYPE_QNAME, + nodeThreeAssocName, + TEST_TYPE_QNAME).getChildRef(); + NodeRef nodeFour = this.nodeService.createNode( + nodeOne, + TEST_CHILD_ASSOC_TYPE_QNAME, + nodeFourAssocName, + TEST_TYPE_QNAME).getChildRef(); + this.nodeService.addChild(nodeFour, nodeThree, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME); + this.nodeService.createAssociation(nodeTwo, nodeThree, TEST_ASSOC_TYPE_QNAME); + + // Make node one actionable with a rule to copy nodes into node two + Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, nodeTwo); params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_TYPE_QNAME); params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName("{test}ruleCopy")); @@ -556,233 +590,225 @@ public class CopyServiceImplTest extends BaseSpringTest action.addActionCondition(condition); rule.setAction(action); this.ruleService.saveRule(nodeOne, rule); - - // Do a deep copy - NodeRef nodeOneCopy = this.copyService.copy(nodeOne, this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copiedNodeOne"), true); - NodeRef nodeTwoCopy = null; - NodeRef nodeThreeCopy = null; - NodeRef nodeFourCopy = null; - - //System.out.println( - // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); - - List nodeOneCopyChildren = this.nodeService.getChildAssocs(nodeOneCopy); - assertNotNull(nodeOneCopyChildren); - assertEquals(3, nodeOneCopyChildren.size()); - for (ChildAssociationRef nodeOneCopyChild : nodeOneCopyChildren) - { - if (nodeOneCopyChild.getQName().equals(nodeTwoAssocName) == true) - { - nodeTwoCopy = nodeOneCopyChild.getChildRef(); - - List nodeTwoCopyChildren = this.nodeService.getChildAssocs(nodeTwoCopy); - assertNotNull(nodeTwoCopyChildren); - assertEquals(1, nodeTwoCopyChildren.size()); - for (ChildAssociationRef nodeTwoCopyChild : nodeTwoCopyChildren) - { - if (nodeTwoCopyChild.getQName().equals(nodeThreeAssocName) == true) - { - nodeThreeCopy = nodeTwoCopyChild.getChildRef(); - } - } - } - else if (nodeOneCopyChild.getQName().equals(nodeFourAssocName) == true) - { - nodeFourCopy = nodeOneCopyChild.getChildRef(); - } - } - assertNotNull(nodeTwoCopy); - assertNotNull(nodeThreeCopy); - assertNotNull(nodeFourCopy); - - // Check the non primary child assoc - List children = this.nodeService.getChildAssocs( + + // Do a deep copy + NodeRef nodeOneCopy = this.copyService.copy(nodeOne, this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copiedNodeOne"), true); + NodeRef nodeTwoCopy = null; + NodeRef nodeThreeCopy = null; + NodeRef nodeFourCopy = null; + + //System.out.println( + // NodeStoreInspector.dumpNodeStore(this.nodeService, this.storeRef)); + + List nodeOneCopyChildren = this.nodeService.getChildAssocs(nodeOneCopy); + assertNotNull(nodeOneCopyChildren); + assertEquals(3, nodeOneCopyChildren.size()); + for (ChildAssociationRef nodeOneCopyChild : nodeOneCopyChildren) + { + if (nodeOneCopyChild.getQName().equals(nodeTwoAssocName) == true) + { + nodeTwoCopy = nodeOneCopyChild.getChildRef(); + + List nodeTwoCopyChildren = this.nodeService.getChildAssocs(nodeTwoCopy); + assertNotNull(nodeTwoCopyChildren); + assertEquals(1, nodeTwoCopyChildren.size()); + for (ChildAssociationRef nodeTwoCopyChild : nodeTwoCopyChildren) + { + if (nodeTwoCopyChild.getQName().equals(nodeThreeAssocName) == true) + { + nodeThreeCopy = nodeTwoCopyChild.getChildRef(); + } + } + } + else if (nodeOneCopyChild.getQName().equals(nodeFourAssocName) == true) + { + nodeFourCopy = nodeOneCopyChild.getChildRef(); + } + } + assertNotNull(nodeTwoCopy); + assertNotNull(nodeThreeCopy); + assertNotNull(nodeFourCopy); + + // Check the non primary child assoc + List children = this.nodeService.getChildAssocs( nodeFourCopy, RegexQNamePattern.MATCH_ALL, TEST_CHILD_ASSOC_QNAME); - assertNotNull(children); - assertEquals(1, children.size()); - ChildAssociationRef child = children.get(0); - assertEquals(child.getChildRef(), nodeThreeCopy); - - // Check the node ref property - NodeRef nodeRef = (NodeRef)this.nodeService.getProperty(nodeOneCopy, PROP_QNAME_MY_NODE_REF); - assertNotNull(nodeRef); - assertEquals(nodeThreeCopy, nodeRef); - - // Check the any property - NodeRef anyNodeRef = (NodeRef)this.nodeService.getProperty(nodeOneCopy, PROP_QNAME_MY_ANY); - assertNotNull(anyNodeRef); - assertEquals(nodeThreeCopy, anyNodeRef); - - // Check the target assoc - List assocs = this.nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME); - assertNotNull(assocs); - assertEquals(1, assocs.size()); - AssociationRef assoc = assocs.get(0); - assertEquals(assoc.getTargetRef(), nodeThreeCopy); - - // Check that the rule parameter values have been made relative - List rules = this.ruleService.getRules(nodeOneCopy); - assertNotNull(rules); - assertEquals(1, rules.size()); - Rule copiedRule = rules.get(0); - assertNotNull(copiedRule); - Action ruleAction = copiedRule.getAction(); - assertNotNull(ruleAction); - NodeRef value = (NodeRef)ruleAction.getParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER); - assertNotNull(value); - assertEquals(nodeTwoCopy, value); - } - - public void testCopyAndRename() - { - // Check a normal copy with no dup restrictions - NodeRef copy = this.copyService.copyAndRename( - this.sourceNodeRef, - this.rootNodeRef, + assertNotNull(children); + assertEquals(1, children.size()); + ChildAssociationRef child = children.get(0); + assertEquals(child.getChildRef(), nodeThree); + +// // Check the target assoc +// List assocs = this.nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME); +// assertNotNull(assocs); +// assertEquals(1, assocs.size()); +// AssociationRef assoc = assocs.get(0); +// assertEquals(assoc.getTargetRef(), nodeThreeCopy); +// + // Check that the rule parameter values have been made relative + List rules = this.ruleService.getRules(nodeOneCopy); + assertNotNull(rules); + assertEquals(1, rules.size()); + Rule copiedRule = rules.get(0); + assertNotNull(copiedRule); + Action ruleAction = copiedRule.getAction(); + assertNotNull(ruleAction); + NodeRef value = (NodeRef)ruleAction.getParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER); + assertNotNull(value); + assertEquals(nodeTwoCopy, value); + } + + public void testCopyAndRename() + { + // Check a normal copy with no dup restrictions + NodeRef copy = this.copyService.copyAndRename( + this.sourceNodeRef, + this.rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}copyAssoc"), - false); - checkCopiedNode(this.sourceNodeRef, copy, true, true, false); - assertTrue(TEST_NAME.equals(this.nodeService.getProperty(copy, ContentModel.PROP_NAME))); - - // Create a folder and content node - Map propsFolder = new HashMap(1); - propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); - NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); - Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, TEST_NAME); - NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}renametest"), ContentModel.TYPE_CONTENT, props).getChildRef(); - - // Now copy the content node with the duplicate name restriction - NodeRef contentCopy = this.copyService.copy(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}bobbins"), false); - assertFalse(TEST_NAME.equals(this.nodeService.getProperty(contentCopy, ContentModel.PROP_NAME))); - } - - /** - * https://issues.alfresco.com/jira/browse/ETWOONE-224 - */ - public void testETWOONE_244() - { - // Create a folder and content node - Map propsFolder = new HashMap(1); - propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); - NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); - Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, "myDoc.txt"); - NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef(); - - NodeRef copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); - QName copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); - - copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); - copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + QName.createQName("{test}copyAssoc"), + false); + checkCopiedNode(this.sourceNodeRef, copy, true, true, false); + assertTrue(TEST_NAME.equals(this.nodeService.getProperty(copy, ContentModel.PROP_NAME))); + + // Create a folder and content node + Map propsFolder = new HashMap(1); + propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); + NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, TEST_NAME); + NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}renametest"), ContentModel.TYPE_CONTENT, props).getChildRef(); + + // Now copy the content node with the duplicate name restriction + NodeRef contentCopy = this.copyService.copy(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}bobbins"), false); + assertFalse(TEST_NAME.equals(this.nodeService.getProperty(contentCopy, ContentModel.PROP_NAME))); + } + + /** + * https://issues.alfresco.com/jira/browse/ETWOONE-224 + */ + public void testETWOONE_244() + { + // Create a folder and content node + Map propsFolder = new HashMap(1); + propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); + NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "myDoc.txt"); + NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef(); + + NodeRef copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + QName copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of myDoc.txt"); + assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + + copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of myDoc.txt"); + assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); - copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); - assertEquals("Copy of Copy of Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); - copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of Copy of myDoc.txt"); - assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); - } - - /** - * Check that the copied node contains the state we are expecting - * - * @param sourceNodeRef the source node reference - * @param destinationNodeRef the destination node reference - */ - private void checkCopiedNode(NodeRef sourceNodeRef, NodeRef destinationNodeRef, boolean newCopy, boolean sameStore, boolean copyChildren) - { - if (newCopy == true) - { - if (sameStore == true) - { - // Check that the copy aspect has been applied to the copy - boolean hasCopyAspect = this.nodeService.hasAspect(destinationNodeRef, ContentModel.ASPECT_COPIEDFROM); - assertTrue(hasCopyAspect); - NodeRef copyNodeRef = (NodeRef)this.nodeService.getProperty(destinationNodeRef, ContentModel.PROP_COPY_REFERENCE); - assertNotNull(copyNodeRef); - assertEquals(sourceNodeRef, copyNodeRef); - } - else - { - // Check that destiantion has the same id as the source - assertEquals(sourceNodeRef.getId(), destinationNodeRef.getId()); + copy = this.copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false); + assertEquals("Copy of Copy of Copy of myDoc.txt", this.nodeService.getProperty(copy, ContentModel.PROP_NAME)); + copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of Copy of myDoc.txt"); + assertEquals(copyQName, this.nodeService.getPrimaryParent(copy).getQName()); + } + + /** + * Check that the copied node contains the state we are expecting + * + * @param sourceNodeRef the source node reference + * @param destinationNodeRef the destination node reference + */ + private void checkCopiedNode( + NodeRef sourceNodeRef, NodeRef destinationNodeRef, + boolean newCopy, boolean sameStore, boolean copyChildren) + { + if (newCopy == true) + { + if (sameStore == true) + { + // Check that the copy aspect has been applied to the copy + boolean hasCopyAspect = this.nodeService.hasAspect(destinationNodeRef, ContentModel.ASPECT_COPIEDFROM); + assertTrue("Missing aspect: " + ContentModel.ASPECT_COPIEDFROM, hasCopyAspect); + NodeRef copyNodeRef = (NodeRef)this.nodeService.getProperty(destinationNodeRef, ContentModel.PROP_COPY_REFERENCE); + assertNotNull(copyNodeRef); + assertEquals(sourceNodeRef, copyNodeRef); } - } - - boolean hasTestAspect = this.nodeService.hasAspect(destinationNodeRef, TEST_ASPECT_QNAME); - assertTrue(hasTestAspect); - - // Check that all the correct properties have been copied - Map destinationProperties = this.nodeService.getProperties(destinationNodeRef); - assertNotNull(destinationProperties); - String value1 = (String)destinationProperties.get(PROP1_QNAME_MANDATORY); - assertNotNull(value1); - assertEquals(TEST_VALUE_1, value1); - String value2 = (String)destinationProperties.get(PROP2_QNAME_OPTIONAL); - assertNotNull(value2); - assertEquals(TEST_VALUE_2, value2); - String value3 = (String)destinationProperties.get(PROP3_QNAME_MANDATORY); - assertNotNull(value3); - assertEquals(TEST_VALUE_1, value3); - String value4 = (String)destinationProperties.get(PROP4_QNAME_OPTIONAL); - assertNotNull(value4); - assertEquals(TEST_VALUE_2, value4); - - // Check all the target associations have been copied - List destinationTargets = this.nodeService.getTargetAssocs(destinationNodeRef, TEST_ASSOC_TYPE_QNAME); - assertNotNull(destinationTargets); - assertEquals(1, destinationTargets.size()); - AssociationRef nodeAssocRef = destinationTargets.get(0); - assertNotNull(nodeAssocRef); - assertEquals(this.targetNodeRef, nodeAssocRef.getTargetRef()); - - // Check all the child associations have been copied - List childAssocRefs = this.nodeService.getChildAssocs(destinationNodeRef); - assertNotNull(childAssocRefs); - int expectedSize = 2; - if (this.nodeService.hasAspect(destinationNodeRef, RuleModel.ASPECT_RULES) == true) - { - expectedSize = expectedSize + 1; - } - - assertEquals(expectedSize, childAssocRefs.size()); - for (ChildAssociationRef ref : childAssocRefs) - { - if (ref.getQName().equals(TEST_CHILD_ASSOC_QNAME2) == true) - { - // Since this child is non-primary in the source it will always be non-primary in the destination - assertFalse(ref.isPrimary()); - assertEquals(this.nonPrimaryChildNodeRef, ref.getChildRef()); - } - else - { - if (copyChildren == false) - { - if (ref.getTypeQName().equals(RuleModel.ASSOC_RULE_FOLDER) == true) - { - assertTrue(ref.isPrimary()); - assertTrue(this.childNodeRef.equals(ref.getChildRef()) == false); - } - else - { - assertFalse(ref.isPrimary()); - assertEquals(this.childNodeRef, ref.getChildRef()); - } - } - else - { - assertTrue(ref.isPrimary()); - assertTrue(this.childNodeRef.equals(ref.getChildRef()) == false); - - // TODO need to check that the copied child has all the correct details .. - } - } - } - } + else + { + // Check that destiantion has the same id as the source + assertEquals(sourceNodeRef.getId(), destinationNodeRef.getId()); + } + } + + boolean hasTestAspect = this.nodeService.hasAspect(destinationNodeRef, TEST_ASPECT_QNAME); + assertTrue(hasTestAspect); + + // Check that all the correct properties have been copied + Map destinationProperties = this.nodeService.getProperties(destinationNodeRef); + assertNotNull(destinationProperties); + String value1 = (String)destinationProperties.get(PROP1_QNAME_MANDATORY); + assertNotNull(value1); + assertEquals(TEST_VALUE_1, value1); + String value2 = (String)destinationProperties.get(PROP2_QNAME_OPTIONAL); + assertNotNull(value2); + assertEquals(TEST_VALUE_2, value2); + String value3 = (String)destinationProperties.get(PROP3_QNAME_MANDATORY); + assertNotNull(value3); + assertEquals(TEST_VALUE_1, value3); + String value4 = (String)destinationProperties.get(PROP4_QNAME_OPTIONAL); + assertNotNull(value4); + assertEquals(TEST_VALUE_2, value4); + +// // Check all the target associations have been copied +// List destinationTargets = this.nodeService.getTargetAssocs(destinationNodeRef, TEST_ASSOC_TYPE_QNAME); +// assertNotNull(destinationTargets); +// assertEquals(1, destinationTargets.size()); +// AssociationRef nodeAssocRef = destinationTargets.get(0); +// assertNotNull(nodeAssocRef); +// assertEquals(this.targetNodeRef, nodeAssocRef.getTargetRef()); +// + // Check all the child associations have been copied + List childAssocRefs = this.nodeService.getChildAssocs(destinationNodeRef); + assertNotNull(childAssocRefs); + int expectedSize = copyChildren ? 2 : 0; + if (this.nodeService.hasAspect(destinationNodeRef, RuleModel.ASPECT_RULES) == true) + { + expectedSize = expectedSize + 1; + } + + assertEquals(expectedSize, childAssocRefs.size()); + for (ChildAssociationRef ref : childAssocRefs) + { + if (ref.getQName().equals(TEST_CHILD_ASSOC_QNAME2) == true) + { + // Since this child is non-primary in the source it will always be non-primary in the destination + assertFalse(ref.isPrimary()); + assertEquals(this.nonPrimaryChildNodeRef, ref.getChildRef()); + } + else + { + if (copyChildren == false) + { + if (ref.getTypeQName().equals(RuleModel.ASSOC_RULE_FOLDER) == true) + { + assertTrue(ref.isPrimary()); + assertTrue(this.childNodeRef.equals(ref.getChildRef()) == false); + } + else + { + assertFalse(ref.isPrimary()); + assertEquals(this.childNodeRef, ref.getChildRef()); + } + } + else + { + assertTrue(ref.isPrimary()); + assertTrue(this.childNodeRef.equals(ref.getChildRef()) == false); + + // TODO need to check that the copied child has all the correct details .. + } + } + } + } } diff --git a/source/java/org/alfresco/repo/copy/CopyServicePolicies.java b/source/java/org/alfresco/repo/copy/CopyServicePolicies.java index c75ba71e4f..7c3e00811d 100644 --- a/source/java/org/alfresco/repo/copy/CopyServicePolicies.java +++ b/source/java/org/alfresco/repo/copy/CopyServicePolicies.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 @@ -27,61 +27,125 @@ package org.alfresco.repo.copy; import java.util.Map; import org.alfresco.repo.policy.ClassPolicy; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; /** - * @author Roy Wetherall + * Policies for the CopyService. + *

+ * A typical registration and invocation would look like this: + *

+ *  public void init()
+ *  {
+ *      this.policyComponent.bindClassBehaviour(
+ *              QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"),
+ *              ActionModel.ASPECT_ACTIONS,
+ *              new JavaBehaviour(this, "getCopyCallback"));
+ *      this.policyComponent.bindClassBehaviour(
+ *              QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"),
+ *              ActionModel.ASPECT_ACTIONS,
+ *              new JavaBehaviour(this, "onCopyComplete"));
+ *      ...
+ *  }
+ *  
+ *  public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails)
+ *  {
+ *      return new XyzAspectCopyBehaviourCallback();
+ *  }
+ *      
+ *  private static class XyzAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback
+ *  {
+ *      // Override methods any to achieve the desired behaviour
+ *      
+ *      public boolean mustCopyChildAssociation(QName classQName, CopyDetails copyDetails, ChildAssociationRef childAssocRef)
+ *      {
+ *          ...
+ *      }
+ *  }
+ *  
+ *  public void onCopyComplete(
+ *          NodeRef sourceNodeRef,
+ *          NodeRef targetNodeRef,
+ *          boolean copyToNewNode,
+ *          Map copyMap)
+ *  {
+ *      ...
+ *  }
+ *  
+ * + * @author Derek Hulley */ public interface CopyServicePolicies { - /** - * Policy invoked when a node is copied - */ - public interface OnCopyNodePolicy extends ClassPolicy - { + /** + * Policy invoked when a node is copied. + *

+ * Note: Copy policies are used to modify the copy behaviour. Rather than attempt to + * determine, up front, the behaviour that applies for all types and aspects, + * the callbacks are used to lazily adjust the behaviour. + *

+ * Implementing this policy is particularly important if aspects want to partake in the copy process. + * The behaviour can change whether or not the aspect is copied and which of the properties to carry + * to the new node. + *

+ * If no behaviour is registered or no callback is given, then + * the {@link DefaultCopyBehaviourCallback default behaviour} is assumed. Several pre-defined behaviours + * exist to simplify the callbacks, including: + *

    + *
  • Do nothing: {@link DoNothingCopyBehaviourCallback}
  • + *
  • Default: {@link DefaultCopyBehaviourCallback}
  • + *
+ * The {@link DefaultCopyBehaviourCallback} is probably the best starting point for further + * callback implementations; overriding the class allows the behaviour to be overridden, provided + * that this policy method is implemented. + *

+ * Note: A 'class' is either a type or an aspect. + */ + public interface OnCopyNodePolicy extends ClassPolicy + { /** - * @param classRef the type of node being copied - * @param sourceNodeRef node being copied - * @param destinationStoreRef the destination store reference - * @param copyToNewNode indicates whether we are copying to a new node or not - * @param copyDetails modifiable node details + * Called for all types and aspects before copying a node. * - * @deprecated WARNING: This method will be replaced with a more - * flexible and future-proof policy callback in the post-3.1 / Labs 3D code. + * @param classRef the type or aspect qualified name + * @param copyDetails the details of the impending copy + * @return Return the callback that will be used to modify the copy behaviour for this + * dictionary class. Return null to assume the default copy the helper to carry information back to the Copy Service. If this is not used, then + * neither the aspect nor any of its properties will be copied. + * + * @see CopyServicePolicies + * + * @since V3.2 */ - public void onCopyNode( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, + CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails); + + static Arg ARG_0 = Arg.KEY; + static Arg ARG_1 = Arg.KEY; + } + + /** + * Final callback after the copy (including any cascading) has been completed. This should + * be used where post-copy manipulation of nodes is required in order to enforce adherence + * to a particular dictionary or business model. + *

+ * The copy map contains all the nodes created during the copy, this helps to re-map + * any potentially relative associations. + */ + public interface OnCopyCompletePolicy extends ClassPolicy + { + /** + * @param classRef the type of the node that was copied + * @param sourceNodeRef the origional node + * @param targetNodeRef the destination node + * @param copyMap a map containing all the nodes that have been created during the copy + */ + public void onCopyComplete( + QName classRef, + NodeRef sourceNodeRef, + NodeRef targetNodeRef, boolean copyToNewNode, - PolicyScope copyDetails); - } - - /** - * Policy invoked when the copy operation invoked on a node is complete. - *

- * The copy map contains all the nodes created during the copy, this helps to re-map - * any potentially relative associations. - */ - public interface OnCopyCompletePolicy extends ClassPolicy - { - /** - * @param classRef the type of the node that was copied - * @param sourceNodeRef the origional node - * @param destinationRef the destination node - * @param copyMap a map containing all the nodes that have been created during the copy - */ - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef destinationRef, - boolean copyToNewNode, - Map copyMap); - - static Arg ARG_0 = Arg.KEY; + Map copyMap); + + static Arg ARG_0 = Arg.KEY; static Arg ARG_1 = Arg.KEY; - } + } } diff --git a/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java new file mode 100644 index 0000000000..8c41e2d267 --- /dev/null +++ b/source/java/org/alfresco/repo/copy/DefaultCopyBehaviourCallback.java @@ -0,0 +1,107 @@ +/* + * 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.copy; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.namespace.QName; + + +/** + * The default behaviour that a type of aspect implements if there is no associated + * <{@link CopyBehaviourCallback behaviour}. + *

+ * This implementation is {@link #getInstance() stateless} and therefore thread-safe. + *

+ * The default behaviour is: + *

    + *
  • Must Copy: YES
  • + *
  • Must Cascade: YES, if cascade is on
  • + *
  • Properties to Copy:ALL
  • + *
+ * + * @author Derek Hulley + * @since 3.2 + */ +public class DefaultCopyBehaviourCallback extends AbstractCopyBehaviourCallback +{ + private static CopyBehaviourCallback instance = new DefaultCopyBehaviourCallback(); + + /** + * @return Returns a stateless singleton + */ + public static CopyBehaviourCallback getInstance() + { + return instance; + } + + /** + * Default behaviour: Always copy + * + * @return Returns true always + */ + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + return true; + } + + /** + * Default behaviour: Cascade if we are copying children AND the association is primary + * + * @return Returns true if the association is primary and copyChildren == true + */ + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + if (!childAssocCopyDetails.isCopyChildren()) + { + return ChildAssocCopyAction.IGNORE; + } + if (childAssocCopyDetails.getChildAssocRef().isPrimary()) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + return ChildAssocCopyAction.COPY_ASSOC; + } + } + + /** + * Default behaviour: Copy all associated properties + * + * @return Returns all the properties passes in + */ + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + return properties; + } +} diff --git a/source/java/org/alfresco/repo/copy/DoNothingCopyBehaviourCallback.java b/source/java/org/alfresco/repo/copy/DoNothingCopyBehaviourCallback.java new file mode 100644 index 0000000000..1a8c861d25 --- /dev/null +++ b/source/java/org/alfresco/repo/copy/DoNothingCopyBehaviourCallback.java @@ -0,0 +1,82 @@ +/* + * 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.copy; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + +import org.alfresco.service.namespace.QName; + +/** + * Simple copy behaviour to prevent any copying. + *

+ * This implementation is {@link #getInstance() stateless} and therefore thread-safe. + * + * @author Derek Hulley + * @since 3.2 + */ +public class DoNothingCopyBehaviourCallback extends AbstractCopyBehaviourCallback +{ + private static CopyBehaviourCallback instance = new DoNothingCopyBehaviourCallback(); + + /** + * @return Returns a stateless singleton + */ + public static CopyBehaviourCallback getInstance() + { + return instance; + } + + /** + * @return Returns false always + */ + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + return false; + } + + /** + * @return Returns false always + */ + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + return ChildAssocCopyAction.IGNORE; + } + + /** + * @return Returns an empty map always + */ + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + return Collections.emptyMap(); + } +} diff --git a/source/java/org/alfresco/repo/forum/DiscussableAspect.java b/source/java/org/alfresco/repo/forum/DiscussableAspect.java index 95d1d89533..d694920770 100644 --- a/source/java/org/alfresco/repo/forum/DiscussableAspect.java +++ b/source/java/org/alfresco/repo/forum/DiscussableAspect.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 @@ -22,97 +22,88 @@ * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ - package org.alfresco.repo.forum; import java.io.Serializable; import java.text.SimpleDateFormat; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback; +import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; -import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.dao.ConcurrencyFailureException; -public class DiscussableAspect +/** + * Discussion-specific behaviours. + * + * @author Derek Hulley + * @since 3.2 + */ +public class DiscussableAspect implements + NodeServicePolicies.OnAddAspectPolicy, + CopyServicePolicies.OnCopyNodePolicy, + CopyServicePolicies.OnCopyCompletePolicy { + private static final String KEY_WORKING_COPIES = DiscussableAspect.class.getName() + ".WorkingCopies"; + private static final Log logger = LogFactory.getLog(DiscussableAspect.class); - /** - * Policy component - */ private PolicyComponent policyComponent; - - /** - * The node service - */ private NodeService nodeService; - - /** - * The file folder service - */ private FileFolderService fileFolderService; - /** - * Sets the policy component - * - * @param policyComponent the policy component - */ public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; } - - /** - * Set the node service - * - * @param nodeService the node service - */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } - - /** - * Set the file folder service - * - * @param fileFolderService the file folder service - */ - public void setFileFolderService(FileFolderService fileFolderService) + public final void setFileFolderService(FileFolderService fileFolderService) { this.fileFolderService = fileFolderService; } - + /** * Initialise method */ public void init() { - // Register copy behaviour for the discussable aspect + // All forum-related copy behaviour uses the same copy callback this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ForumModel.ASPECT_DISCUSSABLE, - new JavaBehaviour(this, "onCopy")); - + new JavaBehaviour(this, "onAddAspect")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ForumModel.ASPECT_DISCUSSABLE, + new JavaBehaviour(this, "getCopyCallback")); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), ForumModel.ASPECT_DISCUSSABLE, @@ -120,148 +111,267 @@ public class DiscussableAspect } /** - * onCopy policy behaviour - * - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(QName, NodeRef, StoreRef, boolean, PolicyScope) + * @return Returns {@link DiscussableAspectCopyBehaviourCallback} */ - public void onCopy( - QName sourceClassRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) { - // NOTE: we intentionally don't do anything in here, this stops the discussable - // aspect from being added to the new copied node - the behaviour we want. + return DiscussableAspectCopyBehaviourCallback.INSTANCE; } - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef destinationRef, - boolean copyNewNode, - Map copyMap) + /** + * Copy behaviour for the fm:discussable aspect. + *

+ * Only the aspect is copied (to get the default behaviour). All topics are copied later. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class DiscussableAspectCopyBehaviourCallback extends DoNothingCopyBehaviourCallback { - // if the copy is not a new node it is a checkin, we therefore - // need to copy any discussions from the working copy document - // to the document being checked in - if (copyNewNode == false) + private static final CopyBehaviourCallback INSTANCE = new DiscussableAspectCopyBehaviourCallback(); + + /** + * Copy the aspect over only if the source document has also been checked out + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) { - List sourceChildren = this.nodeService.getChildAssocs(sourceNodeRef, - ForumModel.ASSOC_DISCUSSION, RegexQNamePattern.MATCH_ALL); - - if (sourceChildren.size() != 1) + if (copyDetails.getSourceNodeAspectQNames().contains(ContentModel.ASPECT_WORKING_COPY) && + !copyDetails.isTargetNodeIsNew()) { - throw new CheckOutCheckInServiceException( - "The source node has the discussable aspect but does not have 1 child, it has " + - sourceChildren.size() + " children!"); + // We are copying back from a working copy to the original node (probably) + // We need to do a full merge of the discussions. Keep track of the nodes + // that need this behaviour and complete the copy after the copy completes. + Set nodeRefs = TransactionalResourceHelper.getSet(KEY_WORKING_COPIES); + nodeRefs.add(copyDetails.getSourceNodeRef()); } - - NodeRef sourceForum = sourceChildren.get(0).getChildRef(); - - // get the forum for the destination node, it's created if necessary - NodeRef destinationForum = getDestinationForum(destinationRef); - - // copy any topics from the source forum to the destination forum - int copied = 0; - List sourceForums = this.nodeService.getChildAssocs(sourceForum); - for (ChildAssociationRef childRef : sourceForums) - { - String topicName = null; - NodeRef childNode = childRef.getChildRef(); - if (this.nodeService.getType(childNode).equals(ForumModel.TYPE_TOPIC)) - { - try - { - // work out the name for the copied topic - String childName = this.nodeService.getProperty(childNode, - ContentModel.PROP_NAME).toString(); - Serializable labelProp = this.nodeService.getProperty(destinationRef, - ContentModel.PROP_VERSION_LABEL); - if (labelProp == null) - { - SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy-HH-mm-ss"); - topicName = childName + " - " + dateFormat.format(new Date()); - } - else - { - topicName = childName + " (" + labelProp.toString() + ")"; - } - - this.fileFolderService.copy(childNode, destinationForum, topicName); - copied++; - } - catch (FileNotFoundException fnfe) - { - throw new CheckOutCheckInServiceException( - "Failed to copy topic from working copy to checked out content", fnfe); - } - catch (FileExistsException fee) - { - throw new CheckOutCheckInServiceException("Failed to checkin content as a topic called " + - topicName + " already exists on the checked out content", fee); - } - } - } - - if (logger.isDebugEnabled()) - logger.debug("Copied " + copied + " topics from the working copy to the checked out content"); + return false; } } /** - * Retrieves or creates the forum node for the given destination node - * - * @param destNodeRef The node to get the forum for - * @return NodeRef representing the forum + * Ensure that the node has a fm:forum child node otherwise create one */ - private NodeRef getDestinationForum(NodeRef destNodeRef) + public void onAddAspect(NodeRef discussableNodeRef, QName aspectTypeQName) { - NodeRef destinationForum = null; + String name = (String)this.nodeService.getProperty( + discussableNodeRef, + ContentModel.PROP_NAME); + String forumName = I18NUtil.getMessage("discussion.discussion_for", new Object[] {name}); - if (this.nodeService.hasAspect(destNodeRef, ForumModel.ASPECT_DISCUSSABLE)) + NodeRef forumNodeRef = getForum(discussableNodeRef); + + if (forumNodeRef == null) { - List destChildren = this.nodeService.getChildAssocs(destNodeRef, - ForumModel.ASSOC_DISCUSSION, RegexQNamePattern.MATCH_ALL); + Map forumProps = new HashMap(1); + forumProps.put(ContentModel.PROP_NAME, forumName); - if (destChildren.size() != 1) - { - throw new IllegalStateException("Locked node has the discussable aspect but does not have 1 child, it has " + - destChildren.size() + " children!"); - } - - destinationForum = destChildren.get(0).getChildRef(); + ChildAssociationRef childRef = nodeService.createNode( + discussableNodeRef, + ForumModel.ASSOC_DISCUSSION, + QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"), + ForumModel.TYPE_FORUM, forumProps); + + forumNodeRef = childRef.getChildRef(); } else { - // create the forum - TODO: Move this to a repo discussion service so that it can - // be shared between here and the discussion wizard - - // add the discussable aspect - this.nodeService.addAspect(destNodeRef, ForumModel.ASPECT_DISCUSSABLE, null); - - // create a child forum space using the child association just introduced by - // adding the discussable aspect - String name = (String)this.nodeService.getProperty(destNodeRef, - ContentModel.PROP_NAME); - String forumName = I18NUtil.getMessage("coci_service.discussion_for", new Object[] {name}); - - Map forumProps = new HashMap(1); - forumProps.put(ContentModel.PROP_NAME, forumName); - - ChildAssociationRef childRef = this.nodeService.createNode(destNodeRef, - ForumModel.ASSOC_DISCUSSION, - QName.createQName(NamespaceService.FORUMS_MODEL_1_0_URI, "discussion"), - ForumModel.TYPE_FORUM, forumProps); - - destinationForum = childRef.getChildRef(); + // Just adjust the name + nodeService.setProperty(forumNodeRef, ContentModel.PROP_NAME, forumName); + } + + // apply the uifacets aspect + Map uiFacetsProps = new HashMap(5); + uiFacetsProps.put(ApplicationModel.PROP_ICON, "forum"); + this.nodeService.addAspect(forumNodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps); + + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Created forum node for discussion: \n" + + " Discussable Node: " + discussableNodeRef + "\n" + + " Forum Node: " + forumNodeRef); + } + } - // apply the uifacets aspect - Map uiFacetsProps = new HashMap(5); - uiFacetsProps.put(ApplicationModel.PROP_ICON, "forum"); - this.nodeService.addAspect(destinationForum, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps); + /** + * Retrieves the forum node the the given discussable + * + * @return Returns the fm:forum node or null + */ + private NodeRef getForum(NodeRef discussableNodeRef) + { + List destChildren = nodeService.getChildAssocs( + discussableNodeRef, + ForumModel.ASSOC_DISCUSSION, + RegexQNamePattern.MATCH_ALL); + // Take the first one + if (destChildren.size() == 0) + { + return null; + } + else + { + // We just take the first one + ChildAssociationRef discussionAssoc = destChildren.get(0); + return discussionAssoc.getChildRef(); + } + } +// +// /** +// * Copies the discussions to the new node, whilst being sensitive to any existing discussions. +// */ +// public void onCopyComplete( +// QName classQName, +// NodeRef sourceNodeRef, +// NodeRef targetNodeRef, +// boolean copyToNewNode, +// Map copyMap) +// { +// // if the copy is not a new node it is a checkin, we therefore +// // need to copy any discussions from the working copy document +// // to the document being checked in +// if (copyToNewNode) +// { +// // We don't care about new copies, just copies to existing nodes +// return; +// } +// List sourceChildren = nodeService.getChildAssocs(sourceNodeRef, +// ForumModel.ASSOC_DISCUSSION, RegexQNamePattern.MATCH_ALL); +// +// if (sourceChildren.size() != 1) +// { +// throw new CheckOutCheckInServiceException( +// "The source node has the discussable aspect but does not have 1 child, it has " + +// sourceChildren.size() + " children!"); +// } +// +// NodeRef sourceForum = sourceChildren.get(0).getChildRef(); +// +// // get the forum for the destination node, it's created if necessary +// NodeRef destinationForum = getDestinationForum(targetNodeRef); +// +// // copy any topics from the source forum to the destination forum +// int copied = 0; +// List sourceForums = nodeService.getChildAssocs(sourceForum); +// for (ChildAssociationRef childRef : sourceForums) +// { +// String topicName = null; +// NodeRef childNode = childRef.getChildRef(); +// if (nodeService.getType(childNode).equals(ForumModel.TYPE_TOPIC)) +// { +// try +// { +// // work out the name for the copied topic +// String childName = nodeService.getProperty(childNode, +// ContentModel.PROP_NAME).toString(); +// Serializable labelProp = nodeService.getProperty(targetNodeRef, +// ContentModel.PROP_VERSION_LABEL); +// if (labelProp == null) +// { +// SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy-HH-mm-ss"); +// topicName = childName + " - " + dateFormat.format(new Date()); +// } +// else +// { +// topicName = childName + " (" + labelProp.toString() + ")"; +// } +// +// fileFolderService.copy(childNode, destinationForum, topicName); +// copied++; +// } +// catch (FileNotFoundException fnfe) +// { +// throw new CheckOutCheckInServiceException( +// "Failed to copy topic from working copy to checked out content", fnfe); +// } +// catch (FileExistsException fee) +// { +// throw new CheckOutCheckInServiceException("Failed to checkin content as a topic called " + +// topicName + " already exists on the checked out content", fee); +// } +// } +// } +// +// if (logger.isDebugEnabled()) +// logger.debug("Copied " + copied + " topics from the working copy to the checked out content"); +// } +// + + public void onCopyComplete( + QName classRef, + NodeRef sourceNodeRef, + NodeRef targetNodeRef, + boolean copyToNewNode, + Map copyMap) + { + Set workingCopyNodeRefs = TransactionalResourceHelper.getSet(KEY_WORKING_COPIES); + if (!workingCopyNodeRefs.contains(sourceNodeRef)) + { + // This is not one of the nodes that needs to have discussions copied over + return; } - return destinationForum; + // First check that the source node has forums + NodeRef sourceForumNodeRef = getForum(sourceNodeRef); + if (sourceForumNodeRef == null) + { + // Missing! Clean the source node up! + nodeService.removeAspect(sourceNodeRef, ForumModel.ASPECT_DISCUSSABLE); + return; + } + + // The aspect may or may not exist on the target node + if (!nodeService.hasAspect(targetNodeRef, ForumModel.ASPECT_DISCUSSABLE)) + { + // Add the aspect + nodeService.addAspect(targetNodeRef, ForumModel.ASPECT_DISCUSSABLE, null); + } + // Get the forum node + NodeRef targetForumNodeRef = getForum(targetNodeRef); + // Merge the forum topics + List topicAssocRefs = nodeService.getChildAssocs( + sourceForumNodeRef, + Collections.singleton(ForumModel.TYPE_TOPIC)); + int copied = 0; + for (ChildAssociationRef topicAssocRef : topicAssocRefs) + { + NodeRef topicNodeRef = topicAssocRef.getChildRef(); + try + { + // work out the name for the copied topic + String topicName; + String topicNodeName = nodeService.getProperty(topicNodeRef, ContentModel.PROP_NAME).toString(); + Serializable labelProp = nodeService.getProperty(targetNodeRef, ContentModel.PROP_VERSION_LABEL); + if (labelProp == null) + { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy-HH-mm-ss"); + topicName = topicNodeName + " - " + dateFormat.format(new Date()); + } + else + { + topicName = topicNodeName + " (" + labelProp.toString() + ")"; + } + + if (fileFolderService.searchSimple(targetForumNodeRef, topicName) != null) + { + // A topic with that name already exists + continue; + } + fileFolderService.copy(topicNodeRef, targetForumNodeRef, topicName); + copied++; + } + catch (FileExistsException e) + { + // We checked for this, so this is a concurrency condition + throw new ConcurrencyFailureException("Target topic exists: " + e.getMessage(), e); + } + catch (FileNotFoundException e) + { + // The node was there, but now it's gone + throw new ConcurrencyFailureException("Forum was deleted: " + e.getMessage(), e); + } + } } } diff --git a/source/java/org/alfresco/repo/lock/LockServiceImpl.java b/source/java/org/alfresco/repo/lock/LockServiceImpl.java index 7f7593c12b..1e2f67ed28 100644 --- a/source/java/org/alfresco/repo/lock/LockServiceImpl.java +++ b/source/java/org/alfresco/repo/lock/LockServiceImpl.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -35,7 +36,10 @@ import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -194,11 +198,11 @@ public class LockServiceImpl implements LockService, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "beforeDeleteNode")); - // Register onCopy class behaviour + // Register copy class behaviour this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_LOCKABLE, - new JavaBehaviour(this, "onCopyNode")); + new JavaBehaviour(this, "getCopyCallback")); // Register the onCreateVersion behavior for the version aspect this.policyComponent.bindClassBehaviour( @@ -542,22 +546,34 @@ public class LockServiceImpl implements LockService, } /** - * OnCopy behaviour implementation for the lock aspect. - *

- * Ensures that the propety values of the lock aspect are not copied onto - * the destination node. + * @return Returns {@link LockableAspectCopyBehaviourCallback} */ - public void onCopyNode( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) { - // Add the lock aspect, but do not copy any of the properties - copyDetails.addAspect(ContentModel.ASPECT_LOCKABLE); + return LockableAspectCopyBehaviourCallback.INSTANCE; } + /** + * Extends the default copy behaviour to prevent copying of lockable properties. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class LockableAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new LockableAspectCopyBehaviourCallback(); + + /** + * @return Returns an empty map + */ + @Override + public Map getCopyProperties( + QName classQName, CopyDetails copyDetails, Map properties) + { + return Collections.emptyMap(); + } + } + /** * Ensures that node is not locked. * diff --git a/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java b/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java index 789cc9eeca..c2c9e9f4e7 100644 --- a/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java +++ b/source/java/org/alfresco/repo/model/ml/EmptyTranslationAspect.java @@ -27,7 +27,10 @@ package org.alfresco.repo.model.ml; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; @@ -65,9 +68,9 @@ public class EmptyTranslationAspect implements public void init() { this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), - ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, - new JavaBehaviour(this, "onCopyNode")); + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION, + new JavaBehaviour(this, "getCopyCallback")); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onContentUpdate"), @@ -94,8 +97,6 @@ public class EmptyTranslationAspect implements /** * Copy a cm:mlEmptyTranslation is not permit. - * - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.StoreRef, boolean, org.alfresco.repo.policy.PolicyScope) */ public void onCopyNode(QName classRef, NodeRef sourceNodeRef, StoreRef destinationStoreRef, boolean copyToNewNode, PolicyScope copyDetails) { @@ -104,8 +105,6 @@ public class EmptyTranslationAspect implements /** * If a content is added to a cm:mlEmptyTranslation, remove this aspect. - * - * @see org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy#onContentUpdate(org.alfresco.service.cmr.repository.NodeRef, boolean) */ public void onContentUpdate(NodeRef nodeRef, boolean newContent) { @@ -115,4 +114,35 @@ public class EmptyTranslationAspect implements nodeService.removeAspect(nodeRef, ContentModel.ASPECT_TEMPORARY); } } + + /** + * Extends the NO-OP copy behaviour to generate an exception if copied. In other words, + * the presence of {@link ContentModel#ASPECT_MULTILINGUAL_EMPTY_TRANSLATION} should prevent + * a node from being copied; if this is not done by the copy client, it is enforced here. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class EmptyTranslationAspectCopyBehaviourCallback extends DoNothingCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new EmptyTranslationAspectCopyBehaviourCallback(); + + /** + * @throws IllegalStateException always + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + throw new IllegalStateException( + "Nodes with " + ContentModel.ASPECT_MULTILINGUAL_EMPTY_TRANSLATION + " may not be copied"); + } + } + + /** + * @return Returns {@link EmptyTranslationAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return EmptyTranslationAspectCopyBehaviourCallback.INSTANCE; + } } diff --git a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java index bba53907e7..2f30a892ef 100644 --- a/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java +++ b/source/java/org/alfresco/repo/model/ml/MultilingualDocumentAspect.java @@ -29,15 +29,16 @@ import java.util.Locale; import java.util.Map; import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.ml.MultilingualContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -71,9 +72,9 @@ public class MultilingualDocumentAspect implements public void init() { this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_MULTILINGUAL_DOCUMENT, - new JavaBehaviour(this, "onCopyNode")); + new JavaBehaviour(this, "getCopyCallback")); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), @@ -114,12 +115,12 @@ public class MultilingualDocumentAspect implements /** * The copy of a cm:mlDocument can't keep the Multilingual aspect. - * - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.StoreRef, boolean, org.alfresco.repo.policy.PolicyScope) + * + * @return Returns the {@link DoNothingCopyBehaviourCallback} */ - public void onCopyNode(QName classRef, NodeRef sourceNodeRef, StoreRef destinationStoreRef, boolean copyToNewNode, PolicyScope copyDetails) + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) { - copyDetails.removeAspect(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); + return DoNothingCopyBehaviourCallback.getInstance(); } /** @@ -144,8 +145,6 @@ public class MultilingualDocumentAspect implements * * If the locale of a pivot translation is modified, the pivot locale reference of the mlContainer * must be modified too. - * - * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) */ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) { diff --git a/source/java/org/alfresco/repo/node/ReferenceableAspect.java b/source/java/org/alfresco/repo/node/ReferenceableAspect.java index 11bd3564aa..16fe82e166 100644 --- a/source/java/org/alfresco/repo/node/ReferenceableAspect.java +++ b/source/java/org/alfresco/repo/node/ReferenceableAspect.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,12 +25,12 @@ package org.alfresco.repo.node; import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -63,21 +63,16 @@ public class ReferenceableAspect implements CopyServicePolicies.OnCopyNodePolicy { // disable copy for referencable aspect this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_REFERENCEABLE, - new JavaBehaviour(this, "onCopyNode")); + new JavaBehaviour(this, "getCopyCallback")); } /** - * Does nothing + * @return Returns {@link DoNothingCopyBehaviourCallback} */ - public void onCopyNode( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) { - // don't copy + return DoNothingCopyBehaviourCallback.getInstance(); } } diff --git a/source/java/org/alfresco/repo/node/TemporaryAspect.java b/source/java/org/alfresco/repo/node/TemporaryAspect.java index c2a57b51a7..3135440129 100644 --- a/source/java/org/alfresco/repo/node/TemporaryAspect.java +++ b/source/java/org/alfresco/repo/node/TemporaryAspect.java @@ -25,7 +25,11 @@ package org.alfresco.repo.node; import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; @@ -63,9 +67,25 @@ public class TemporaryAspect implements CopyServicePolicies.OnCopyNodePolicy { // disable copy for referencable aspect this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_TEMPORARY, - new JavaBehaviour(this, "onCopyNode")); + new JavaBehaviour(this, "getCopyCallback")); + } + + /** + * The {@link ContentModel#ASPECT_TEMPORARY sys:temporary} aspect is only copied + * if the copy is clean i.e. not to an existing node. + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + if (copyDetails.getTargetNodeRef() != null) + { + return DoNothingCopyBehaviourCallback.getInstance(); + } + else + { + return DefaultCopyBehaviourCallback.getInstance(); + } } /** diff --git a/source/java/org/alfresco/repo/policy/BehaviourFilter.java b/source/java/org/alfresco/repo/policy/BehaviourFilter.java index ee21251655..ea85ad97ee 100644 --- a/source/java/org/alfresco/repo/policy/BehaviourFilter.java +++ b/source/java/org/alfresco/repo/policy/BehaviourFilter.java @@ -35,7 +35,9 @@ import org.alfresco.service.namespace.QName; public interface BehaviourFilter { /** - * Disable behaviour for all nodes + * Disable behaviour for all nodes. + *

+ * The change applies ONLY to the current trasaction. * * @param className the type/aspect behaviour to disable * @return true => already disabled @@ -44,6 +46,8 @@ public interface BehaviourFilter /** * Disable behaviour for specific node + *

+ * The change applies ONLY to the current trasaction. * * @param nodeRef the node to disable for * @param className the type/aspect behaviour to disable @@ -53,6 +57,8 @@ public interface BehaviourFilter /** * Enable behaviour for all nodes + *

+ * The change applies ONLY to the current trasaction. * * @param className the type/aspect behaviour to enable */ @@ -60,6 +66,8 @@ public interface BehaviourFilter /** * Enable behaviour for specific node + *

+ * The change applies ONLY to the current trasaction. * * @param nodeRef the node to enable for * @param className the type/aspect behaviour to enable @@ -68,18 +76,25 @@ public interface BehaviourFilter /** * Enable all behaviours for specific node + *

+ * The change applies ONLY to the current trasaction. * * @param nodeRef the node to enable for */ public void enableBehaviours(NodeRef nodeRef); /** - * Enable all behaviours + * Enable all behaviours i.e. undo all disable calls - both at the + * node and class level. + *

+ * The change applies ONLY to the current trasaction. */ public void enableAllBehaviours(); /** * Determine if behaviour is enabled across all nodes. + *

+ * The change applies ONLY to the current trasaction. * * @param className the behaviour to test for * @return true => behaviour is enabled @@ -88,10 +103,12 @@ public interface BehaviourFilter /** * Determine if behaviour is enabled for specific node. - * + *

* Note: A node behaviour is enabled only when: * a) the behaviour is not disabled across all nodes * b) the behaviour is not disabled specifically for the provided node + *

+ * The change applies ONLY to the current trasaction. * * @param nodeRef the node to test for * @param className the behaviour to test for @@ -101,6 +118,8 @@ public interface BehaviourFilter /** * Determine if any behaviours have been disabled? + *

+ * The change applies ONLY to the current trasaction. * * @return true => behaviours have been filtered */ diff --git a/source/java/org/alfresco/repo/policy/BehaviourFilterImpl.java b/source/java/org/alfresco/repo/policy/BehaviourFilterImpl.java index 908e896de3..5121591c75 100644 --- a/source/java/org/alfresco/repo/policy/BehaviourFilterImpl.java +++ b/source/java/org/alfresco/repo/policy/BehaviourFilterImpl.java @@ -25,25 +25,26 @@ package org.alfresco.repo.policy; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; /** - * Implementation of Behaviour Filter. + * Implementation of Behaviour Filter. All methods operate on transactionally-bound + * resources. Behaviour will therefore never span transactions; the filter state has + * the same lifespan as the transaction in which it was created. * * @author David Caruana */ public class BehaviourFilterImpl implements BehaviourFilter { - // Thread local storage of filters - ThreadLocal> classFilter = new ThreadLocal>(); - ThreadLocal>> nodeRefFilter = new ThreadLocal>>(); + private static final String KEY_CLASS_FILTER = "BehaviourFilterImpl.classFilter"; + private static final String KEY_NODEREF_FILTER = "BehaviourFilterImpl.nodeRefFilter"; // Dictionary Service private DictionaryService dictionaryService; @@ -67,43 +68,27 @@ public class BehaviourFilterImpl implements BehaviourFilter this.tenantService = tenantService; } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#disableBehaviour(org.alfresco.service.namespace.QName) - */ public boolean disableBehaviour(QName className) { - List classNames = classFilter.get(); - if (classNames == null) - { - classNames = new ArrayList(); - classFilter.set(classNames); - } - boolean alreadyDisabled = classNames.contains(className); + List classFilters = TransactionalResourceHelper.getList(KEY_CLASS_FILTER); + boolean alreadyDisabled = classFilters.contains(className); if (!alreadyDisabled) { - classNames.add(className); + classFilters.add(className); } return alreadyDisabled; } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#disableBehaviour(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) - */ public boolean disableBehaviour(NodeRef nodeRef, QName className) { nodeRef = tenantService.getName(nodeRef); - Map> filters = nodeRefFilter.get(); - if (filters == null) - { - filters = new HashMap>(); - nodeRefFilter.set(filters); - } - List classNames = filters.get(nodeRef); + Map> nodeRefFilters = TransactionalResourceHelper.getMap(KEY_NODEREF_FILTER); + List classNames = nodeRefFilters.get(nodeRef); if (classNames == null) { classNames = new ArrayList(); - filters.put(nodeRef, classNames); + nodeRefFilters.put(nodeRef, classNames); } boolean alreadyDisabled = classNames.contains(className); if (!alreadyDisabled) @@ -113,69 +98,42 @@ public class BehaviourFilterImpl implements BehaviourFilter return alreadyDisabled; } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#enableBehaviour(org.alfresco.service.namespace.QName) - */ public void enableBehaviour(QName className) { - List classNames = classFilter.get(); - if (classNames != null) - { - classNames.remove(className); - } + List classFilters = TransactionalResourceHelper.getList(KEY_CLASS_FILTER); + classFilters.remove(className); } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#enableBehaviour(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) - */ public void enableBehaviour(NodeRef nodeRef, QName className) { nodeRef = tenantService.getName(nodeRef); - Map> filters = nodeRefFilter.get(); - if (filters != null) + Map> nodeRefFilters = TransactionalResourceHelper.getMap(KEY_NODEREF_FILTER); + List classNames = nodeRefFilters.get(nodeRef); + if (classNames != null) { - List classNames = filters.get(nodeRef); - if (classNames != null) - { - classNames.remove(className); - } + classNames.remove(className); if (classNames.size() == 0) { - filters.remove(nodeRef); + nodeRefFilters.remove(nodeRef); } } } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#enableBehaviours(org.alfresco.service.cmr.repository.NodeRef) - */ public void enableBehaviours(NodeRef nodeRef) { nodeRef = tenantService.getName(nodeRef); - Map> filters = nodeRefFilter.get(); - if (filters != null) - { - filters.remove(nodeRef); - } + Map> nodeRefFilters = TransactionalResourceHelper.getMap(KEY_NODEREF_FILTER); + nodeRefFilters.remove(nodeRef); } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#enableAllBehaviours() - */ public void enableAllBehaviours() { - Map> filters = nodeRefFilter.get(); - if (filters != null) - { - filters.clear(); - } + Map> filters = TransactionalResourceHelper.getMap(KEY_NODEREF_FILTER); + filters.clear(); } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#isEnabled(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) - */ public boolean isEnabled(NodeRef nodeRef, QName className) { // check global filters @@ -187,46 +145,16 @@ public class BehaviourFilterImpl implements BehaviourFilter nodeRef = tenantService.getName(nodeRef); // check node level filters - Map> nodeFilters = nodeRefFilter.get(); - if (nodeFilters != null) + Map> filters = TransactionalResourceHelper.getMap(KEY_NODEREF_FILTER); + List nodeClassFilters = filters.get(nodeRef); + if (nodeClassFilters != null) { - List nodeClassFilters = nodeFilters.get(nodeRef); - if (nodeClassFilters != null) - { - boolean filtered = nodeClassFilters.contains(className); - if (filtered) - { - return false; - } - for (QName filterName : nodeClassFilters) - { - filtered = dictionaryService.isSubClass(className, filterName); - if (filtered) - { - return false; - } - } - } - } - - return true; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#isEnabled(org.alfresco.service.namespace.QName) - */ - public boolean isEnabled(QName className) - { - // check global class filters - List classFilters = classFilter.get(); - if (classFilters != null) - { - boolean filtered = classFilters.contains(className); + boolean filtered = nodeClassFilters.contains(className); if (filtered) { return false; } - for (QName filterName : classFilters) + for (QName filterName : nodeClassFilters) { filtered = dictionaryService.isSubClass(className, filterName); if (filtered) @@ -235,18 +163,35 @@ public class BehaviourFilterImpl implements BehaviourFilter } } } + + return true; + } + + public boolean isEnabled(QName className) + { + // check global class filters + List classFilters = TransactionalResourceHelper.getList(KEY_CLASS_FILTER); + boolean filtered = classFilters.contains(className); + if (filtered) + { + return false; + } + for (QName classFilter : classFilters) + { + filtered = dictionaryService.isSubClass(className, classFilter); + if (filtered) + { + return false; + } + } return true; } - /* (non-Javadoc) - * @see org.alfresco.repo.policy.BehaviourFilter#isActivated() - */ public boolean isActivated() { - List classFilters = classFilter.get(); - Map> nodeFilters = nodeRefFilter.get(); - return (classFilters != null && !classFilters.isEmpty()) || (nodeFilters != null && !nodeFilters.isEmpty()); + List classFilters = TransactionalResourceHelper.getList(KEY_CLASS_FILTER); + Map> nodeRefFilters = TransactionalResourceHelper.getMap(KEY_NODEREF_FILTER); + return (!classFilters.isEmpty()) || (!nodeRefFilters.isEmpty()); } - } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 8fa8db6511..ddfa35ce3c 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -126,15 +126,15 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ private Set disabledRules = new HashSet(5); - /** - * All the rule type currently registered - */ - private Map ruleTypes = new HashMap(); + /** + * All the rule type currently registered + */ + private Map ruleTypes = new HashMap(); - /** - * The rule transaction listener - */ - private TransactionListener ruleTransactionListener = new RuleTransactionListener(this); + /** + * The rule transaction listener + */ + private TransactionListener ruleTransactionListener = new RuleTransactionListener(this); /** * Indicates whether the rules are disabled for the current thread @@ -172,9 +172,9 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService * @param actionService the action service */ public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } + { + this.actionService = actionService; + } /** * Set the runtime action service @@ -199,11 +199,11 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService /** * Set the permission service * - * @param permissionService the permission service + * @param permissionService the permission service */ public void setPermissionService(PermissionService permissionService) { - this.permissionService = permissionService; + this.permissionService = permissionService; } /** @@ -215,39 +215,39 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService { this.globalRulesDisabled = rulesDisabled; } - - /** - * Gets the saved rule folder reference - * - * @param nodeRef the node reference - * @return the node reference - */ - private NodeRef getSavedRuleFolderRef(NodeRef nodeRef) - { + + /** + * Gets the saved rule folder reference + * + * @param nodeRef the node reference + * @return the node reference + */ + private NodeRef getSavedRuleFolderRef(NodeRef nodeRef) + { NodeRef result = null; - List assocs = this.runtimeNodeService.getChildAssocs( + List assocs = this.runtimeNodeService.getChildAssocs( nodeRef, - RegexQNamePattern.MATCH_ALL, + RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER); - if (assocs.size() > 1) - { - throw new ActionServiceException("There is more than one rule folder, which is invalid."); - } + if (assocs.size() > 1) + { + throw new ActionServiceException("There is more than one rule folder, which is invalid."); + } else if (assocs.size() == 1) { result = assocs.get(0).getChildRef(); } - - return result; - } + + return result; + } /** * @see org.alfresco.repo.rule.RuleService#getRuleTypes() */ public List getRuleTypes() { - return new ArrayList(this.ruleTypes.values()); + return new ArrayList(this.ruleTypes.values()); } /** @@ -329,7 +329,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public boolean hasRules(NodeRef nodeRef) { - return getRules(nodeRef).size() != 0; + return getRules(nodeRef).size() != 0; } /** @@ -337,7 +337,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public List getRules(NodeRef nodeRef) { - return getRules(nodeRef, true, null); + return getRules(nodeRef, true, null); } /** @@ -345,7 +345,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public List getRules(NodeRef nodeRef, boolean includeInherited) { - return getRules(nodeRef, includeInherited, null); + return getRules(nodeRef, includeInherited, null); } /** @@ -395,11 +395,11 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService // Build the list of rules that is returned to the client for (Rule rule : allRules) - { + { if ((rules.contains(rule) == false) && (ruleTypeName == null || rule.getRuleTypes().contains(ruleTypeName) == true)) { - rules.add(rule); + rules.add(rule); } } } @@ -461,44 +461,44 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService return result; } - + /** * Gets the inherited rules for a given node reference * - * @param nodeRef the nodeRef - * @param ruleTypeName the rule type (null if all applicable) - * @return a list of inherited rules (empty if none) + * @param nodeRef the nodeRef + * @param ruleTypeName the rule type (null if all applicable) + * @return a list of inherited rules (empty if none) */ - private List getInheritedRules(NodeRef nodeRef, String ruleTypeName, Set visitedNodeRefs) - { - List inheritedRules = new ArrayList(); + private List getInheritedRules(NodeRef nodeRef, String ruleTypeName, Set visitedNodeRefs) + { + List inheritedRules = new ArrayList(); if (this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) - { - // Create the visited nodes set if it has not already been created - if (visitedNodeRefs == null) - { - visitedNodeRefs = new HashSet(); - } - - // This check prevents stack over flow when we have a cyclic node graph - if (visitedNodeRefs.contains(nodeRef) == false) - { - visitedNodeRefs.add(nodeRef); - - List allInheritedRules = new ArrayList(); - List parents = this.runtimeNodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef parent : parents) - { + { + // Create the visited nodes set if it has not already been created + if (visitedNodeRefs == null) + { + visitedNodeRefs = new HashSet(); + } + + // This check prevents stack over flow when we have a cyclic node graph + if (visitedNodeRefs.contains(nodeRef) == false) + { + visitedNodeRefs.add(nodeRef); + + List allInheritedRules = new ArrayList(); + List parents = this.runtimeNodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef parent : parents) + { // Add the inherited rule first - for (Rule rule : getInheritedRules(parent.getParentRef(), ruleTypeName, visitedNodeRefs)) - { - // Ensure that we don't get any rule duplication (don't use a set cos we want to preserve order) - if (allInheritedRules.contains(rule) == false) - { - allInheritedRules.add(rule); - } - } + for (Rule rule : getInheritedRules(parent.getParentRef(), ruleTypeName, visitedNodeRefs)) + { + // Ensure that we don't get any rule duplication (don't use a set cos we want to preserve order) + if (allInheritedRules.contains(rule) == false) + { + allInheritedRules.add(rule); + } + } List rules = getRules(parent.getParentRef(), false); for (Rule rule : rules) @@ -509,43 +509,43 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService allInheritedRules.add(rule); } } - } - - if (ruleTypeName == null) - { - inheritedRules = allInheritedRules; - } - else - { - // Filter the rule list by rule type - for (Rule rule : allInheritedRules) - { - if (rule.getRuleTypes().contains(ruleTypeName) == true) - { - inheritedRules.add(rule); - } - } - } - } + } + + if (ruleTypeName == null) + { + inheritedRules = allInheritedRules; + } + else + { + // Filter the rule list by rule type + for (Rule rule : allInheritedRules) + { + if (rule.getRuleTypes().contains(ruleTypeName) == true) + { + inheritedRules.add(rule); + } + } + } + } } - - return inheritedRules; - } + + return inheritedRules; + } - /** - * Create the rule object from the rule node reference - * - * @param ruleNodeRef the rule node reference - * @return the rule - */ + /** + * Create the rule object from the rule node reference + * + * @param ruleNodeRef the rule node reference + * @return the rule + */ @SuppressWarnings("unchecked") - public Rule getRule(NodeRef ruleNodeRef) - { - // Get the rule properties - Map props = this.runtimeNodeService.getProperties(ruleNodeRef); - - // Create the rule - Rule rule = new Rule(ruleNodeRef); + public Rule getRule(NodeRef ruleNodeRef) + { + // Get the rule properties + Map props = this.runtimeNodeService.getProperties(ruleNodeRef); + + // Create the rule + Rule rule = new Rule(ruleNodeRef); // Set the title and description String title = DefaultTypeConverter.INSTANCE.convert(String.class, props.get(ContentModel.PROP_TITLE)); @@ -556,14 +556,14 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService // Set the rule types rule.setRuleTypes((List)props.get(RuleModel.PROP_RULE_TYPE)); - // Set the applied to children value + // Set the applied to children value boolean isAppliedToChildren = false; Boolean value = (Boolean)props.get(RuleModel.PROP_APPLY_TO_CHILDREN); if (value != null) { isAppliedToChildren = value.booleanValue(); } - rule.applyToChildren(isAppliedToChildren); + rule.applyToChildren(isAppliedToChildren); // Set the execute asynchronously value boolean executeAsync = false; @@ -582,8 +582,8 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService ruleDisabled = value3.booleanValue(); } rule.setRuleDisabled(ruleDisabled); - - // Get the action node reference + + // Get the action node reference List actions = this.nodeService.getChildAssocs(ruleNodeRef, RuleModel.ASSOC_ACTION, RuleModel.ASSOC_ACTION); if (actions.size() == 0) { @@ -594,69 +594,69 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService throw new RuleServiceException("Rule exists with more than one specified action"); } NodeRef actionNodeRef = actions.get(0).getChildRef(); - + // Here we need to create the action from the action node reference Action action = runtimeActionService.createAction(actionNodeRef); rule.setAction(action); - return rule; - } + return rule; + } /** * @see org.alfresco.repo.rule.RuleService#saveRule(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.rule.Rule) */ public void saveRule(NodeRef nodeRef, Rule rule) { - if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) - { - disableRules(); - try - { - if (this.nodeService.exists(nodeRef) == false) - { - throw new RuleServiceException("The node does not exist."); - } - - NodeRef ruleNodeRef = rule.getNodeRef(); - if (ruleNodeRef == null) - { - if (this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == false) - { - // Add the actionable aspect - this.nodeService.addAspect(nodeRef, RuleModel.ASPECT_RULES, null); - } - - // Create the action node - ruleNodeRef = this.nodeService.createNode( - getSavedRuleFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()), - RuleModel.TYPE_RULE).getChildRef(); - - // Set the rule node reference and the owning node reference - rule.setNodeRef(ruleNodeRef); - } - - // Update the properties of the rule - this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_TITLE, rule.getTitle()); - this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_DESCRIPTION, rule.getDescription()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_RULE_TYPE, (Serializable)rule.getRuleTypes()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_APPLY_TO_CHILDREN, rule.isAppliedToChildren()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_EXECUTE_ASYNC, rule.getExecuteAsynchronously()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_DISABLED, rule.getRuleDisabled()); - - // Save the rule's action - saveAction(ruleNodeRef, rule); - } - finally - { - enableRules(); - } - } - else - { - throw new RuleServiceException("Insufficient permissions to save a rule."); - } + if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) + { + disableRules(); + try + { + if (this.nodeService.exists(nodeRef) == false) + { + throw new RuleServiceException("The node does not exist."); + } + + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef == null) + { + if (this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == false) + { + // Add the actionable aspect + this.nodeService.addAspect(nodeRef, RuleModel.ASPECT_RULES, null); + } + + // Create the action node + ruleNodeRef = this.nodeService.createNode( + getSavedRuleFolderRef(nodeRef), + ContentModel.ASSOC_CONTAINS, + QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()), + RuleModel.TYPE_RULE).getChildRef(); + + // Set the rule node reference and the owning node reference + rule.setNodeRef(ruleNodeRef); + } + + // Update the properties of the rule + this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_TITLE, rule.getTitle()); + this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_DESCRIPTION, rule.getDescription()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_RULE_TYPE, (Serializable)rule.getRuleTypes()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_APPLY_TO_CHILDREN, rule.isAppliedToChildren()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_EXECUTE_ASYNC, rule.getExecuteAsynchronously()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_DISABLED, rule.getRuleDisabled()); + + // Save the rule's action + saveAction(ruleNodeRef, rule); + } + finally + { + enableRules(); + } + } + else + { + throw new RuleServiceException("Insufficient permissions to save a rule."); + } } /** @@ -709,61 +709,61 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public void removeRule(NodeRef nodeRef, Rule rule) { - if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) - { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) - { - disableRules(nodeRef); - try - { - NodeRef ruleNodeRef = rule.getNodeRef(); - if (ruleNodeRef != null) - { - this.nodeService.removeChild(getSavedRuleFolderRef(nodeRef), ruleNodeRef); - } - } - finally - { - enableRules(nodeRef); - } - } - } - else - { - throw new RuleServiceException("Insufficient permissions to remove a rule."); - } - } + if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) + { + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) + { + disableRules(nodeRef); + try + { + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef != null) + { + this.nodeService.removeChild(getSavedRuleFolderRef(nodeRef), ruleNodeRef); + } + } + finally + { + enableRules(nodeRef); + } + } + } + else + { + throw new RuleServiceException("Insufficient permissions to remove a rule."); + } + } /** * @see org.alfresco.repo.rule.RuleService#removeAllRules(NodeRef) */ public void removeAllRules(NodeRef nodeRef) { - if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) - { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) - { - NodeRef folder = getSavedRuleFolderRef(nodeRef); - if (folder != null) - { - List ruleChildAssocs = this.nodeService.getChildAssocs( - folder, - RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX); - for (ChildAssociationRef ruleChildAssoc : ruleChildAssocs) - { - this.nodeService.removeChild(folder, ruleChildAssoc.getChildRef()); - } - } - } - } - else - { - throw new RuleServiceException("Insufficient permissions to remove a rule."); - } + if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) + { + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) + { + NodeRef folder = getSavedRuleFolderRef(nodeRef); + if (folder != null) + { + List ruleChildAssocs = this.nodeService.getChildAssocs( + folder, + RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX); + for (ChildAssociationRef ruleChildAssoc : ruleChildAssocs) + { + this.nodeService.removeChild(folder, ruleChildAssoc.getChildRef()); + } + } + } + } + else + { + throw new RuleServiceException("Insufficient permissions to remove a rule."); + } } - + /** * @see org.alfresco.repo.rule.RuntimeRuleService#addRulePendingExecution(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rule.Rule) */ @@ -776,9 +776,9 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService /** * @see org.alfresco.repo.rule.RuntimeRuleService#addRulePendingExecution(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rule.Rule, boolean) */ - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") public void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule, boolean executeAtEnd) - { + { ParameterCheck.mandatory("actionableNodeRef", actionableNodeRef); ParameterCheck.mandatory("actionedUponNodeRef", actionedUponNodeRef); @@ -787,14 +787,14 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService this.disabledNodeRefs.contains(this.getOwningNodeRef(rule)) == false && this.disabledRules.contains(rule) == false) { - PendingRuleData pendingRuleData = new PendingRuleData(actionableNodeRef, actionedUponNodeRef, rule, executeAtEnd); + PendingRuleData pendingRuleData = new PendingRuleData(actionableNodeRef, actionedUponNodeRef, rule, executeAtEnd); List pendingRules = (List) AlfrescoTransactionSupport.getResource(KEY_RULES_PENDING); - if (pendingRules == null) - { + if (pendingRules == null) + { // bind pending rules to the current transaction - pendingRules = new ArrayList(); + pendingRules = new ArrayList(); AlfrescoTransactionSupport.bindResource(KEY_RULES_PENDING, pendingRules); // bind the rule transaction listener AlfrescoTransactionSupport.bindListener(this.ruleTransactionListener); @@ -803,8 +803,8 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService { logger.debug("Rule '" + rule.getTitle() + "' has been added pending execution to action upon node '" + actionedUponNodeRef.getId() + "'"); } - } - + } + // Prevent the same rule being executed more than once in the same transaction if (pendingRules.contains(pendingRuleData) == false) { @@ -818,23 +818,23 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService logger.debug("The rule '" + rule.getTitle() + "' or the node '" + this.getOwningNodeRef(rule).getId() + "' has been disabled."); } } - } - - - - /** - * @see org.alfresco.repo.rule.RuleService#executePendingRules() - */ - public void executePendingRules() - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Creating the executed rules list"); - } - if (AlfrescoTransactionSupport.getResource(KEY_RULES_EXECUTED) == null) - { - AlfrescoTransactionSupport.bindResource(KEY_RULES_EXECUTED, new HashSet()); - } + } + + + + /** + * @see org.alfresco.repo.rule.RuleService#executePendingRules() + */ + public void executePendingRules() + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Creating the executed rules list"); + } + if (AlfrescoTransactionSupport.getResource(KEY_RULES_EXECUTED) == null) + { + AlfrescoTransactionSupport.bindResource(KEY_RULES_EXECUTED, new HashSet()); + } List executeAtEndRules = new ArrayList(); executePendingRulesImpl(executeAtEndRules); @@ -842,7 +842,7 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService { executePendingRule(data); } - } + } /** * Executes the pending rules, iterating until all pending rules have been executed @@ -876,26 +876,26 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService executePendingRulesImpl(executeAtEndRules); } } - - /** - * Executes a pending rule - * - * @param pendingRule the pending rule data object - */ - @SuppressWarnings("unchecked") + + /** + * Executes a pending rule + * + * @param pendingRule the pending rule data object + */ + @SuppressWarnings("unchecked") private void executePendingRule(PendingRuleData pendingRule) - { - Set executedRules = + { + Set executedRules = (Set) AlfrescoTransactionSupport.getResource(KEY_RULES_EXECUTED); - NodeRef actionedUponNodeRef = pendingRule.getActionedUponNodeRef(); - Rule rule = pendingRule.getRule(); - - if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) - { + NodeRef actionedUponNodeRef = pendingRule.getActionedUponNodeRef(); + Rule rule = pendingRule.getRule(); + + if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) + { executeRule(rule, actionedUponNodeRef, executedRules); - } - } + } + } /** * @see org.alfresco.repo.rule.RuntimeRuleService#executeRule(org.alfresco.service.cmr.rule.Rule, org.alfresco.service.cmr.repository.NodeRef, java.util.Set) @@ -928,111 +928,111 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService this.actionService.executeAction(action, actionedUponNodeRef, true, executeAsync); } } - - /** - * Determines whether the rule can be executed - * - * @param executedRules - * @param actionedUponNodeRef - * @param rule - * @return - */ - private boolean canExecuteRule(Set executedRules, NodeRef actionedUponNodeRef, Rule rule) - { - boolean result = true; - - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Current executed items count = " + executedRules.size()); - } - - if (executedRules != null) - { - if (executedRules.contains(new ExecutedRuleData(actionedUponNodeRef, rule)) == true) - { - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Already executed this rule (" + rule.getTitle()+ ") on this nodeRef (" + actionedUponNodeRef.getId() + ")"); - } - result = false; - } - else - { - result = checkForCopy(executedRules, actionedUponNodeRef, rule); - } - } - else - { - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Executed this rule (" + rule.getTitle()+ ") on (" + actionedUponNodeRef.getId() + ") executed rule is null"); - } - } - - return result; - } - - /** - * Checks to see if a copy exists in the executed rules list - * - * @param executedRules - * @param actionedUponNodeRef - * @param rule - * @return - */ - private boolean checkForCopy(Set executedRules, NodeRef actionedUponNodeRef, Rule rule) - { - boolean result = true; - if (this.nodeService.exists(actionedUponNodeRef) == true && this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_COPIEDFROM) == true) - { - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Has the copied from aspect (" + actionedUponNodeRef.getId() + ")"); - } - NodeRef copiedFrom = (NodeRef)this.nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_COPY_REFERENCE); - - if (logger.isDebugEnabled() == true && copiedFrom != null) {logger.debug(" >> Got the copedFrom nodeRef (" + copiedFrom.getId() + ")");}; - - if (copiedFrom != null) - { - if (executedRules.contains(new ExecutedRuleData(copiedFrom, rule)) == true) - { - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Already executed this rule (" + rule.getTitle()+ ") on this the copied from nodeRef (" + copiedFrom.getId() + ")"); - } - return false; - } - else - { - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Executed this rule (" + rule.getTitle()+ ") on (" + actionedUponNodeRef.getId() + ") copiedFrom is not is list"); - logger.debug(" > Checking copy"); - } - result = checkForCopy(executedRules, copiedFrom, rule); - } - } - } - else - { - if (logger.isDebugEnabled() == true) - { - logger.debug(" >> Executed this rule (" + rule.getTitle()+ ") on (" + actionedUponNodeRef.getId() + ") no copied from aspect"); - } - } - return result; - } - /** - * Register the rule type - * - * @param ruleTypeAdapter the rule type adapter - */ - public void registerRuleType(RuleType ruleType) - { - this.ruleTypes.put(ruleType.getName(), ruleType); - } + /** + * Determines whether the rule can be executed + * + * @param executedRules + * @param actionedUponNodeRef + * @param rule + * @return + */ + private boolean canExecuteRule(Set executedRules, NodeRef actionedUponNodeRef, Rule rule) + { + boolean result = true; + + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Current executed items count = " + executedRules.size()); + } + + if (executedRules != null) + { + if (executedRules.contains(new ExecutedRuleData(actionedUponNodeRef, rule)) == true) + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Already executed this rule (" + rule.getTitle()+ ") on this nodeRef (" + actionedUponNodeRef.getId() + ")"); + } + result = false; + } + else + { + result = checkForCopy(executedRules, actionedUponNodeRef, rule); + } + } + else + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Executed this rule (" + rule.getTitle()+ ") on (" + actionedUponNodeRef.getId() + ") executed rule is null"); + } + } + + return result; + } + + /** + * Checks to see if a copy exists in the executed rules list + * + * @param executedRules + * @param actionedUponNodeRef + * @param rule + * @return + */ + private boolean checkForCopy(Set executedRules, NodeRef actionedUponNodeRef, Rule rule) + { + boolean result = true; + if (this.nodeService.exists(actionedUponNodeRef) == true && this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_COPIEDFROM) == true) + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Has the copied from aspect (" + actionedUponNodeRef.getId() + ")"); + } + NodeRef copiedFrom = (NodeRef)this.nodeService.getProperty(actionedUponNodeRef, ContentModel.PROP_COPY_REFERENCE); + + if (logger.isDebugEnabled() == true && copiedFrom != null) {logger.debug(" >> Got the copedFrom nodeRef (" + copiedFrom.getId() + ")");}; + + if (copiedFrom != null) + { + if (executedRules.contains(new ExecutedRuleData(copiedFrom, rule)) == true) + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Already executed this rule (" + rule.getTitle()+ ") on this the copied from nodeRef (" + copiedFrom.getId() + ")"); + } + return false; + } + else + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Executed this rule (" + rule.getTitle()+ ") on (" + actionedUponNodeRef.getId() + ") copiedFrom is not is list"); + logger.debug(" > Checking copy"); + } + result = checkForCopy(executedRules, copiedFrom, rule); + } + } + } + else + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" >> Executed this rule (" + rule.getTitle()+ ") on (" + actionedUponNodeRef.getId() + ") no copied from aspect"); + } + } + return result; + } + + /** + * Register the rule type + * + * @param ruleTypeAdapter the rule type adapter + */ + public void registerRuleType(RuleType ruleType) + { + this.ruleTypes.put(ruleType.getName(), ruleType); + } /** * Helper class to contain the information about a rule that is executed @@ -1053,12 +1053,12 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService public NodeRef getActionableNodeRef() { - return actionableNodeRef; + return actionableNodeRef; } public Rule getRule() { - return rule; + return rule; } @Override @@ -1089,61 +1089,61 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService } } - /** - * Helper class to contain the information about a rule that is pending execution - * - * @author Roy Wetherall - */ - private class PendingRuleData extends ExecutedRuleData - { - private NodeRef actionedUponNodeRef; + /** + * Helper class to contain the information about a rule that is pending execution + * + * @author Roy Wetherall + */ + private class PendingRuleData extends ExecutedRuleData + { + private NodeRef actionedUponNodeRef; private boolean executeAtEnd = false; - + public PendingRuleData(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule, boolean executeAtEnd) { super(actionableNodeRef, rule); this.actionedUponNodeRef = actionedUponNodeRef; this.executeAtEnd = executeAtEnd; } - - public NodeRef getActionedUponNodeRef() - { - return actionedUponNodeRef; - } + + public NodeRef getActionedUponNodeRef() + { + return actionedUponNodeRef; + } public boolean getExecuteAtEnd() { return this.executeAtEnd; } - - @Override - public int hashCode() - { - int i = super.hashCode(); - i = (i*37) + actionedUponNodeRef.hashCode(); - return i; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj instanceof PendingRuleData) - { - PendingRuleData that = (PendingRuleData) obj; - return (this.actionableNodeRef.equals(that.actionableNodeRef) && - this.actionedUponNodeRef.equals(that.actionedUponNodeRef) && - this.rule.equals(that.rule)); - } - else - { - return false; - } - } - } + + @Override + public int hashCode() + { + int i = super.hashCode(); + i = (i*37) + actionedUponNodeRef.hashCode(); + return i; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj instanceof PendingRuleData) + { + PendingRuleData that = (PendingRuleData) obj; + return (this.actionableNodeRef.equals(that.actionableNodeRef) && + this.actionedUponNodeRef.equals(that.actionedUponNodeRef) && + this.rule.equals(that.rule)); + } + else + { + return false; + } + } + } /** * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.rule.Rule) diff --git a/source/java/org/alfresco/repo/rule/RulesAspect.java b/source/java/org/alfresco/repo/rule/RulesAspect.java index 5f1934c5fb..3eac5d6849 100644 --- a/source/java/org/alfresco/repo/rule/RulesAspect.java +++ b/source/java/org/alfresco/repo/rule/RulesAspect.java @@ -24,149 +24,201 @@ */ package org.alfresco.repo.rule; -import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.repo.policy.Behaviour; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.PropertyCheck; /** * Class containing behaviour for the rules aspect * * @author Roy Wetherall */ -public class RulesAspect +public class RulesAspect implements + CopyServicePolicies.OnCopyNodePolicy, + CopyServicePolicies.OnCopyCompletePolicy { - private Behaviour onAddAspectBehaviour; - - private PolicyComponent policyComponent; - + private PolicyComponent policyComponent; + private BehaviourFilter behaviourFilter; private RuleService ruleService; + private NodeService nodeService; - private NodeService nodeService; - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } - public void setRuleService(RuleService ruleService) + public void setRuleService(RuleService ruleService) { this.ruleService = ruleService; } - public void init() - { - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), - RuleModel.ASPECT_RULES, - new JavaBehaviour(this, "onCopyNode")); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), - RuleModel.ASPECT_RULES, - new JavaBehaviour(this, "onCopyComplete")); - - this.onAddAspectBehaviour = new JavaBehaviour(this, "onAddAspect"); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), + public void init() + { + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter); + PropertyCheck.mandatory(this, "ruleService", ruleService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + RuleModel.ASPECT_RULES, + new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), + RuleModel.ASPECT_RULES, + new JavaBehaviour(this, "onCopyComplete")); + + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), RuleModel.ASPECT_RULES, - onAddAspectBehaviour); - } - - /** - * Helper to diable the on add aspect policy behaviour. Helpful when importing, - * copying and other bulk respstorative operations. - * - * TODO will eventually be redundant when policies can be enabled/diabled in the - * policy componenet - */ - public void disbleOnAddAspect() - { - this.onAddAspectBehaviour.disable(); + new JavaBehaviour(this, "onAddAspect")); } /** - * Helper to enable the on add aspect policy behaviour. Helpful when importing, - * copying and other bulk respstorative operations. - * - * TODO will eventually be redundant when policies can be enabled/diabled in the - * policy componenet + * Creates the rules folder below the node */ - public void enableOnAddAspect() + public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { - this.onAddAspectBehaviour.enable(); - } - - /** - * On add aspect policy behaviour - * @param nodeRef - * @param aspectTypeQName - */ - public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) - { this.ruleService.disableRules(nodeRef); try { - int count = this.nodeService.getChildAssocs(nodeRef, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).size(); + int count = this.nodeService.getChildAssocs(nodeRef, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).size(); if (count == 0) { - this.nodeService.createNode( + this.nodeService.createNode( nodeRef, - RuleModel.ASSOC_RULE_FOLDER, - RuleModel.ASSOC_RULE_FOLDER, - ContentModel.TYPE_SYSTEM_FOLDER); + RuleModel.ASSOC_RULE_FOLDER, + RuleModel.ASSOC_RULE_FOLDER, + ContentModel.TYPE_SYSTEM_FOLDER); } } finally { this.ruleService.enableRules(nodeRef); } - } - - public void onCopyNode( - QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, + } + + /** + * @return Returns {@link RulesAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return new RulesAspectCopyBehaviourCallback(behaviourFilter); + } + + /** + * Copy behaviour for the 'rules' model + * + * @author Derek Hulley + * @since 3.2 + */ + private class RulesAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private final BehaviourFilter behaviourFilter; + boolean behaviourDisabled = false; + + private RulesAspectCopyBehaviourCallback(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * Disables the aspect behaviour for this node + * + * @return Returns true + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + NodeRef targetNodeRef = copyDetails.getTargetNodeRef(); + behaviourFilter.disableBehaviour(targetNodeRef, RuleModel.ASPECT_RULES); + // Always copy + return true; + } + + /** + * Always copy into rules folders + * + * @return Returns {@link ChildAssocCopyAction#COPY_CHILD} + * for {@link RuleModel#ASSOC_RULE_FOLDER} + */ + @Override + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + if (childAssocRef.getTypeQName().equals(RuleModel.ASSOC_RULE_FOLDER)) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + super.throwExceptionForUnexpectedBehaviour(copyDetails, childAssocCopyDetails.toString()); + return null; // Never reached + } + } + + /** + * Force copy recursion after copying a rules folder + * + * @return Returns {@link ChildAssocRecurseAction#FORCE_RECURSE} + * for {@link RuleModel#ASSOC_RULE_FOLDER} + */ + @Override + public ChildAssocRecurseAction getChildAssociationRecurseAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + if (childAssocRef.getTypeQName().equals(RuleModel.ASSOC_RULE_FOLDER)) + { + return ChildAssocRecurseAction.FORCE_RECURSE; + } + else + { + super.throwExceptionForUnexpectedBehaviour(copyDetails, childAssocCopyDetails.toString()); + return null; // Never reached + } + } + } + + /** + * Re-enable aspect behaviour for the source node + */ + public void onCopyComplete( + QName classRef, + NodeRef sourceNodeRef, + NodeRef destinationRef, boolean copyToNewNode, - PolicyScope copyDetails) - { - copyDetails.addAspect(RuleModel.ASPECT_RULES); - - List assocs = this.nodeService.getChildAssocs( - sourceNodeRef, - RegexQNamePattern.MATCH_ALL, - RuleModel.ASSOC_RULE_FOLDER); - for (ChildAssociationRef assoc : assocs) - { - copyDetails.addChildAssociation(classRef, assoc, true); - } - - this.onAddAspectBehaviour.disable(); - } - - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef destinationRef, - boolean copyToNew, - Map copyMap) - { - this.onAddAspectBehaviour.enable(); - } + Map copyMap) + { + behaviourFilter.enableBehaviour(destinationRef, RuleModel.ASPECT_RULES); + } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java index bf0578fc35..1494235e5b 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java @@ -25,16 +25,19 @@ package org.alfresco.repo.thumbnail; import java.io.Serializable; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.CompositeAction; @@ -45,11 +48,9 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.EqualsHelper; /** @@ -127,9 +128,9 @@ public class ThumbnailedAspect implements NodeServicePolicies.OnUpdateProperties ContentModel.ASPECT_THUMBNAILED, new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_THUMBNAILED, - new JavaBehaviour(this, "onCopyNode")); + new JavaBehaviour(this, "getCopyCallback")); } /** @@ -229,31 +230,70 @@ public class ThumbnailedAspect implements NodeServicePolicies.OnUpdateProperties } /** - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.StoreRef, boolean, org.alfresco.repo.policy.PolicyScope) + * @return Returns {@link ThumbnailedAspectCopyBehaviourCallback} */ - public void onCopyNode( QName classRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) { - // Add the automatic update property - copyDetails.addProperty( - ContentModel.ASPECT_THUMBNAILED, - ContentModel.PROP_AUTOMATIC_UPDATE, - this.nodeService.getProperty(sourceNodeRef, ContentModel.PROP_AUTOMATIC_UPDATE)); + return ThumbnailedAspectCopyBehaviourCallback.INSTANCE; + } + + /** + * Behaviour for the {@link ContentModel#ASPECT_THUMBNAILED cm:thumbnailed} aspect. + * + * @author Derek Hulley + * @since 3.2 + */ + private static class ThumbnailedAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new ThumbnailedAspectCopyBehaviourCallback(); - if (copyToNewNode == true) + /** + * @return Returns true always + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) { - List assocs = this.nodeService.getChildAssocs( - sourceNodeRef, - ContentModel.ASSOC_THUMBNAILS, - RegexQNamePattern.MATCH_ALL); - for (ChildAssociationRef assoc : assocs) - { - copyDetails.addChildAssociation(classRef, assoc); - } + return true; + } + + /** + * Copy thumbnail-related associations, {@link ContentModel#ASSOC_THUMBNAILS} regardless of + * cascade options. + */ + @Override + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + if (childAssocRef.getTypeQName().equals(ContentModel.ASSOC_THUMBNAILS)) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + throw new IllegalStateException( + "Behaviour should have been invoked: \n" + + " Aspect: " + this.getClass().getName() + "\n" + + " " + childAssocCopyDetails + "\n" + + " " + copyDetails); + } + } + + /** + * Copy only the {@link ContentModel#PROP_AUTOMATIC_UPDATE} + */ + @Override + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + Map newProperties = new HashMap(5); + Serializable value = properties.get(ContentModel.PROP_AUTOMATIC_UPDATE); + newProperties.put(ContentModel.PROP_AUTOMATIC_UPDATE, value); + return newProperties; } - // otherwise we don't care about copying the associations over or we will get duplicates } } diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index 8a68bf8d7b..bfe4b9446a 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -25,22 +25,25 @@ package org.alfresco.repo.version; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.PolicyScope; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.NamespaceService; @@ -52,20 +55,21 @@ import org.alfresco.service.namespace.QName; * @author Roy Wetherall */ public class VersionableAspect implements ContentServicePolicies.OnContentUpdatePolicy, - NodeServicePolicies.OnAddAspectPolicy, - NodeServicePolicies.OnRemoveAspectPolicy, - NodeServicePolicies.OnDeleteNodePolicy, - VersionServicePolicies.AfterCreateVersionPolicy + NodeServicePolicies.OnAddAspectPolicy, + NodeServicePolicies.OnRemoveAspectPolicy, + NodeServicePolicies.OnDeleteNodePolicy, + VersionServicePolicies.AfterCreateVersionPolicy, + CopyServicePolicies.OnCopyNodePolicy { - /** The i18n'ized messages */ - private static final String MSG_INITIAL_VERSION = "create_version.initial_version"; - private static final String MSG_AUTO_VERSION = "create_version.auto_version"; - - /** Transaction resource key */ - private static final String KEY_VERSIONED_NODEREFS = "versioned_noderefs"; - + /** The i18n'ized messages */ + private static final String MSG_INITIAL_VERSION = "create_version.initial_version"; + private static final String MSG_AUTO_VERSION = "create_version.auto_version"; + + /** Transaction resource key */ + private static final String KEY_VERSIONED_NODEREFS = "versioned_noderefs"; + /** The policy component */ - private PolicyComponent policyComponent; + private PolicyComponent policyComponent; /** The node service */ private NodeService nodeService; @@ -75,26 +79,26 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate /** Auto version behaviour */ private Behaviour autoVersionBehaviour; - + /** * Set the policy component * * @param policyComponent the policy component */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } - /** - * Set the version service - * - * @param versionService the version service - */ + /** + * Set the version service + * + * @param versionService the version service + */ public void setVersionService(VersionService versionService) { - this.versionService = versionService; - } + this.versionService = versionService; + } /** * Set the node service @@ -109,25 +113,25 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate /** * Initialise the versionable aspect policies */ - public void init() - { - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), - ContentModel.ASPECT_VERSIONABLE, + public void init() + { + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), + ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onAddAspect", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), - ContentModel.ASPECT_VERSIONABLE, + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), + ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onRemoveAspect", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), - ContentModel.ASPECT_VERSIONABLE, + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), + ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "onDeleteNode", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "afterCreateVersion"), - ContentModel.ASPECT_VERSIONABLE, + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "afterCreateVersion"), + ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "afterCreateVersion", Behaviour.NotificationFrequency.EVERY_EVENT)); - + autoVersionBehaviour = new JavaBehaviour(this, "onContentUpdate", Behaviour.NotificationFrequency.TRANSACTION_COMMIT); this.policyComponent.bindClassBehaviour( ContentServicePolicies.ON_CONTENT_UPDATE, @@ -136,57 +140,72 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate // Register the copy behaviour this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyNode"), + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_VERSIONABLE, - new JavaBehaviour(this, "onCopy")); - } + new JavaBehaviour(this, "getCopyCallback")); + } - /** - * @see org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy#onDeleteNode(org.alfresco.service.cmr.repository.ChildAssociationRef, boolean) - */ - public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) - { - if (isNodeArchived == false) - { - // If we are perminantly deleting the node then we need to remove the associated version history - this.versionService.deleteVersionHistory(childAssocRef.getChildRef()); - } - // otherwise we do nothing since we need to hold onto the version history in case the node is restored later - } - /** - * OnCopy behaviour implementation for the version aspect. - *

- * Ensures that the propety values of the version aspect are not copied onto - * the destination node. - * - * @see org.alfresco.repo.copy.CopyServicePolicies.OnCopyNodePolicy#onCopyNode(QName, NodeRef, StoreRef, boolean, PolicyScope) + * @see org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy#onDeleteNode(org.alfresco.service.cmr.repository.ChildAssociationRef, boolean) */ - public void onCopy( - QName sourceClassRef, - NodeRef sourceNodeRef, - StoreRef destinationStoreRef, - boolean copyToNewNode, - PolicyScope copyDetails) + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) { - // Add the version aspect, but do not copy the version label - copyDetails.addAspect(ContentModel.ASPECT_VERSIONABLE); - copyDetails.addProperty( - ContentModel.ASPECT_VERSIONABLE, - ContentModel.PROP_AUTO_VERSION, - this.nodeService.getProperty(sourceNodeRef, ContentModel.PROP_AUTO_VERSION)); - } - - - /** - * On add aspect policy behaviour + if (isNodeArchived == false) + { + // If we are perminantly deleting the node then we need to remove the associated version history + this.versionService.deleteVersionHistory(childAssocRef.getChildRef()); + } + // otherwise we do nothing since we need to hold onto the version history in case the node is restored later + } + + /** + * @return Returns the {@link VersionableAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return VersionableAspectCopyBehaviourCallback.INSTANCE; + } + + /** + * Copy behaviour for the cm:versionable aspect * - * @param nodeRef - * @param aspectTypeQName - */ + * @author Derek Hulley + * @since 3.2 + */ + private static class VersionableAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new VersionableAspectCopyBehaviourCallback(); + + /** + * Copy the aspect, but only the {@link ContentModel#PROP_AUTO_VERSION} property + */ + @Override + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + Serializable value = properties.get(ContentModel.PROP_AUTO_VERSION); + if (value != null) + { + return Collections.singletonMap(ContentModel.PROP_AUTO_VERSION, value); + } + else + { + return Collections.emptyMap(); + } + } + } + + /** + * On add aspect policy behaviour + * + * @param nodeRef + * @param aspectTypeQName + */ public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) - { - if (this.nodeService.exists(nodeRef) == true && aspectTypeQName.equals(ContentModel.ASPECT_VERSIONABLE) == true) + { + if (this.nodeService.exists(nodeRef) == true && aspectTypeQName.equals(ContentModel.ASPECT_VERSIONABLE) == true) { boolean initialVersion = true; Boolean value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_INITIAL_VERSION); @@ -210,16 +229,16 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate } } } - } - + } + /** * @see org.alfresco.repo.node.NodeServicePolicies.OnRemoveAspectPolicy#onRemoveAspect(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) */ - public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) - { - // When the versionable aspect is removed from a node, then delete the associatied verison history - this.versionService.deleteVersionHistory(nodeRef); - } + public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) + { + // When the versionable aspect is removed from a node, then delete the associatied verison history + this.versionService.deleteVersionHistory(nodeRef); + } /** * On content update policy bahaviour @@ -227,47 +246,47 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate * @param nodeRef the node reference */ @SuppressWarnings("unchecked") - public void onContentUpdate(NodeRef nodeRef, boolean newContent) + public void onContentUpdate(NodeRef nodeRef, boolean newContent) { if (this.nodeService.exists(nodeRef) == true && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true - && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY) == false) + && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY) == false) { - Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); - if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false) - { - // Determine whether the node is auto versionable or not - boolean autoVersion = false; - Boolean value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION); - if (value != null) - { - // If the value is not null then - autoVersion = value.booleanValue(); - } - // else this means that the default value has not been set and the versionable aspect was applied pre-1.1 - - if (autoVersion == true) - { - // Create the auto-version - Map versionProperties = new HashMap(1); - versionProperties.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_AUTO_VERSION)); - this.versionService.createVersion(nodeRef, versionProperties); - } - } + Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); + if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false) + { + // Determine whether the node is auto versionable or not + boolean autoVersion = false; + Boolean value = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION); + if (value != null) + { + // If the value is not null then + autoVersion = value.booleanValue(); + } + // else this means that the default value has not been set and the versionable aspect was applied pre-1.1 + + if (autoVersion == true) + { + // Create the auto-version + Map versionProperties = new HashMap(1); + versionProperties.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_AUTO_VERSION)); + this.versionService.createVersion(nodeRef, versionProperties); + } + } } } /** * @see org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy#onCreateVersion(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, java.util.Map, org.alfresco.repo.policy.PolicyScope) */ - @SuppressWarnings("unchecked") - public void afterCreateVersion(NodeRef versionableNode, Version version) - { - Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); - if (versionedNodeRefs == null) - { - versionedNodeRefs = new HashMap(); - AlfrescoTransactionSupport.bindResource(KEY_VERSIONED_NODEREFS, versionedNodeRefs); - } - versionedNodeRefs.put(versionableNode, versionableNode); - } + @SuppressWarnings("unchecked") + public void afterCreateVersion(NodeRef versionableNode, Version version) + { + Map versionedNodeRefs = (Map)AlfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS); + if (versionedNodeRefs == null) + { + versionedNodeRefs = new HashMap(); + AlfrescoTransactionSupport.bindResource(KEY_VERSIONED_NODEREFS, versionedNodeRefs); + } + versionedNodeRefs.put(versionableNode, versionableNode); + } } diff --git a/source/java/org/alfresco/service/cmr/repository/CopyService.java b/source/java/org/alfresco/service/cmr/repository/CopyService.java index 9f118df5c8..05228b2197 100644 --- a/source/java/org/alfresco/service/cmr/repository/CopyService.java +++ b/source/java/org/alfresco/service/cmr/repository/CopyService.java @@ -78,20 +78,20 @@ public interface CopyService * cm:name property and catch the {@link DuplicateChildNodeNameException}. * * @param sourceNodeRef the node reference used as the source of the copy - * @param destinationParent the intended parent of the new node - * @param destinationAssocTypeQName the type of the new child assoc - * @param destinationQName the qualified name of the child association from the + * @param targetParentNodeRef the intended parent of the new node + * @param assocTypeQName the type of the new child assoc + * @param assocQName the qualified name of the child association from the * parent to the new node - * @param copyChildren indicates that the children of the node should also be copied + * @param copyChildren indicates that the children of the node should also be copied * * @return the new node reference */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "destinationParent", "destinationAssocTypeQName", "destinationQName", "copyChildren"}) + @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "targetParentNodeRef", "assocTypeQName", "assocQName", "copyChildren"}) public NodeRef copy( NodeRef sourceNodeRef, - NodeRef destinationParent, - QName destinationAssocTypeQName, - QName destinationQName, + NodeRef targetParentNodeRef, + QName assocTypeQName, + QName assocQName, boolean copyChildren); /** @@ -100,20 +100,20 @@ public interface CopyService * Ensures the copy name is the same as the origional or is renamed to prevent duplicate names. * * @param sourceNodeRef the node reference used as the source of the copy - * @param destinationParent the intended parent of the new node - * @param destinationAssocTypeQName the type of the new child assoc - * @param destinationQName the qualified name of the child association from the + * @param targetParentNodeRef the intended parent of the new node + * @param assocTypeQName the type of the new child assoc + * @param assocQName the qualified name of the child association from the * parent to the new node - * @param copyChildren indicates that the children of the node should also be copied + * @param copyChildren indicates that the children of the node should also be copied * * @return the new node reference */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "destinationParent", "destinationAssocTypeQName", "destinationQName", "copyChildren"}) + @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "targetParentNodeRef", "assocTypeQName", "assocQName", "copyChildren"}) public NodeRef copyAndRename( NodeRef sourceNodeRef, - NodeRef destinationParent, - QName destinationAssocTypeQName, - QName destinationQName, + NodeRef targetParentNodeRef, + QName assocTypeQName, + QName assocQName, boolean copyChildren); /** @@ -122,18 +122,18 @@ public interface CopyService * @see CopyService#copy(NodeRef, NodeRef, QName, QName, boolean) * * @param sourceNodeRef the node reference used as the source of the copy - * @param destinationParent the intended parent of the new node - * @param destinationAssocTypeQName the type of the new child assoc - * @param destinationQName the qualified name of the child association from the + * @param targetParentNodeRef the intended parent of the new node + * @param assocTypeQName the type of the new child assoc + * @param assocQName the qualified name of the child association from the * parent to the new node * @return the new node reference */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "destinationParent", "destinationAssocTypeQName", "destinationQName"}) + @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "targetParentNodeRef", "assocTypeQName", "assocQName"}) public NodeRef copy( NodeRef sourceNodeRef, - NodeRef destinationParent, - QName destinationAssocTypeQName, - QName destinationQName); + NodeRef targetParentNodeRef, + QName assocTypeQName, + QName assocQName); /** * Copies the state of one node on top of another.