/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ 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.alfresco.util.Pair; 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(); } @Override public Pair getAssociationCopyAction( QName classQName, CopyDetails copyDetails, CopyAssociationDetails assocCopyDetails) { AssocCopySourceAction bestSourceAction = AssocCopySourceAction.COPY; AssocCopyTargetAction bestTargetAction = AssocCopyTargetAction.USE_ORIGINAL_TARGET; for (CopyBehaviourCallback callback : callbacks) { Pair action = callback.getAssociationCopyAction( classQName, copyDetails, assocCopyDetails); if (action.getFirst().compareTo(bestSourceAction) > 0) { // We've trumped the last best one bestSourceAction = action.getFirst(); } if (action.getSecond().compareTo(bestTargetAction) > 0) { // We've trumped the last best one bestTargetAction = action.getSecond(); } } Pair bestAction = new Pair(bestSourceAction, bestTargetAction); // Done if (logger.isDebugEnabled()) { logger.debug( "Association copy behaviour: " + bestAction + "\n" + " " + assocCopyDetails + "\n" + " " + copyDetails + "\n" + " " + this); } return bestAction; } /** * 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) { Map propsToCopy = callback.getCopyProperties(classQName, copyDetails, copyProperties); if(propsToCopy != copyProperties) { /* * Collections.emptyMap() is a valid return from the callback so we need to ensure it * is still mutable for the next iteration */ copyProperties = new HashMap(propsToCopy); } } // Done if (logger.isDebugEnabled()) { logger.debug( "Copy properties: \n" + " " + copyDetails + "\n" + " " + this + "\n" + " Before: " + properties + "\n" + " After: " + copyProperties); } return copyProperties; } }