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.