diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index d646227d03..0eaa33ee87 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -41,7 +41,6 @@ import org.alfresco.repo.version.common.VersionHistoryImpl; import org.alfresco.repo.version.common.VersionImpl; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.cmr.repository.AspectMissingException; -import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -1036,22 +1035,35 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe if (versionHistoryNodeRef != null) { List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, Version2Model.CHILD_QNAME_VERSIONS, RegexQNamePattern.MATCH_ALL); + + ChildAssociationRef headVersionAssoc = null; + int headVersionNumber = -1; for (ChildAssociationRef versionAssoc : versionsAssoc) { - NodeRef versionNodeRef = versionAssoc.getChildRef(); - List successors = this.dbNodeService.getTargetAssocs(versionNodeRef, Version2Model.ASSOC_SUCCESSOR); - if (successors.size() == 0) + String localName = versionAssoc.getQName().getLocalName(); + if (localName.indexOf(Version2Model.CHILD_VERSIONS+"-") != -1) // TODO - could remove this belts-and-braces, should match correctly above ! { - NodeRef versionedNodeRef = (NodeRef)this.dbNodeService.getProperty( - versionNodeRef, - QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.PROP_FROZEN_NODE_REF)); - StoreRef versionStoreRef = versionedNodeRef.getStoreRef(); - if (storeRef.equals(versionStoreRef) == true) + int versionNumber = Integer.parseInt(localName.substring((Version2Model.CHILD_VERSIONS+"-").length())); + if (versionNumber > headVersionNumber) { - version = getVersion(versionNodeRef); + headVersionNumber = versionNumber; + headVersionAssoc = versionAssoc; } } } + + if (headVersionAssoc != null) + { + NodeRef versionNodeRef = headVersionAssoc.getChildRef(); + NodeRef versionedNodeRef = (NodeRef)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.PROP_FROZEN_NODE_REF)); + StoreRef versionStoreRef = versionedNodeRef.getStoreRef(); + if (storeRef.equals(versionStoreRef) == true) + { + version = getVersion(versionNodeRef); + } + } } return version; @@ -1085,4 +1097,40 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe } } } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.version.VersionService#deleteVersion(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version) + */ + public void deleteVersion(NodeRef nodeRef, Version version) + { + if (useDeprecatedV1) + { + super.deleteVersion(nodeRef, version); // throws UnsupportedOperationException + } + else + { + // Check the mandatory parameters + ParameterCheck.mandatory("nodeRef", nodeRef); + ParameterCheck.mandatory("version", version); + + Version currentVersion = getCurrentVersion(nodeRef); + + // Delete the version node + this.dbNodeService.deleteNode(VersionUtil.convertNodeRef(version.getFrozenStateNodeRef())); + + if (currentVersion.getVersionLabel().equals(version.getVersionLabel())) + { + Version headVersion = getHeadVersion(nodeRef); + if (headVersion != null) + { + // Reset the version label property on the versionable node to new head version + this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, headVersion.getVersionLabel()); + } + else + { + deleteVersionHistory(nodeRef); + } + } + } + } } diff --git a/source/java/org/alfresco/repo/version/VersionServiceImpl.java b/source/java/org/alfresco/repo/version/VersionServiceImpl.java index bd4d0d2f57..e634d7c7ef 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImpl.java @@ -1,1271 +1,1277 @@ -/* - * Copyright (C) 2005-2008 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.version; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -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.node.MLPropertyInterceptor; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyScope; -import org.alfresco.repo.version.common.AbstractVersionServiceImpl; -import org.alfresco.repo.version.common.VersionHistoryImpl; -import org.alfresco.repo.version.common.VersionImpl; -import org.alfresco.repo.version.common.VersionUtil; -import org.alfresco.repo.version.common.counter.VersionCounterService; -import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.AspectMissingException; -import org.alfresco.service.cmr.repository.AssociationRef; -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.search.SearchService; -import org.alfresco.service.cmr.version.ReservedVersionNameException; -import org.alfresco.service.cmr.version.Version; -import org.alfresco.service.cmr.version.VersionHistory; -import org.alfresco.service.cmr.version.VersionService; -import org.alfresco.service.cmr.version.VersionServiceException; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.ParameterCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Version1 Service - implements lightWeightVersionStore - * - * @author Roy Wetheral - */ -public class VersionServiceImpl extends AbstractVersionServiceImpl implements VersionService, VersionModel -{ - private static Log logger = LogFactory.getLog(VersionServiceImpl.class); - - /** - * Error message I18N id's - */ - protected static final String MSGID_ERR_NOT_FOUND = "version_service.err_not_found"; - protected static final String MSGID_ERR_NO_BRANCHES = "version_service.err_unsupported"; - protected static final String MSGID_ERR_RESTORE_EXISTS = "version_service.err_restore_exists"; - protected static final String MSGID_ERR_ONE_PRECEEDING = "version_service.err_one_preceeding"; - protected static final String MSGID_ERR_RESTORE_NO_VERSION = "version_service.err_restore_no_version"; - protected static final String MSGID_ERR_REVERT_MISMATCH = "version_service.err_revert_mismatch"; - - /** - * The version counter service - */ - protected VersionCounterService versionCounterService; - - /** - * The db node service, used as the version store implementation - */ - protected NodeService dbNodeService; - - /** - * Policy behaviour filter - */ - protected BehaviourFilter policyBehaviourFilter; - - /** - * The repository searcher - */ - protected SearchService searcher; // unused - - /** - * Sets the db node service, used as the version store implementation - * - * @param nodeService the node service - */ - public void setDbNodeService(NodeService nodeService) - { - this.dbNodeService = nodeService; - } - - /** - * @param searcher the searcher - */ - public void setSearcher(SearchService searcher) - { - this.searcher = searcher; - } - - /** - * @param versionCounterService the version counter service - */ - public void setVersionCounterService(VersionCounterService versionCounterService) - { - this.versionCounterService = versionCounterService; - } - - /** - * Set the policy behaviour filter - * - * @param policyBehaviourFilter the policy behaviour filter - */ - public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) - { - this.policyBehaviourFilter = policyBehaviourFilter; - } - - /** - * Initialise method - */ - @Override - public void initialise() - { - super.initialise(); - - // Register the serial version label behaviour - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "calculateVersionLabel"), - ContentModel.TYPE_CMOBJECT, - new JavaBehaviour(new SerialVersionLabelPolicy(), "calculateVersionLabel")); - - // Register the serial version label behaviour for the mlContainer too - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "calculateVersionLabel"), - ContentModel.TYPE_MULTILINGUAL_CONTAINER, - new JavaBehaviour(new SerialVersionLabelPolicy(), "calculateVersionLabel")); - } - - // TODO - temp - protected void initialiseWithoutBind() - { - super.initialise(); - } - - /** - * Gets the reference to the version store - * - * @return reference to the version store - */ - @Override - public StoreRef getVersionStoreReference() - { - return new StoreRef( - StoreRef.PROTOCOL_WORKSPACE, - VersionModel.STORE_ID); - } - - /** - * @see VersionCounterService#nextVersionNumber(StoreRef) - */ - public Version createVersion( - NodeRef nodeRef, - Map versionProperties) - throws ReservedVersionNameException, AspectMissingException - { - long startTime = System.currentTimeMillis(); - - // Get the next version number - int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); - - // Create the version - Version version = createVersion(nodeRef, versionProperties, versionNumber); - - if (logger.isDebugEnabled()) - { - logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in " + (System.currentTimeMillis()-startTime) + " ms"); - } - - return version; - } - - /** - * The version's are created from the children upwards with the parent being created first. This will - * ensure that the child version references in the version node will point to the version history nodes - * for the (possibly) newly created version histories. - */ - public Collection createVersion( - NodeRef nodeRef, - Map versionProperties, - boolean versionChildren) - throws ReservedVersionNameException, AspectMissingException - { - long startTime = System.currentTimeMillis(); - - // Get the next version number - int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); - - // Create the versions - Collection versions = createVersion(nodeRef, versionProperties, versionChildren, versionNumber); - - if (logger.isDebugEnabled()) - { - Version[] versionsArray = versions.toArray(new Version[0]); - Version version = versionsArray[versionsArray.length -1]; // last item is the new parent version - logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in "+ (System.currentTimeMillis()-startTime) +" ms "+(versionChildren ? "(with " + (versions.size() - 1) + " children)" : "")); - } - - return versions; - } - - /** - * Helper method used to create the version when the versionChildren flag is provided. This method - * ensures that all the children (if the falg is set to true) are created with the same version - * number, this ensuring that the version stripe is correct. - * - * @param nodeRef the parent node reference - * @param versionProperties the version properties - * @param versionChildren indicates whether to version the children of the parent - * node - * @param versionNumber the version number - - * @return a collection of the created versions - * @throws ReservedVersionNameException thrown if there is a reserved version property name clash - * @throws AspectMissingException thrown if the version aspect is missing from a node - */ - private Collection createVersion( - NodeRef nodeRef, - Map versionProperties, - boolean versionChildren, - int versionNumber) - throws ReservedVersionNameException, AspectMissingException - { - - Collection result = new ArrayList(); - - if (versionChildren == true) - { - // Get the children of the node - Collection children = this.dbNodeService.getChildAssocs(nodeRef); - for (ChildAssociationRef childAssoc : children) - { - // Recurse into this method to version all the children with the same version number - Collection childVersions = createVersion( - childAssoc.getChildRef(), - versionProperties, - versionChildren, - versionNumber); - result.addAll(childVersions); - } - } - - result.add(createVersion(nodeRef, versionProperties, versionNumber)); - - return result; - } - - /** - * Note: we can't control the order of the list, so if we have children and parents in the list and the - * parents get versioned before the children and the children are not already versioned then the parents - * child references will be pointing to the node ref, rather than the verison history. - */ - public Collection createVersion( - Collection nodeRefs, - Map versionProperties) - throws ReservedVersionNameException, AspectMissingException - { - long startTime = System.currentTimeMillis(); - - Collection result = new ArrayList(nodeRefs.size()); - - // Get the next version number - int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); - - // Version each node in the list - for (NodeRef nodeRef : nodeRefs) - { - result.add(createVersion(nodeRef, versionProperties, versionNumber)); - } - - if (logger.isDebugEnabled()) - { - logger.debug("created version list (" + versionNumber + " - " + getVersionStoreReference() + ") in "+ (System.currentTimeMillis()-startTime) +" ms (with " + nodeRefs.size() + " nodes)"); - } - - return result; - } - - /** - * Creates a new version of the passed node assigning the version properties - * accordingly. - * - * @param nodeRef a node reference - * @param versionProperties the version properties - * @param versionNumber the version number - * @return the newly created version - * @throws ReservedVersionNameException - * thrown if there is a name clash in the version properties - */ - protected Version createVersion( - NodeRef nodeRef, - Map origVersionProperties, - int versionNumber) - throws ReservedVersionNameException - { - long startTime = System.currentTimeMillis(); - - // Copy the version properties (to prevent unexpected side effects to the caller) - Map versionProperties = new HashMap(); - if (origVersionProperties != null) - { - versionProperties.putAll(origVersionProperties); - } - - // If the version aspect is not there then add it - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) - { - this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); - } - - // Call the policy behaviour - invokeBeforeCreateVersion(nodeRef); - - // Check that the supplied additional version properties do not clash with the reserved ones - VersionUtil.checkVersionPropertyNames(versionProperties.keySet()); - - // Check the repository for the version history for this node - NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); - NodeRef currentVersionRef = null; - - if (versionHistoryRef == null) - { - // Create the version history - versionHistoryRef = createVersionHistory(nodeRef); - } - else - { - // Since we have an exisiting version history we should be able to lookup - // the current version - currentVersionRef = getCurrentVersionNodeRef(versionHistoryRef, nodeRef); - - if (currentVersionRef == null) - { - throw new VersionServiceException(MSGID_ERR_NOT_FOUND); - } - - // Need to check that we are not about to create branch since this is not currently supported - VersionHistory versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); - Version currentVersion = getVersion(currentVersionRef); - if (versionHistory.getSuccessors(currentVersion).size() != 0) - { - throw new VersionServiceException(MSGID_ERR_NO_BRANCHES); - } - } - - // Create the node details - QName classRef = this.nodeService.getType(nodeRef); - PolicyScope nodeDetails = new PolicyScope(classRef); - - // Get the node details by calling the onVersionCreate policy behaviour - invokeOnCreateVersion(nodeRef, versionProperties, nodeDetails); - - // Create the new version node (child of the version history) - NodeRef newVersionRef = createNewVersion( - nodeRef, - versionHistoryRef, - getStandardVersionProperties(versionProperties, nodeRef, currentVersionRef, versionNumber), - versionProperties, - nodeDetails); - - if (currentVersionRef == null) - { - // Set the new version to be the root version in the version history - this.dbNodeService.createAssociation( - versionHistoryRef, - newVersionRef, - VersionServiceImpl.ASSOC_ROOT_VERSION); - } - else - { - // Relate the new version to the current version as its successor - this.dbNodeService.createAssociation( - currentVersionRef, - newVersionRef, - VersionServiceImpl.ASSOC_SUCCESSOR); - } - - // Create the version data object - Version version = this.getVersion(newVersionRef); - - // Set the new version label on the versioned node - this.nodeService.setProperty( - nodeRef, - ContentModel.PROP_VERSION_LABEL, - version.getVersionLabel()); - - // Freeze the version label property - Map versionLabelAsMap = new HashMap(1); - versionLabelAsMap.put(ContentModel.PROP_VERSION_LABEL, version.getVersionLabel()); - this.freezeProperties(newVersionRef, versionLabelAsMap); - - // Invoke the policy behaviour - invokeAfterCreateVersion(nodeRef, version); - - if (logger.isTraceEnabled()) - { - logger.trace("created Version (" + versionNumber + " - " + getVersionStoreReference() + ") " + nodeRef + " in " + (System.currentTimeMillis()-startTime) +" ms"); - } - - // Return the data object representing the newly created version - return version; - } - - /** - * Creates a new version history node, applying the root version aspect is required - * - * @param nodeRef the node ref - * @return the version history node reference - */ - private NodeRef createVersionHistory(NodeRef nodeRef) - { - HashMap props = new HashMap(); - props.put(ContentModel.PROP_NAME, nodeRef.getId()); - props.put(PROP_QNAME_VERSIONED_NODE_ID, nodeRef.getId()); - - // Create a new version history node - ChildAssociationRef childAssocRef = this.dbNodeService.createNode( - getRootNode(), - CHILD_QNAME_VERSION_HISTORIES, - QName.createQName(VersionModel.NAMESPACE_URI, nodeRef.getId()), - TYPE_QNAME_VERSION_HISTORY, - props); - return childAssocRef.getChildRef(); - } - - /** - * @see org.alfresco.service.cmr.version.VersionService#getVersionHistory(NodeRef) - */ - public VersionHistory getVersionHistory(NodeRef nodeRef) - { - VersionHistory versionHistory = null; - - if (this.nodeService.exists(nodeRef) == true) - { - NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); - if (versionHistoryRef != null) - { - versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); - } - } - - return versionHistory; - } - - /** - * @see VersionService#getCurrentVersion(NodeRef) - */ - public Version getCurrentVersion(NodeRef nodeRef) - { - Version version = null; - - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) - { - VersionHistory versionHistory = getVersionHistory(nodeRef); - if (versionHistory != null) - { - String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - version = versionHistory.getVersion(versionLabel); - } - } - - return version; - } - - /** - * Get a map containing the standard list of version properties populated. - * - * @param versionProperties the version meta data properties - * @param nodeRef the node reference - * @param preceedingNodeRef the preceeding node reference - * @param versionNumber the version number - * @return the standard version properties - */ - private Map getStandardVersionProperties(Map versionProperties, NodeRef nodeRef, NodeRef preceedingNodeRef, int versionNumber) - { - Map result = new HashMap(10); - - // Set the version number for the new version - result.put(VersionModel.PROP_QNAME_VERSION_NUMBER, Integer.toString(versionNumber)); - - // Set the versionable node id - result.put(VersionModel.PROP_QNAME_FROZEN_NODE_ID, nodeRef.getId()); - - // Set the versionable node store protocol - result.put(VersionModel.PROP_QNAME_FROZEN_NODE_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol()); - - // Set the versionable node store id - result.put(VersionModel.PROP_QNAME_FROZEN_NODE_STORE_ID, nodeRef.getStoreRef().getIdentifier()); - - // Store the current node type - QName nodeType = this.nodeService.getType(nodeRef); - result.put(VersionModel.PROP_QNAME_FROZEN_NODE_TYPE, nodeType); - - // Store the current aspects - Set aspects = this.nodeService.getAspects(nodeRef); - result.put(VersionModel.PROP_QNAME_FROZEN_ASPECTS, (Serializable)aspects); - - // Calculate the version label - QName classRef = this.nodeService.getType(nodeRef); - Version preceedingVersion = getVersion(preceedingNodeRef); - String versionLabel = invokeCalculateVersionLabel(classRef, preceedingVersion, versionNumber, versionProperties); - result.put(VersionModel.PROP_QNAME_VERSION_LABEL, versionLabel); - - return result; - } - - /** - * Creates a new version node, setting the properties both calculated and specified. - * - * @param versionableNodeRef the reference to the node being versioned - * @param versionHistoryRef version history node reference - * @param preceedingNodeRef the version node preceeding this in the version history - * , null if none - * @param versionProperties version properties - * @param versionNumber the version number - * @return the version node reference - */ - private NodeRef createNewVersion( - NodeRef versionableNodeRef, - NodeRef versionHistoryRef, - Map standardVersionProperties, - Map versionProperties, - PolicyScope nodeDetails) - { - // Create the new version - ChildAssociationRef childAssocRef = this.dbNodeService.createNode( - versionHistoryRef, - CHILD_QNAME_VERSIONS, - CHILD_QNAME_VERSIONS, - TYPE_QNAME_VERSION, - standardVersionProperties); - NodeRef versionNodeRef = childAssocRef.getChildRef(); - - // Store the meta data - storeVersionMetaData(versionNodeRef, versionProperties); - - // Freeze the various parts of the node - freezeProperties(versionNodeRef, nodeDetails.getProperties()); - freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations()); - freezeAssociations(versionNodeRef, nodeDetails.getAssociations()); - freezeAspects(nodeDetails, versionNodeRef, nodeDetails.getAspects()); - - // Return the created node reference - return versionNodeRef; - } - - /** - * Store the version meta data - * - * @param versionNodeRef the version node reference - * @param versionProperties the version properties - */ - private void storeVersionMetaData(NodeRef versionNodeRef, Map versionProperties) - { - for (Map.Entry entry : versionProperties.entrySet()) - { - HashMap properties = new HashMap(); - - properties.put(PROP_QNAME_META_DATA_NAME, entry.getKey()); - properties.put(PROP_QNAME_META_DATA_VALUE, entry.getValue()); - - this.dbNodeService.createNode( - versionNodeRef, - CHILD_QNAME_VERSION_META_DATA, - CHILD_QNAME_VERSION_META_DATA, - TYPE_QNAME_VERSION_META_DATA_VALUE, - properties); - } - } - - protected Map getVersionMetaData(NodeRef versionNodeRef) - { - // Get the meta data - List metaData = this.dbNodeService.getChildAssocs( - versionNodeRef, - RegexQNamePattern.MATCH_ALL, - CHILD_QNAME_VERSION_META_DATA); - - Map versionProperties = new HashMap(metaData.size()); - - for (ChildAssociationRef ref : metaData) - { - NodeRef metaDataValue = (NodeRef)ref.getChildRef(); - String name = (String)this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_NAME); - Serializable value = this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_VALUE); - versionProperties.put(name, value); - } - - return versionProperties; - } - - /** - * Freeze the aspects - * - * @param nodeDetails the node details - * @param versionNodeRef the version node reference - * @param aspects the set of aspects - */ - private void freezeAspects(PolicyScope nodeDetails, NodeRef versionNodeRef, Set aspects) - { - for (QName aspect : aspects) - { - // Freeze the details of the aspect - freezeProperties(versionNodeRef, nodeDetails.getProperties(aspect)); - freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations(aspect)); - freezeAssociations(versionNodeRef, nodeDetails.getAssociations(aspect)); - } - } - - /** - * Freeze associations - * - * @param versionNodeRef the version node reference - * @param associations the list of associations - */ - private void freezeAssociations(NodeRef versionNodeRef, List associations) - { - for (AssociationRef targetAssoc : associations) - { - HashMap properties = new HashMap(); - - // Set the qname of the association - properties.put(PROP_QNAME_ASSOC_TYPE_QNAME, targetAssoc.getTypeQName()); - - // Set the reference property to point to the child node - properties.put(ContentModel.PROP_REFERENCE, targetAssoc.getTargetRef()); - - // Create child version reference - this.dbNodeService.createNode( - versionNodeRef, - CHILD_QNAME_VERSIONED_ASSOCS, - CHILD_QNAME_VERSIONED_ASSOCS, - TYPE_QNAME_VERSIONED_ASSOC, - properties); - } - } - - /** - * Freeze child associations - * - * @param versionNodeRef the version node reference - * @param childAssociations the child associations - */ - private void freezeChildAssociations(NodeRef versionNodeRef, List childAssociations) - { - for (ChildAssociationRef childAssocRef : childAssociations) - { - HashMap properties = new HashMap(); - - // Set the qname, isPrimary and nthSibling properties - properties.put(PROP_QNAME_ASSOC_QNAME, childAssocRef.getQName()); - properties.put(PROP_QNAME_ASSOC_TYPE_QNAME, childAssocRef.getTypeQName()); - properties.put(PROP_QNAME_IS_PRIMARY, Boolean.valueOf(childAssocRef.isPrimary())); - properties.put(PROP_QNAME_NTH_SIBLING, Integer.valueOf(childAssocRef.getNthSibling())); - - // Set the reference property to point to the child node - properties.put(ContentModel.PROP_REFERENCE, childAssocRef.getChildRef()); - - // Create child version reference - this.dbNodeService.createNode( - versionNodeRef, - CHILD_QNAME_VERSIONED_CHILD_ASSOCS, - CHILD_QNAME_VERSIONED_CHILD_ASSOCS, - TYPE_QNAME_VERSIONED_CHILD_ASSOC, - properties); - } - } - - /** - * Freeze properties - * - * @param versionNodeRef the version node reference - * @param properties the properties - */ - private void freezeProperties(NodeRef versionNodeRef, Map properties) - { - // Copy the property values from the node onto the version node - for (Map.Entry entry : properties.entrySet()) - { - // Get the property values - HashMap props = new HashMap(); - props.put(PROP_QNAME_QNAME, entry.getKey()); - - if (entry.getValue() instanceof Collection) - { - props.put(PROP_QNAME_MULTI_VALUE, entry.getValue()); - props.put(PROP_QNAME_IS_MULTI_VALUE, true); - } - else - { - props.put(PROP_QNAME_VALUE, entry.getValue()); - props.put(PROP_QNAME_IS_MULTI_VALUE, false); - } - - // Create the node storing the frozen attribute details - this.dbNodeService.createNode( - versionNodeRef, - CHILD_QNAME_VERSIONED_ATTRIBUTES, - CHILD_QNAME_VERSIONED_ATTRIBUTES, - TYPE_QNAME_VERSIONED_PROPERTY, - props); - } - } - - /** - * Gets the version stores root node - * - * @return the node ref to the root node of the version store - */ - protected NodeRef getRootNode() - { - // Get the version store root node reference - return this.dbNodeService.getRootNode(getVersionStoreReference()); - } - - /** - * Builds a version history object from the version history reference. - *

- * The node ref is passed to enable the version history to be scoped to the - * appropriate branch in the version history. - * - * @param versionHistoryRef the node ref for the version history - * @param nodeRef the node reference - * @return a constructed version history object - */ - protected VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef) - { - VersionHistory versionHistory = null; - - ArrayList versionHistoryNodeRefs = new ArrayList(); - - NodeRef currentVersion; - if (this.nodeService.exists(nodeRef)) - { - currentVersion = getCurrentVersionNodeRef(versionHistoryRef, nodeRef); - } - else - { - currentVersion = VersionUtil.convertNodeRef(getLatestVersion(nodeRef).getFrozenStateNodeRef()); - } - - while (currentVersion != null) - { - AssociationRef preceedingVersion = null; - - versionHistoryNodeRefs.add(0, currentVersion); - - List preceedingVersions = this.dbNodeService.getSourceAssocs( - currentVersion, - VersionModel.ASSOC_SUCCESSOR); - if (preceedingVersions.size() == 1) - { - preceedingVersion = (AssociationRef)preceedingVersions.toArray()[0]; - currentVersion = preceedingVersion.getSourceRef(); - } - else if (preceedingVersions.size() > 1) - { - // Error since we only currently support one preceeding version - throw new VersionServiceException(MSGID_ERR_ONE_PRECEEDING); - } - else - { - currentVersion = null; - } - } - - // Build the version history object - boolean isRoot = true; - Version preceeding = null; - for (NodeRef versionRef : versionHistoryNodeRefs) - { - Version version = getVersion(versionRef); - - if (isRoot == true) - { - versionHistory = new VersionHistoryImpl(version); - isRoot = false; - } - else - { - ((VersionHistoryImpl)versionHistory).addVersion(version, preceeding); - } - preceeding = version; - } - - return versionHistory; - } - - /** - * Constructs the a version object to contain the version information from the version node ref. - * - * @param versionRef the version reference - * @return object containing verison data - */ - protected Version getVersion(NodeRef versionRef) - { - if (versionRef == null) - { - return null; - } - Map versionProperties = new HashMap(); - - // Get the standard node details - Map nodeProperties = this.dbNodeService.getProperties(versionRef); - for (QName key : nodeProperties.keySet()) - { - Serializable value = nodeProperties.get(key); - versionProperties.put(key.getLocalName(), value); - } - - // Get the meta data - Map versionMetaDataProperties = getVersionMetaData(versionRef); - versionProperties.putAll(versionMetaDataProperties); - - // Create and return the version object - NodeRef newNodeRef = new NodeRef(new StoreRef(STORE_PROTOCOL, STORE_ID), versionRef.getId()); - Version result = new VersionImpl(versionProperties, newNodeRef); - // done - return result; - } - - /** - * Gets a reference to the version history node for a given 'real' node. - * - * @param nodeRef a node reference - * @return a reference to the version history node, null of none - */ - protected NodeRef getVersionHistoryNodeRef(NodeRef nodeRef) - { - if (nodeService.exists(nodeRef)) - { - return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_UUID)); - } - else - { - return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, nodeRef.getId()); - } - } - - /** - * Gets a reference to the node for the current version of the passed node ref. - * - * This uses the version label as a mechanism for looking up the version node in - * the version history. - * - * @param nodeRef a node reference - * @return a reference to a version reference - */ - private NodeRef getCurrentVersionNodeRef(NodeRef versionHistory, NodeRef nodeRef) - { - NodeRef result = null; - String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - - Collection versions = this.dbNodeService.getChildAssocs(versionHistory); - for (ChildAssociationRef version : versions) - { - String tempLabel = (String)this.dbNodeService.getProperty(version.getChildRef(), VersionModel.PROP_QNAME_VERSION_LABEL); - if (tempLabel != null && tempLabel.equals(versionLabel) == true) - { - result = version.getChildRef(); - break; - } - } - - return result; - } - - /** - * @see org.alfresco.cms.version.VersionService#revert(NodeRef) - */ - public void revert(NodeRef nodeRef) - { - revert(nodeRef, getCurrentVersion(nodeRef), true); - } - - /** - * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, boolean) - */ - public void revert(NodeRef nodeRef, boolean deep) - { - revert(nodeRef, getCurrentVersion(nodeRef), deep); - } - - /** - * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version) - */ - public void revert(NodeRef nodeRef, Version version) - { - revert(nodeRef, version, true); - } - - /** - * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version, boolean) - */ - public void revert(NodeRef nodeRef, Version version, boolean deep) - { - // Check the mandatory parameters - ParameterCheck.mandatory("nodeRef", nodeRef); - ParameterCheck.mandatory("version", version); - - // Cross check that the version provided relates to the node reference provided - if (nodeRef.getId().equals(version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID)) == false) - { - // Error since the version provided does not correspond to the node reference provided - throw new VersionServiceException(MSGID_ERR_REVERT_MISMATCH); - } - - // Turn off any auto-version policy behaviours - this.policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); - try - { - // Store the current version label - String currentVersionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - - // Get the node that represents the frozen state - NodeRef versionNodeRef = version.getFrozenStateNodeRef(); - - // Revert the property values - this.nodeService.setProperties(nodeRef, this.nodeService.getProperties(versionNodeRef)); - - // Apply/remove the aspects as required - Set aspects = new HashSet(this.nodeService.getAspects(nodeRef)); - for (QName versionAspect : this.nodeService.getAspects(versionNodeRef)) - { - if (aspects.contains(versionAspect) == false) - { - this.nodeService.addAspect(nodeRef, versionAspect, null); - } - else - { - aspects.remove(versionAspect); - } - } - for (QName aspect : aspects) - { - this.nodeService.removeAspect(nodeRef, aspect); - } - - // Re-add the versionable aspect to the reverted node - if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) - { - this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); - } - - // Re-set the version label property (since it should not be modified from the origional) - this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, currentVersionLabel); - - // Add/remove the child nodes - List children = new ArrayList(this.nodeService.getChildAssocs(nodeRef)); - for (ChildAssociationRef versionedChild : this.nodeService.getChildAssocs(versionNodeRef)) - { - if (children.contains(versionedChild) == false) - { - if (this.nodeService.exists(versionedChild.getChildRef()) == true) - { - // The node was a primary child of the parent, but that is no longer the case. Dispite this - // the node still exits so this means it has been moved. - // The best thing to do in this situation will be to re-add the node as a child, but it will not - // be a primary child. - this.nodeService.addChild(nodeRef, versionedChild.getChildRef(), versionedChild.getTypeQName(), versionedChild.getQName()); - } - else - { - if (versionedChild.isPrimary() == true) - { - // Only try to resotre missing children if we are doing a deep revert - // Look and see if we have a version history for the child node - if (deep == true && getVersionHistoryNodeRef(versionedChild.getChildRef()) != null) - { - // We're going to try and restore the missing child node and recreate the assoc - restore( - versionedChild.getChildRef(), - nodeRef, - versionedChild.getTypeQName(), - versionedChild.getQName()); - } - // else the deleted child did not have a version history so we can't restore the child - // and so we can't revert the association - } - - // else - // Since this was never a primary assoc and the child has been deleted we won't recreate - // the missing node as it was never owned by the node and we wouldn't know where to put it. - } - } - else - { - children.remove(versionedChild); - } - } - for (ChildAssociationRef ref : children) - { - this.nodeService.removeChild(nodeRef, ref.getChildRef()); - } - - // Add/remove the target associations - for (AssociationRef assocRef : this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)) - { - this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName()); - } - for (AssociationRef versionedAssoc : this.nodeService.getTargetAssocs(versionNodeRef, RegexQNamePattern.MATCH_ALL)) - { - if (this.nodeService.exists(versionedAssoc.getTargetRef()) == true) - { - this.nodeService.createAssociation(nodeRef, versionedAssoc.getTargetRef(), versionedAssoc.getTypeQName()); - } - - // else - // Since the tareget of the assoc no longer exists we can't recreate the assoc - } - } - finally - { - // Turn auto-version policies back on - this.policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); - } - } - - /** - * @see org.alfresco.service.cmr.version.VersionService#restore(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) - */ - public NodeRef restore( - NodeRef nodeRef, - NodeRef parentNodeRef, - QName assocTypeQName, - QName assocQName) - { - return restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true); - } - - /** - * @see org.alfresco.service.cmr.version.VersionService#restore(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 restore( - NodeRef nodeRef, - NodeRef parentNodeRef, - QName assocTypeQName, - QName assocQName, - boolean deep) - { - NodeRef restoredNodeRef = null; - - // Check that the node does not exist - if (this.nodeService.exists(nodeRef) == true) - { - // Error since you can not restore a node that already exists - throw new VersionServiceException(MSGID_ERR_RESTORE_EXISTS, new Object[]{nodeRef.toString()}); - } - - // Try and get the version details that we want to restore to - Version version = getHeadVersion(nodeRef); - if (version == null) - { - // Error since there is no version information available to restore the node from - throw new VersionServiceException(MSGID_ERR_RESTORE_NO_VERSION, new Object[]{nodeRef.toString()}); - } - - // Set the uuid of the new node - Map props = new HashMap(1); - props.put(ContentModel.PROP_NODE_UUID, version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID)); - - // Get the type of the node node - QName type = (QName)version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_TYPE); - - // Disable auto-version behaviour - this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE); - try - { - // Create the restored node - restoredNodeRef = this.nodeService.createNode( - parentNodeRef, - assocTypeQName, - assocQName, - type, - props).getChildRef(); - } - finally - { - // Enable auto-version behaviour - this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE); - } - - // Now we need to revert the newly restored node - revert(restoredNodeRef, version, deep); - - return restoredNodeRef; - } - - /** - * Get the head version given a node reference - * - * @param nodeRef the node reference - * @return the 'head' version - */ - private Version getHeadVersion(NodeRef nodeRef) - { - Version version = null; - StoreRef storeRef = nodeRef.getStoreRef(); - - NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); - if (versionHistoryNodeRef != null) - { - List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS); - for (ChildAssociationRef versionAssoc : versionsAssoc) - { - NodeRef versionNodeRef = versionAssoc.getChildRef(); - List successors = this.dbNodeService.getTargetAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR); - if (successors.size() == 0) - { - String storeProtocol = (String)this.dbNodeService.getProperty( - versionNodeRef, - QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL)); - String storeId = (String)this.dbNodeService.getProperty( - versionNodeRef, - QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID)); - StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId); - if (storeRef.equals(versionStoreRef) == true) - { - version = getVersion(versionNodeRef); - } - } - } - } - - return version; - } - - private Version getLatestVersion(NodeRef nodeRef) - { - Version version = null; - StoreRef storeRef = nodeRef.getStoreRef(); - - NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); - if (versionHistoryNodeRef != null) - { - List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS); - for (ChildAssociationRef versionAssoc : versionsAssoc) - { - NodeRef versionNodeRef = versionAssoc.getChildRef(); - List predecessors = this.dbNodeService.getSourceAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR); - if (predecessors.size() == 0) - { - String storeProtocol = (String)this.dbNodeService.getProperty( - versionNodeRef, - QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL)); - String storeId = (String)this.dbNodeService.getProperty( - versionNodeRef, - QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID)); - StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId); - if (storeRef.equals(versionStoreRef) == true) - { - version = getVersion(versionNodeRef); - } - } - } - } - - return version; - } - - /** - * @see org.alfresco.cms.version.VersionService#deleteVersionHistory(NodeRef) - */ - public void deleteVersionHistory(NodeRef nodeRef) - throws AspectMissingException - { - // Get the version history node for the node is question and delete it - NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); - - if (versionHistoryNodeRef != null) - { - // Delete the version history node - this.dbNodeService.deleteNode(versionHistoryNodeRef); - - if (this.nodeService.exists(nodeRef) == true && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) - { - // Reset the version label property on the versionable node - this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, null); - } - } - } - - @Override - protected void defaultOnCreateVersion( - QName classRef, - NodeRef nodeRef, - Map versionProperties, - PolicyScope nodeDetails) - { - ClassDefinition classDefinition = this.dictionaryService.getClass(classRef); - if (classDefinition != null) - { - boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); - try - { - // Copy the properties - Map propertyDefinitions = classDefinition.getProperties(); - for (QName propertyName : propertyDefinitions.keySet()) - { - Serializable propValue = this.nodeService.getProperty(nodeRef, propertyName); - nodeDetails.addProperty(classRef, propertyName, propValue); - } - } - finally - { - MLPropertyInterceptor.setMLAware(wasMLAware); - } - - // Version 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(nodeRef); - for (ChildAssociationRef childAssocRef : childAssocRefs) - { - if (assocDefs.containsKey(childAssocRef.getTypeQName())) - { - nodeDetails.addChildAssociation(classDefinition.getName(), childAssocRef); - } - } - } - - // TODO: Need way of getting assocs of a given type - List nodeAssocRefs = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); - for (AssociationRef nodeAssocRef : nodeAssocRefs) - { - if (assocDefs.containsKey(nodeAssocRef.getTypeQName())) - { - nodeDetails.addAssociation(classDefinition.getName(), nodeAssocRef); - } - } - } - } -} +/* + * Copyright (C) 2005-2008 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.version; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +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.node.MLPropertyInterceptor; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyScope; +import org.alfresco.repo.version.common.AbstractVersionServiceImpl; +import org.alfresco.repo.version.common.VersionHistoryImpl; +import org.alfresco.repo.version.common.VersionImpl; +import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.repo.version.common.counter.VersionCounterService; +import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AspectMissingException; +import org.alfresco.service.cmr.repository.AssociationRef; +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.search.SearchService; +import org.alfresco.service.cmr.version.ReservedVersionNameException; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionServiceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Version1 Service - implements lightWeightVersionStore + * + * @author Roy Wetheral + */ +public class VersionServiceImpl extends AbstractVersionServiceImpl implements VersionService, VersionModel +{ + private static Log logger = LogFactory.getLog(VersionServiceImpl.class); + + /** + * Error message I18N id's + */ + protected static final String MSGID_ERR_NOT_FOUND = "version_service.err_not_found"; + protected static final String MSGID_ERR_NO_BRANCHES = "version_service.err_unsupported"; + protected static final String MSGID_ERR_RESTORE_EXISTS = "version_service.err_restore_exists"; + protected static final String MSGID_ERR_ONE_PRECEEDING = "version_service.err_one_preceeding"; + protected static final String MSGID_ERR_RESTORE_NO_VERSION = "version_service.err_restore_no_version"; + protected static final String MSGID_ERR_REVERT_MISMATCH = "version_service.err_revert_mismatch"; + + /** + * The version counter service + */ + protected VersionCounterService versionCounterService; + + /** + * The db node service, used as the version store implementation + */ + protected NodeService dbNodeService; + + /** + * Policy behaviour filter + */ + protected BehaviourFilter policyBehaviourFilter; + + /** + * The repository searcher + */ + protected SearchService searcher; // unused + + /** + * Sets the db node service, used as the version store implementation + * + * @param nodeService the node service + */ + public void setDbNodeService(NodeService nodeService) + { + this.dbNodeService = nodeService; + } + + /** + * @param searcher the searcher + */ + public void setSearcher(SearchService searcher) + { + this.searcher = searcher; + } + + /** + * @param versionCounterService the version counter service + */ + public void setVersionCounterService(VersionCounterService versionCounterService) + { + this.versionCounterService = versionCounterService; + } + + /** + * Set the policy behaviour filter + * + * @param policyBehaviourFilter the policy behaviour filter + */ + public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) + { + this.policyBehaviourFilter = policyBehaviourFilter; + } + + /** + * Initialise method + */ + @Override + public void initialise() + { + super.initialise(); + + // Register the serial version label behaviour + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "calculateVersionLabel"), + ContentModel.TYPE_CMOBJECT, + new JavaBehaviour(new SerialVersionLabelPolicy(), "calculateVersionLabel")); + + // Register the serial version label behaviour for the mlContainer too + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "calculateVersionLabel"), + ContentModel.TYPE_MULTILINGUAL_CONTAINER, + new JavaBehaviour(new SerialVersionLabelPolicy(), "calculateVersionLabel")); + } + + // TODO - temp + protected void initialiseWithoutBind() + { + super.initialise(); + } + + /** + * Gets the reference to the version store + * + * @return reference to the version store + */ + @Override + public StoreRef getVersionStoreReference() + { + return new StoreRef( + StoreRef.PROTOCOL_WORKSPACE, + VersionModel.STORE_ID); + } + + /** + * @see VersionCounterService#nextVersionNumber(StoreRef) + */ + public Version createVersion( + NodeRef nodeRef, + Map versionProperties) + throws ReservedVersionNameException, AspectMissingException + { + long startTime = System.currentTimeMillis(); + + // Get the next version number + int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); + + // Create the version + Version version = createVersion(nodeRef, versionProperties, versionNumber); + + if (logger.isDebugEnabled()) + { + logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in " + (System.currentTimeMillis()-startTime) + " ms"); + } + + return version; + } + + /** + * The version's are created from the children upwards with the parent being created first. This will + * ensure that the child version references in the version node will point to the version history nodes + * for the (possibly) newly created version histories. + */ + public Collection createVersion( + NodeRef nodeRef, + Map versionProperties, + boolean versionChildren) + throws ReservedVersionNameException, AspectMissingException + { + long startTime = System.currentTimeMillis(); + + // Get the next version number + int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); + + // Create the versions + Collection versions = createVersion(nodeRef, versionProperties, versionChildren, versionNumber); + + if (logger.isDebugEnabled()) + { + Version[] versionsArray = versions.toArray(new Version[0]); + Version version = versionsArray[versionsArray.length -1]; // last item is the new parent version + logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in "+ (System.currentTimeMillis()-startTime) +" ms "+(versionChildren ? "(with " + (versions.size() - 1) + " children)" : "")); + } + + return versions; + } + + /** + * Helper method used to create the version when the versionChildren flag is provided. This method + * ensures that all the children (if the falg is set to true) are created with the same version + * number, this ensuring that the version stripe is correct. + * + * @param nodeRef the parent node reference + * @param versionProperties the version properties + * @param versionChildren indicates whether to version the children of the parent + * node + * @param versionNumber the version number + + * @return a collection of the created versions + * @throws ReservedVersionNameException thrown if there is a reserved version property name clash + * @throws AspectMissingException thrown if the version aspect is missing from a node + */ + private Collection createVersion( + NodeRef nodeRef, + Map versionProperties, + boolean versionChildren, + int versionNumber) + throws ReservedVersionNameException, AspectMissingException + { + + Collection result = new ArrayList(); + + if (versionChildren == true) + { + // Get the children of the node + Collection children = this.dbNodeService.getChildAssocs(nodeRef); + for (ChildAssociationRef childAssoc : children) + { + // Recurse into this method to version all the children with the same version number + Collection childVersions = createVersion( + childAssoc.getChildRef(), + versionProperties, + versionChildren, + versionNumber); + result.addAll(childVersions); + } + } + + result.add(createVersion(nodeRef, versionProperties, versionNumber)); + + return result; + } + + /** + * Note: we can't control the order of the list, so if we have children and parents in the list and the + * parents get versioned before the children and the children are not already versioned then the parents + * child references will be pointing to the node ref, rather than the verison history. + */ + public Collection createVersion( + Collection nodeRefs, + Map versionProperties) + throws ReservedVersionNameException, AspectMissingException + { + long startTime = System.currentTimeMillis(); + + Collection result = new ArrayList(nodeRefs.size()); + + // Get the next version number + int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); + + // Version each node in the list + for (NodeRef nodeRef : nodeRefs) + { + result.add(createVersion(nodeRef, versionProperties, versionNumber)); + } + + if (logger.isDebugEnabled()) + { + logger.debug("created version list (" + versionNumber + " - " + getVersionStoreReference() + ") in "+ (System.currentTimeMillis()-startTime) +" ms (with " + nodeRefs.size() + " nodes)"); + } + + return result; + } + + /** + * Creates a new version of the passed node assigning the version properties + * accordingly. + * + * @param nodeRef a node reference + * @param versionProperties the version properties + * @param versionNumber the version number + * @return the newly created version + * @throws ReservedVersionNameException + * thrown if there is a name clash in the version properties + */ + protected Version createVersion( + NodeRef nodeRef, + Map origVersionProperties, + int versionNumber) + throws ReservedVersionNameException + { + long startTime = System.currentTimeMillis(); + + // Copy the version properties (to prevent unexpected side effects to the caller) + Map versionProperties = new HashMap(); + if (origVersionProperties != null) + { + versionProperties.putAll(origVersionProperties); + } + + // If the version aspect is not there then add it + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + } + + // Call the policy behaviour + invokeBeforeCreateVersion(nodeRef); + + // Check that the supplied additional version properties do not clash with the reserved ones + VersionUtil.checkVersionPropertyNames(versionProperties.keySet()); + + // Check the repository for the version history for this node + NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); + NodeRef currentVersionRef = null; + + if (versionHistoryRef == null) + { + // Create the version history + versionHistoryRef = createVersionHistory(nodeRef); + } + else + { + // Since we have an exisiting version history we should be able to lookup + // the current version + currentVersionRef = getCurrentVersionNodeRef(versionHistoryRef, nodeRef); + + if (currentVersionRef == null) + { + throw new VersionServiceException(MSGID_ERR_NOT_FOUND); + } + + // Need to check that we are not about to create branch since this is not currently supported + VersionHistory versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); + Version currentVersion = getVersion(currentVersionRef); + if (versionHistory.getSuccessors(currentVersion).size() != 0) + { + throw new VersionServiceException(MSGID_ERR_NO_BRANCHES); + } + } + + // Create the node details + QName classRef = this.nodeService.getType(nodeRef); + PolicyScope nodeDetails = new PolicyScope(classRef); + + // Get the node details by calling the onVersionCreate policy behaviour + invokeOnCreateVersion(nodeRef, versionProperties, nodeDetails); + + // Create the new version node (child of the version history) + NodeRef newVersionRef = createNewVersion( + nodeRef, + versionHistoryRef, + getStandardVersionProperties(versionProperties, nodeRef, currentVersionRef, versionNumber), + versionProperties, + nodeDetails); + + if (currentVersionRef == null) + { + // Set the new version to be the root version in the version history + this.dbNodeService.createAssociation( + versionHistoryRef, + newVersionRef, + VersionServiceImpl.ASSOC_ROOT_VERSION); + } + else + { + // Relate the new version to the current version as its successor + this.dbNodeService.createAssociation( + currentVersionRef, + newVersionRef, + VersionServiceImpl.ASSOC_SUCCESSOR); + } + + // Create the version data object + Version version = this.getVersion(newVersionRef); + + // Set the new version label on the versioned node + this.nodeService.setProperty( + nodeRef, + ContentModel.PROP_VERSION_LABEL, + version.getVersionLabel()); + + // Freeze the version label property + Map versionLabelAsMap = new HashMap(1); + versionLabelAsMap.put(ContentModel.PROP_VERSION_LABEL, version.getVersionLabel()); + this.freezeProperties(newVersionRef, versionLabelAsMap); + + // Invoke the policy behaviour + invokeAfterCreateVersion(nodeRef, version); + + if (logger.isTraceEnabled()) + { + logger.trace("created Version (" + versionNumber + " - " + getVersionStoreReference() + ") " + nodeRef + " in " + (System.currentTimeMillis()-startTime) +" ms"); + } + + // Return the data object representing the newly created version + return version; + } + + /** + * Creates a new version history node, applying the root version aspect is required + * + * @param nodeRef the node ref + * @return the version history node reference + */ + private NodeRef createVersionHistory(NodeRef nodeRef) + { + HashMap props = new HashMap(); + props.put(ContentModel.PROP_NAME, nodeRef.getId()); + props.put(PROP_QNAME_VERSIONED_NODE_ID, nodeRef.getId()); + + // Create a new version history node + ChildAssociationRef childAssocRef = this.dbNodeService.createNode( + getRootNode(), + CHILD_QNAME_VERSION_HISTORIES, + QName.createQName(VersionModel.NAMESPACE_URI, nodeRef.getId()), + TYPE_QNAME_VERSION_HISTORY, + props); + return childAssocRef.getChildRef(); + } + + /** + * @see org.alfresco.service.cmr.version.VersionService#getVersionHistory(NodeRef) + */ + public VersionHistory getVersionHistory(NodeRef nodeRef) + { + VersionHistory versionHistory = null; + + if (this.nodeService.exists(nodeRef) == true) + { + NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryRef != null) + { + versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); + } + } + + return versionHistory; + } + + /** + * @see VersionService#getCurrentVersion(NodeRef) + */ + public Version getCurrentVersion(NodeRef nodeRef) + { + Version version = null; + + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + { + VersionHistory versionHistory = getVersionHistory(nodeRef); + if (versionHistory != null) + { + String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + version = versionHistory.getVersion(versionLabel); + } + } + + return version; + } + + /** + * Get a map containing the standard list of version properties populated. + * + * @param versionProperties the version meta data properties + * @param nodeRef the node reference + * @param preceedingNodeRef the preceeding node reference + * @param versionNumber the version number + * @return the standard version properties + */ + private Map getStandardVersionProperties(Map versionProperties, NodeRef nodeRef, NodeRef preceedingNodeRef, int versionNumber) + { + Map result = new HashMap(10); + + // Set the version number for the new version + result.put(VersionModel.PROP_QNAME_VERSION_NUMBER, Integer.toString(versionNumber)); + + // Set the versionable node id + result.put(VersionModel.PROP_QNAME_FROZEN_NODE_ID, nodeRef.getId()); + + // Set the versionable node store protocol + result.put(VersionModel.PROP_QNAME_FROZEN_NODE_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol()); + + // Set the versionable node store id + result.put(VersionModel.PROP_QNAME_FROZEN_NODE_STORE_ID, nodeRef.getStoreRef().getIdentifier()); + + // Store the current node type + QName nodeType = this.nodeService.getType(nodeRef); + result.put(VersionModel.PROP_QNAME_FROZEN_NODE_TYPE, nodeType); + + // Store the current aspects + Set aspects = this.nodeService.getAspects(nodeRef); + result.put(VersionModel.PROP_QNAME_FROZEN_ASPECTS, (Serializable)aspects); + + // Calculate the version label + QName classRef = this.nodeService.getType(nodeRef); + Version preceedingVersion = getVersion(preceedingNodeRef); + String versionLabel = invokeCalculateVersionLabel(classRef, preceedingVersion, versionNumber, versionProperties); + result.put(VersionModel.PROP_QNAME_VERSION_LABEL, versionLabel); + + return result; + } + + /** + * Creates a new version node, setting the properties both calculated and specified. + * + * @param versionableNodeRef the reference to the node being versioned + * @param versionHistoryRef version history node reference + * @param preceedingNodeRef the version node preceeding this in the version history + * , null if none + * @param versionProperties version properties + * @param versionNumber the version number + * @return the version node reference + */ + private NodeRef createNewVersion( + NodeRef versionableNodeRef, + NodeRef versionHistoryRef, + Map standardVersionProperties, + Map versionProperties, + PolicyScope nodeDetails) + { + // Create the new version + ChildAssociationRef childAssocRef = this.dbNodeService.createNode( + versionHistoryRef, + CHILD_QNAME_VERSIONS, + CHILD_QNAME_VERSIONS, + TYPE_QNAME_VERSION, + standardVersionProperties); + NodeRef versionNodeRef = childAssocRef.getChildRef(); + + // Store the meta data + storeVersionMetaData(versionNodeRef, versionProperties); + + // Freeze the various parts of the node + freezeProperties(versionNodeRef, nodeDetails.getProperties()); + freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations()); + freezeAssociations(versionNodeRef, nodeDetails.getAssociations()); + freezeAspects(nodeDetails, versionNodeRef, nodeDetails.getAspects()); + + // Return the created node reference + return versionNodeRef; + } + + /** + * Store the version meta data + * + * @param versionNodeRef the version node reference + * @param versionProperties the version properties + */ + private void storeVersionMetaData(NodeRef versionNodeRef, Map versionProperties) + { + for (Map.Entry entry : versionProperties.entrySet()) + { + HashMap properties = new HashMap(); + + properties.put(PROP_QNAME_META_DATA_NAME, entry.getKey()); + properties.put(PROP_QNAME_META_DATA_VALUE, entry.getValue()); + + this.dbNodeService.createNode( + versionNodeRef, + CHILD_QNAME_VERSION_META_DATA, + CHILD_QNAME_VERSION_META_DATA, + TYPE_QNAME_VERSION_META_DATA_VALUE, + properties); + } + } + + protected Map getVersionMetaData(NodeRef versionNodeRef) + { + // Get the meta data + List metaData = this.dbNodeService.getChildAssocs( + versionNodeRef, + RegexQNamePattern.MATCH_ALL, + CHILD_QNAME_VERSION_META_DATA); + + Map versionProperties = new HashMap(metaData.size()); + + for (ChildAssociationRef ref : metaData) + { + NodeRef metaDataValue = (NodeRef)ref.getChildRef(); + String name = (String)this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_NAME); + Serializable value = this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_VALUE); + versionProperties.put(name, value); + } + + return versionProperties; + } + + /** + * Freeze the aspects + * + * @param nodeDetails the node details + * @param versionNodeRef the version node reference + * @param aspects the set of aspects + */ + private void freezeAspects(PolicyScope nodeDetails, NodeRef versionNodeRef, Set aspects) + { + for (QName aspect : aspects) + { + // Freeze the details of the aspect + freezeProperties(versionNodeRef, nodeDetails.getProperties(aspect)); + freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations(aspect)); + freezeAssociations(versionNodeRef, nodeDetails.getAssociations(aspect)); + } + } + + /** + * Freeze associations + * + * @param versionNodeRef the version node reference + * @param associations the list of associations + */ + private void freezeAssociations(NodeRef versionNodeRef, List associations) + { + for (AssociationRef targetAssoc : associations) + { + HashMap properties = new HashMap(); + + // Set the qname of the association + properties.put(PROP_QNAME_ASSOC_TYPE_QNAME, targetAssoc.getTypeQName()); + + // Set the reference property to point to the child node + properties.put(ContentModel.PROP_REFERENCE, targetAssoc.getTargetRef()); + + // Create child version reference + this.dbNodeService.createNode( + versionNodeRef, + CHILD_QNAME_VERSIONED_ASSOCS, + CHILD_QNAME_VERSIONED_ASSOCS, + TYPE_QNAME_VERSIONED_ASSOC, + properties); + } + } + + /** + * Freeze child associations + * + * @param versionNodeRef the version node reference + * @param childAssociations the child associations + */ + private void freezeChildAssociations(NodeRef versionNodeRef, List childAssociations) + { + for (ChildAssociationRef childAssocRef : childAssociations) + { + HashMap properties = new HashMap(); + + // Set the qname, isPrimary and nthSibling properties + properties.put(PROP_QNAME_ASSOC_QNAME, childAssocRef.getQName()); + properties.put(PROP_QNAME_ASSOC_TYPE_QNAME, childAssocRef.getTypeQName()); + properties.put(PROP_QNAME_IS_PRIMARY, Boolean.valueOf(childAssocRef.isPrimary())); + properties.put(PROP_QNAME_NTH_SIBLING, Integer.valueOf(childAssocRef.getNthSibling())); + + // Set the reference property to point to the child node + properties.put(ContentModel.PROP_REFERENCE, childAssocRef.getChildRef()); + + // Create child version reference + this.dbNodeService.createNode( + versionNodeRef, + CHILD_QNAME_VERSIONED_CHILD_ASSOCS, + CHILD_QNAME_VERSIONED_CHILD_ASSOCS, + TYPE_QNAME_VERSIONED_CHILD_ASSOC, + properties); + } + } + + /** + * Freeze properties + * + * @param versionNodeRef the version node reference + * @param properties the properties + */ + private void freezeProperties(NodeRef versionNodeRef, Map properties) + { + // Copy the property values from the node onto the version node + for (Map.Entry entry : properties.entrySet()) + { + // Get the property values + HashMap props = new HashMap(); + props.put(PROP_QNAME_QNAME, entry.getKey()); + + if (entry.getValue() instanceof Collection) + { + props.put(PROP_QNAME_MULTI_VALUE, entry.getValue()); + props.put(PROP_QNAME_IS_MULTI_VALUE, true); + } + else + { + props.put(PROP_QNAME_VALUE, entry.getValue()); + props.put(PROP_QNAME_IS_MULTI_VALUE, false); + } + + // Create the node storing the frozen attribute details + this.dbNodeService.createNode( + versionNodeRef, + CHILD_QNAME_VERSIONED_ATTRIBUTES, + CHILD_QNAME_VERSIONED_ATTRIBUTES, + TYPE_QNAME_VERSIONED_PROPERTY, + props); + } + } + + /** + * Gets the version stores root node + * + * @return the node ref to the root node of the version store + */ + protected NodeRef getRootNode() + { + // Get the version store root node reference + return this.dbNodeService.getRootNode(getVersionStoreReference()); + } + + /** + * Builds a version history object from the version history reference. + *

+ * The node ref is passed to enable the version history to be scoped to the + * appropriate branch in the version history. + * + * @param versionHistoryRef the node ref for the version history + * @param nodeRef the node reference + * @return a constructed version history object + */ + protected VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef) + { + VersionHistory versionHistory = null; + + ArrayList versionHistoryNodeRefs = new ArrayList(); + + NodeRef currentVersion; + if (this.nodeService.exists(nodeRef)) + { + currentVersion = getCurrentVersionNodeRef(versionHistoryRef, nodeRef); + } + else + { + currentVersion = VersionUtil.convertNodeRef(getLatestVersion(nodeRef).getFrozenStateNodeRef()); + } + + while (currentVersion != null) + { + AssociationRef preceedingVersion = null; + + versionHistoryNodeRefs.add(0, currentVersion); + + List preceedingVersions = this.dbNodeService.getSourceAssocs( + currentVersion, + VersionModel.ASSOC_SUCCESSOR); + if (preceedingVersions.size() == 1) + { + preceedingVersion = (AssociationRef)preceedingVersions.toArray()[0]; + currentVersion = preceedingVersion.getSourceRef(); + } + else if (preceedingVersions.size() > 1) + { + // Error since we only currently support one preceeding version + throw new VersionServiceException(MSGID_ERR_ONE_PRECEEDING); + } + else + { + currentVersion = null; + } + } + + // Build the version history object + boolean isRoot = true; + Version preceeding = null; + for (NodeRef versionRef : versionHistoryNodeRefs) + { + Version version = getVersion(versionRef); + + if (isRoot == true) + { + versionHistory = new VersionHistoryImpl(version); + isRoot = false; + } + else + { + ((VersionHistoryImpl)versionHistory).addVersion(version, preceeding); + } + preceeding = version; + } + + return versionHistory; + } + + /** + * Constructs the a version object to contain the version information from the version node ref. + * + * @param versionRef the version reference + * @return object containing verison data + */ + protected Version getVersion(NodeRef versionRef) + { + if (versionRef == null) + { + return null; + } + Map versionProperties = new HashMap(); + + // Get the standard node details + Map nodeProperties = this.dbNodeService.getProperties(versionRef); + for (QName key : nodeProperties.keySet()) + { + Serializable value = nodeProperties.get(key); + versionProperties.put(key.getLocalName(), value); + } + + // Get the meta data + Map versionMetaDataProperties = getVersionMetaData(versionRef); + versionProperties.putAll(versionMetaDataProperties); + + // Create and return the version object + NodeRef newNodeRef = new NodeRef(new StoreRef(STORE_PROTOCOL, STORE_ID), versionRef.getId()); + Version result = new VersionImpl(versionProperties, newNodeRef); + // done + return result; + } + + /** + * Gets a reference to the version history node for a given 'real' node. + * + * @param nodeRef a node reference + * @return a reference to the version history node, null of none + */ + protected NodeRef getVersionHistoryNodeRef(NodeRef nodeRef) + { + if (nodeService.exists(nodeRef)) + { + return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_UUID)); + } + else + { + return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, nodeRef.getId()); + } + } + + /** + * Gets a reference to the node for the current version of the passed node ref. + * + * This uses the version label as a mechanism for looking up the version node in + * the version history. + * + * @param nodeRef a node reference + * @return a reference to a version reference + */ + private NodeRef getCurrentVersionNodeRef(NodeRef versionHistory, NodeRef nodeRef) + { + NodeRef result = null; + String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + + Collection versions = this.dbNodeService.getChildAssocs(versionHistory); + for (ChildAssociationRef version : versions) + { + String tempLabel = (String)this.dbNodeService.getProperty(version.getChildRef(), VersionModel.PROP_QNAME_VERSION_LABEL); + if (tempLabel != null && tempLabel.equals(versionLabel) == true) + { + result = version.getChildRef(); + break; + } + } + + return result; + } + + /** + * @see org.alfresco.cms.version.VersionService#revert(NodeRef) + */ + public void revert(NodeRef nodeRef) + { + revert(nodeRef, getCurrentVersion(nodeRef), true); + } + + /** + * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, boolean) + */ + public void revert(NodeRef nodeRef, boolean deep) + { + revert(nodeRef, getCurrentVersion(nodeRef), deep); + } + + /** + * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version) + */ + public void revert(NodeRef nodeRef, Version version) + { + revert(nodeRef, version, true); + } + + /** + * @see org.alfresco.service.cmr.version.VersionService#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version, boolean) + */ + public void revert(NodeRef nodeRef, Version version, boolean deep) + { + // Check the mandatory parameters + ParameterCheck.mandatory("nodeRef", nodeRef); + ParameterCheck.mandatory("version", version); + + // Cross check that the version provided relates to the node reference provided + if (nodeRef.getId().equals(version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID)) == false) + { + // Error since the version provided does not correspond to the node reference provided + throw new VersionServiceException(MSGID_ERR_REVERT_MISMATCH); + } + + // Turn off any auto-version policy behaviours + this.policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); + try + { + // Store the current version label + String currentVersionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + + // Get the node that represents the frozen state + NodeRef versionNodeRef = version.getFrozenStateNodeRef(); + + // Revert the property values + this.nodeService.setProperties(nodeRef, this.nodeService.getProperties(versionNodeRef)); + + // Apply/remove the aspects as required + Set aspects = new HashSet(this.nodeService.getAspects(nodeRef)); + for (QName versionAspect : this.nodeService.getAspects(versionNodeRef)) + { + if (aspects.contains(versionAspect) == false) + { + this.nodeService.addAspect(nodeRef, versionAspect, null); + } + else + { + aspects.remove(versionAspect); + } + } + for (QName aspect : aspects) + { + this.nodeService.removeAspect(nodeRef, aspect); + } + + // Re-add the versionable aspect to the reverted node + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + } + + // Re-set the version label property (since it should not be modified from the origional) + this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, currentVersionLabel); + + // Add/remove the child nodes + List children = new ArrayList(this.nodeService.getChildAssocs(nodeRef)); + for (ChildAssociationRef versionedChild : this.nodeService.getChildAssocs(versionNodeRef)) + { + if (children.contains(versionedChild) == false) + { + if (this.nodeService.exists(versionedChild.getChildRef()) == true) + { + // The node was a primary child of the parent, but that is no longer the case. Dispite this + // the node still exits so this means it has been moved. + // The best thing to do in this situation will be to re-add the node as a child, but it will not + // be a primary child. + this.nodeService.addChild(nodeRef, versionedChild.getChildRef(), versionedChild.getTypeQName(), versionedChild.getQName()); + } + else + { + if (versionedChild.isPrimary() == true) + { + // Only try to resotre missing children if we are doing a deep revert + // Look and see if we have a version history for the child node + if (deep == true && getVersionHistoryNodeRef(versionedChild.getChildRef()) != null) + { + // We're going to try and restore the missing child node and recreate the assoc + restore( + versionedChild.getChildRef(), + nodeRef, + versionedChild.getTypeQName(), + versionedChild.getQName()); + } + // else the deleted child did not have a version history so we can't restore the child + // and so we can't revert the association + } + + // else + // Since this was never a primary assoc and the child has been deleted we won't recreate + // the missing node as it was never owned by the node and we wouldn't know where to put it. + } + } + else + { + children.remove(versionedChild); + } + } + for (ChildAssociationRef ref : children) + { + this.nodeService.removeChild(nodeRef, ref.getChildRef()); + } + + // Add/remove the target associations + for (AssociationRef assocRef : this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)) + { + this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName()); + } + for (AssociationRef versionedAssoc : this.nodeService.getTargetAssocs(versionNodeRef, RegexQNamePattern.MATCH_ALL)) + { + if (this.nodeService.exists(versionedAssoc.getTargetRef()) == true) + { + this.nodeService.createAssociation(nodeRef, versionedAssoc.getTargetRef(), versionedAssoc.getTypeQName()); + } + + // else + // Since the tareget of the assoc no longer exists we can't recreate the assoc + } + } + finally + { + // Turn auto-version policies back on + this.policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); + } + } + + /** + * @see org.alfresco.service.cmr.version.VersionService#restore(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName) + */ + public NodeRef restore( + NodeRef nodeRef, + NodeRef parentNodeRef, + QName assocTypeQName, + QName assocQName) + { + return restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true); + } + + /** + * @see org.alfresco.service.cmr.version.VersionService#restore(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 restore( + NodeRef nodeRef, + NodeRef parentNodeRef, + QName assocTypeQName, + QName assocQName, + boolean deep) + { + NodeRef restoredNodeRef = null; + + // Check that the node does not exist + if (this.nodeService.exists(nodeRef) == true) + { + // Error since you can not restore a node that already exists + throw new VersionServiceException(MSGID_ERR_RESTORE_EXISTS, new Object[]{nodeRef.toString()}); + } + + // Try and get the version details that we want to restore to + Version version = getHeadVersion(nodeRef); + if (version == null) + { + // Error since there is no version information available to restore the node from + throw new VersionServiceException(MSGID_ERR_RESTORE_NO_VERSION, new Object[]{nodeRef.toString()}); + } + + // Set the uuid of the new node + Map props = new HashMap(1); + props.put(ContentModel.PROP_NODE_UUID, version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID)); + + // Get the type of the node node + QName type = (QName)version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_TYPE); + + // Disable auto-version behaviour + this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE); + try + { + // Create the restored node + restoredNodeRef = this.nodeService.createNode( + parentNodeRef, + assocTypeQName, + assocQName, + type, + props).getChildRef(); + } + finally + { + // Enable auto-version behaviour + this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE); + } + + // Now we need to revert the newly restored node + revert(restoredNodeRef, version, deep); + + return restoredNodeRef; + } + + /** + * Get the head version given a node reference + * + * @param nodeRef the node reference + * @return the 'head' version + */ + private Version getHeadVersion(NodeRef nodeRef) + { + Version version = null; + StoreRef storeRef = nodeRef.getStoreRef(); + + NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryNodeRef != null) + { + List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS); + for (ChildAssociationRef versionAssoc : versionsAssoc) + { + NodeRef versionNodeRef = versionAssoc.getChildRef(); + List successors = this.dbNodeService.getTargetAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR); + if (successors.size() == 0) + { + String storeProtocol = (String)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL)); + String storeId = (String)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID)); + StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId); + if (storeRef.equals(versionStoreRef) == true) + { + version = getVersion(versionNodeRef); + } + } + } + } + + return version; + } + + private Version getLatestVersion(NodeRef nodeRef) + { + Version version = null; + StoreRef storeRef = nodeRef.getStoreRef(); + + NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryNodeRef != null) + { + List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS); + for (ChildAssociationRef versionAssoc : versionsAssoc) + { + NodeRef versionNodeRef = versionAssoc.getChildRef(); + List predecessors = this.dbNodeService.getSourceAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR); + if (predecessors.size() == 0) + { + String storeProtocol = (String)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL)); + String storeId = (String)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID)); + StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId); + if (storeRef.equals(versionStoreRef) == true) + { + version = getVersion(versionNodeRef); + } + } + } + } + + return version; + } + + /** + * @see org.alfresco.cms.version.VersionService#deleteVersionHistory(NodeRef) + */ + public void deleteVersionHistory(NodeRef nodeRef) + throws AspectMissingException + { + // Get the version history node for the node is question and delete it + NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); + + if (versionHistoryNodeRef != null) + { + // Delete the version history node + this.dbNodeService.deleteNode(versionHistoryNodeRef); + + if (this.nodeService.exists(nodeRef) == true && this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) + { + // Reset the version label property on the versionable node + this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, null); + } + } + } + + public void deleteVersion(NodeRef nodeRef, Version version) + { + // This operation is not supported for a version1 store + throw new UnsupportedOperationException("Delete version is unsupported by the old (deprecated) version store implementation"); + } + + @Override + protected void defaultOnCreateVersion( + QName classRef, + NodeRef nodeRef, + Map versionProperties, + PolicyScope nodeDetails) + { + ClassDefinition classDefinition = this.dictionaryService.getClass(classRef); + if (classDefinition != null) + { + boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); + try + { + // Copy the properties + Map propertyDefinitions = classDefinition.getProperties(); + for (QName propertyName : propertyDefinitions.keySet()) + { + Serializable propValue = this.nodeService.getProperty(nodeRef, propertyName); + nodeDetails.addProperty(classRef, propertyName, propValue); + } + } + finally + { + MLPropertyInterceptor.setMLAware(wasMLAware); + } + + // Version 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(nodeRef); + for (ChildAssociationRef childAssocRef : childAssocRefs) + { + if (assocDefs.containsKey(childAssocRef.getTypeQName())) + { + nodeDetails.addChildAssociation(classDefinition.getName(), childAssocRef); + } + } + } + + // TODO: Need way of getting assocs of a given type + List nodeAssocRefs = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); + for (AssociationRef nodeAssocRef : nodeAssocRefs) + { + if (assocDefs.containsKey(nodeAssocRef.getTypeQName())) + { + nodeDetails.addAssociation(classDefinition.getName(), nodeAssocRef); + } + } + } + } +} diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 6f8ffb77b2..bf375eda54 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -25,8 +25,10 @@ package org.alfresco.repo.version; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -216,6 +218,28 @@ public class VersionServiceImplTest extends BaseVersionStoreTest } } + private void CheckVersionHistory(VersionHistory vh, List expectedVersions) + { + if (vh == null) + { + assertNull(expectedVersions); + } + else + { + Iterator itr = expectedVersions.iterator(); + + for (Version version : vh.getAllVersions()) + { + Version expectedVersion = itr.next(); + + assertEquals(version.getVersionLabel(), expectedVersion.getVersionLabel()); + assertEquals(version.getFrozenStateNodeRef(), expectedVersion.getFrozenStateNodeRef()); + } + + assertFalse(itr.hasNext()); + } + } + /** * Tests the version history */ @@ -512,6 +536,141 @@ public class VersionServiceImplTest extends BaseVersionStoreTest } + /** + * Test deleteVersion + */ + public void testDeleteVersion() + { + // Create a versionable node + NodeRef versionableNode = createNewVersionableNode(); + + // Check that there is no version history + VersionHistory versionHistory = this.versionService.getVersionHistory(versionableNode); + CheckVersionHistory(versionHistory, null); + + // Check that the current version property on the versionable node is not set + String versionLabel = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); + assertNull(versionLabel); + + // Check that there is no current version + Version version = this.versionService.getCurrentVersion(versionableNode); + assertNull(version); + + // Create a couple of versions + Version version0 = createVersion(versionableNode); + Version version1 = createVersion(versionableNode); + + // Check that the version label is correct on the versionable node + String versionLabel1 = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); + assertEquals("1.1", versionLabel1); + assertEquals(version1.getVersionLabel(), versionLabel1); + + // Check the version history + List expectedVersions = new ArrayList(2); + expectedVersions.add(version1); + expectedVersions.add(version0); + versionHistory = this.versionService.getVersionHistory(versionableNode); + assertEquals(2, versionHistory.getAllVersions().size()); + CheckVersionHistory(versionHistory, expectedVersions); + + // Check current version + Version currentVersion = this.versionService.getCurrentVersion(versionableNode); + assertEquals(currentVersion.getVersionLabel(), version1.getVersionLabel()); + assertEquals(currentVersion.getFrozenStateNodeRef(), version1.getFrozenStateNodeRef()); + + // Create a couple more versions + Version version2 = createVersion(versionableNode); + Version version3 = createVersion(versionableNode); + + // Check that the version label is correct on the versionable node + String versionLabel3 = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); + assertEquals("1.3", versionLabel3); + assertEquals(version3.getVersionLabel(), versionLabel3); + + // Check the version history + expectedVersions = new ArrayList(4); + expectedVersions.add(version3); + expectedVersions.add(version2); + expectedVersions.add(version1); + expectedVersions.add(version0); + versionHistory = this.versionService.getVersionHistory(versionableNode); + assertEquals(4, versionHistory.getAllVersions().size()); + CheckVersionHistory(versionHistory, expectedVersions); + + // Check current version + currentVersion = this.versionService.getCurrentVersion(versionableNode); + assertEquals(currentVersion.getVersionLabel(), version3.getVersionLabel()); + assertEquals(currentVersion.getFrozenStateNodeRef(), version3.getFrozenStateNodeRef()); + + // Delete version 2 + this.versionService.deleteVersion(versionableNode, version2); + + // Delete version 0 + this.versionService.deleteVersion(versionableNode, version0); + + // Check the version history + expectedVersions = new ArrayList(2); + expectedVersions.add(version3); + expectedVersions.add(version1); + versionHistory = this.versionService.getVersionHistory(versionableNode); + assertEquals(2, versionHistory.getAllVersions().size()); + CheckVersionHistory(versionHistory, expectedVersions); + + // Check current version is unchanged + currentVersion = this.versionService.getCurrentVersion(versionableNode); + assertEquals(currentVersion.getVersionLabel(), version3.getVersionLabel()); + assertEquals(currentVersion.getFrozenStateNodeRef(), version3.getFrozenStateNodeRef()); + + // Delete version 3 + this.versionService.deleteVersion(versionableNode, version3); + + // Check the version history size + expectedVersions = new ArrayList(1); + expectedVersions.add(version1); + versionHistory = this.versionService.getVersionHistory(versionableNode); + assertEquals(1, versionHistory.getAllVersions().size()); + CheckVersionHistory(versionHistory, expectedVersions); + + // Check current version has changed to version 1 + currentVersion = this.versionService.getCurrentVersion(versionableNode); + assertEquals(currentVersion.getVersionLabel(), version1.getVersionLabel()); + assertEquals(currentVersion.getFrozenStateNodeRef(), version1.getFrozenStateNodeRef()); + + // Create version 4 + Version version4 = createVersion(versionableNode); + + // Check the version history size + expectedVersions = new ArrayList(2); + expectedVersions.add(version4); + expectedVersions.add(version1); + versionHistory = this.versionService.getVersionHistory(versionableNode); + assertEquals(2, versionHistory.getAllVersions().size()); + CheckVersionHistory(versionHistory, expectedVersions); + + // Check current version has changed to version 4 + currentVersion = this.versionService.getCurrentVersion(versionableNode); + assertEquals(currentVersion.getVersionLabel(), version4.getVersionLabel()); + assertEquals(currentVersion.getFrozenStateNodeRef(), version4.getFrozenStateNodeRef()); + + // Delete version 1 + this.versionService.deleteVersion(versionableNode, version1); + + // Delete version 4 + this.versionService.deleteVersion(versionableNode, version4); + + // Check the version history is empty + versionHistory = this.versionService.getVersionHistory(versionableNode); + CheckVersionHistory(versionHistory, null); + + // Check that the current version property on the versionable node is no longer set + versionLabel = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); + assertNull(versionLabel); + + // Check that there is no current version + version = this.versionService.getCurrentVersion(versionableNode); + assertNull(version); + } + public void testAutoVersionOnInitialVersionOn() { // Create a versionable node diff --git a/source/java/org/alfresco/service/cmr/version/VersionService.java b/source/java/org/alfresco/service/cmr/version/VersionService.java index a6572b6a4a..712a74712c 100644 --- a/source/java/org/alfresco/service/cmr/version/VersionService.java +++ b/source/java/org/alfresco/service/cmr/version/VersionService.java @@ -1,285 +1,300 @@ -/* - * 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.service.cmr.version; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Map; - -import org.alfresco.service.Auditable; -import org.alfresco.service.PublicService; -import org.alfresco.service.cmr.repository.AspectMissingException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.QName; - -/** - * Interface for public and internal version operations. - * - * @author Roy Wetherall - */ -@PublicService -public interface VersionService -{ - /** - * The version store protocol label, used in store references - */ - public static final String VERSION_STORE_PROTOCOL = "versionStore"; - - /** - * Gets the reference to the version store - * - * @return reference to the version store - */ - @Auditable - public StoreRef getVersionStoreReference(); - - /** - * Creates a new version based on the referenced node. - *

- * If the node has not previously been versioned then a version history and - * initial version will be created. - *

- * If the node referenced does not or can not have the version aspect - * applied to it then an exception will be raised. - *

- * The version properties are stored as version meta-data against the newly - * created version. - * - * @param nodeRef a node reference - * @param versionProperties the version properties that are stored with the newly created - * version - * @return the created version object - * @throws ReservedVersionNameException - * thrown if a reserved property name is used int he version properties - * provided - * @throws AspectMissingException - * thrown if the version aspect is missing - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "versionProperties"}) - public Version createVersion( - NodeRef nodeRef, - Map versionProperties) - throws ReservedVersionNameException, AspectMissingException; - - /** - * Creates a new version based on the referenced node. - *

- * If the node has not previously been versioned then a version history and - * initial version will be created. - *

- * If the node referenced does not or can not have the version aspect - * applied to it then an exception will be raised. - *

- * The version properties are stored as version meta-data against the newly - * created version. - * - * @param nodeRef a node reference - * @param versionProperties the version properties that are stored with the newly created - * version - * @param versionChildren if true then the children of the referenced node are also - * versioned, false otherwise - * @return the created version object(s) - * @throws ReservedVersionNameException - * thrown if a reserved property name is used int he version properties - * provided - * @throws AspectMissingException - * thrown if the version aspect is missing - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "versionProperties", "versionChildren"}) - public Collection createVersion( - NodeRef nodeRef, - Map versionProperties, - boolean versionChildren) - throws ReservedVersionNameException, AspectMissingException; - - /** - * Creates new versions based on the list of node references provided. - * - * @param nodeRefs a list of node references - * @param versionProperties version property values - * @return a collection of newly created versions - * @throws ReservedVersionNameException - * thrown if a reserved property name is used int he version properties - * provided - * @throws AspectMissingException - * thrown if the version aspect is missing - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "versionProperties"}) - public Collection createVersion( - Collection nodeRefs, - Map versionProperties) - throws ReservedVersionNameException, AspectMissingException; - - /** - * Gets the version history information for a node. - *

- * If the node has not been versioned then null is returned. - *

- * If the node referenced does not or can not have the version aspect - * applied to it then an exception will be raised. - * - * @param nodeRef a node reference - * @return the version history information - * @throws AspectMissingException - * thrown if the version aspect is missing - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) - public VersionHistory getVersionHistory(NodeRef nodeRef) - throws AspectMissingException; - - /** - * Gets the version object for the current version of the node reference - * passed. - *

- * Returns null if the node is not versionable or has not been versioned. - * @param nodeRef the node reference - * @return the version object for the current version - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) - public Version getCurrentVersion(NodeRef nodeRef); - - /** - * The node reference will be reverted to the current version. - *

- * A deep revert will be performed. - * - * @see VersionService#revert(NodeRef, Version, boolean) - * - * @param nodeRef the node reference - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) - public void revert(NodeRef nodeRef); - - /** - * The node reference will be reverted to the current version. - * - * @see VersionService#revert(NodeRef, Version, boolean) - * - * @param nodeRef the node reference - * @param deep true if a deep revert is to be performed, flase otherwise - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "deep"}) - public void revert(NodeRef nodeRef, boolean deep); - - /** - * A deep revert will take place by default. - * - * @see VersionService#revert(NodeRef, Version, boolean) - * - * @param nodeRef the node reference - * @param version the version to revert to - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "version"}) - public void revert(NodeRef nodeRef, Version version); - - /** - * Revert the state of the node to the specified version. - *

- * Any changes made to the node will be lost and the state of the node will reflect - * that of the version specified. - *

- * The version label property on the node reference will remain unchanged. - *

- * If the node is further versioned then the new version will be created at the head of - * the version history graph. A branch will not be created. - *

- * If a deep revert is to be performed then any child nodes that are no longer present will - * be deep restored (if appropriate) otherwise child associations to deleted, versioned nodes - * will not be restored. - * - * @param nodeRef the node reference - * @param version the version to revert to - * @param deep true is a deep revert is to be performed, false otherwise. - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "version", "deep"}) - public void revert(NodeRef nodeRef, Version version, boolean deep); - - /** - * By default a deep restore is performed. - * - * @see org.alfresco.service.cmr.version.VersionService#restore(NodeRef, NodeRef, QName, QName, boolean) - * - * @param nodeRef the node reference to a node that no longer exists in the store - * @param parentNodeRef the new parent of the restored node - * @param assocTypeQName the assoc type qname - * @param assocQName the assoc qname - * @return the newly restored node reference - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "parentNodeRef", "assocTypeQName", "assocQName"}) - public NodeRef restore( - NodeRef nodeRef, - NodeRef parentNodeRef, - QName assocTypeQName, - QName assocQName); - - /** - * Restores a node not currently present in the store, but that has a version - * history. - *

- * The restored node will be at the head (most resent version). - *

- * Restoration will fail if there is no version history for the specified node id in - * the specified store. - *

- * If the node already exists in the store then an exception will be raised. - *

- * Once the node is restored it is reverted to the head version in the appropriate - * version history tree. If deep is set to true then this will be a deep revert, false - * otherwise. - * - * @param nodeRef the node reference to a node that no longer exists in - * the store - * @param parentNodeRef the new parent of the restored node - * @param assocTypeQName the assoc type qname - * @param assocQName the assoc qname - * @param deep true is a deep revert should be performed once the node has been - * restored, false otherwise - * @return the newly restored node reference - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "parentNodeRef", "assocTypeQName", "assocQName", "deep"}) - public NodeRef restore( - NodeRef nodeRef, - NodeRef parentNodeRef, - QName assocTypeQName, - QName assocQName, - boolean deep); - - /** - * Delete the version history associated with a node reference. - *

- * This operation is permanent, all versions in the version history are - * deleted and cannot be retrieved. - *

- * The current version label for the node reference is reset and any subsequent versions - * of the node will result in a new version history being created. - * - * @param nodeRef the node reference - * @throws AspectMissingException thrown if the version aspect is missing - */ - @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) - public void deleteVersionHistory(NodeRef nodeRef) - throws AspectMissingException; -} +/* + * Copyright (C) 2005-2008 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.service.cmr.version; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; + +import org.alfresco.service.Auditable; +import org.alfresco.service.PublicService; +import org.alfresco.service.cmr.repository.AspectMissingException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; + +/** + * Interface for public and internal version operations. + * + * @author Roy Wetherall, janv + */ +@PublicService +public interface VersionService +{ + /** + * The version store protocol label, used in store references + */ + public static final String VERSION_STORE_PROTOCOL = "versionStore"; + + /** + * Gets the reference to the version store + * + * @return reference to the version store + */ + @Auditable + public StoreRef getVersionStoreReference(); + + /** + * Creates a new version based on the referenced node. + *

+ * If the node has not previously been versioned then a version history and + * initial version will be created. + *

+ * If the node referenced does not or can not have the version aspect + * applied to it then an exception will be raised. + *

+ * The version properties are stored as version meta-data against the newly + * created version. + * + * @param nodeRef a node reference + * @param versionProperties the version properties that are stored with the newly created + * version + * @return the created version object + * @throws ReservedVersionNameException + * thrown if a reserved property name is used int he version properties + * provided + * @throws AspectMissingException + * thrown if the version aspect is missing + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "versionProperties"}) + public Version createVersion( + NodeRef nodeRef, + Map versionProperties) + throws ReservedVersionNameException, AspectMissingException; + + /** + * Creates a new version based on the referenced node. + *

+ * If the node has not previously been versioned then a version history and + * initial version will be created. + *

+ * If the node referenced does not or can not have the version aspect + * applied to it then an exception will be raised. + *

+ * The version properties are stored as version meta-data against the newly + * created version. + * + * @param nodeRef a node reference + * @param versionProperties the version properties that are stored with the newly created + * version + * @param versionChildren if true then the children of the referenced node are also + * versioned, false otherwise + * @return the created version object(s) + * @throws ReservedVersionNameException + * thrown if a reserved property name is used int he version properties + * provided + * @throws AspectMissingException + * thrown if the version aspect is missing + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "versionProperties", "versionChildren"}) + public Collection createVersion( + NodeRef nodeRef, + Map versionProperties, + boolean versionChildren) + throws ReservedVersionNameException, AspectMissingException; + + /** + * Creates new versions based on the list of node references provided. + * + * @param nodeRefs a list of node references + * @param versionProperties version property values + * @return a collection of newly created versions + * @throws ReservedVersionNameException + * thrown if a reserved property name is used int he version properties + * provided + * @throws AspectMissingException + * thrown if the version aspect is missing + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "versionProperties"}) + public Collection createVersion( + Collection nodeRefs, + Map versionProperties) + throws ReservedVersionNameException, AspectMissingException; + + /** + * Gets the version history information for a node. + *

+ * If the node has not been versioned then null is returned. + *

+ * If the node referenced does not or can not have the version aspect + * applied to it then an exception will be raised. + * + * @param nodeRef a node reference + * @return the version history information + * @throws AspectMissingException + * thrown if the version aspect is missing + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) + public VersionHistory getVersionHistory(NodeRef nodeRef) + throws AspectMissingException; + + /** + * Gets the version object for the current version of the node reference + * passed. + *

+ * Returns null if the node is not versionable or has not been versioned. + * @param nodeRef the node reference + * @return the version object for the current version + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) + public Version getCurrentVersion(NodeRef nodeRef); + + /** + * The node reference will be reverted to the current version. + *

+ * A deep revert will be performed. + * + * @see VersionService#revert(NodeRef, Version, boolean) + * + * @param nodeRef the node reference + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) + public void revert(NodeRef nodeRef); + + /** + * The node reference will be reverted to the current version. + * + * @see VersionService#revert(NodeRef, Version, boolean) + * + * @param nodeRef the node reference + * @param deep true if a deep revert is to be performed, flase otherwise + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "deep"}) + public void revert(NodeRef nodeRef, boolean deep); + + /** + * A deep revert will take place by default. + * + * @see VersionService#revert(NodeRef, Version, boolean) + * + * @param nodeRef the node reference + * @param version the version to revert to + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "version"}) + public void revert(NodeRef nodeRef, Version version); + + /** + * Revert the state of the node to the specified version. + *

+ * Any changes made to the node will be lost and the state of the node will reflect + * that of the version specified. + *

+ * The version label property on the node reference will remain unchanged. + *

+ * If the node is further versioned then the new version will be created at the head of + * the version history graph. A branch will not be created. + *

+ * If a deep revert is to be performed then any child nodes that are no longer present will + * be deep restored (if appropriate) otherwise child associations to deleted, versioned nodes + * will not be restored. + * + * @param nodeRef the node reference + * @param version the version to revert to + * @param deep true is a deep revert is to be performed, false otherwise. + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "version", "deep"}) + public void revert(NodeRef nodeRef, Version version, boolean deep); + + /** + * By default a deep restore is performed. + * + * @see org.alfresco.service.cmr.version.VersionService#restore(NodeRef, NodeRef, QName, QName, boolean) + * + * @param nodeRef the node reference to a node that no longer exists in the store + * @param parentNodeRef the new parent of the restored node + * @param assocTypeQName the assoc type qname + * @param assocQName the assoc qname + * @return the newly restored node reference + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "parentNodeRef", "assocTypeQName", "assocQName"}) + public NodeRef restore( + NodeRef nodeRef, + NodeRef parentNodeRef, + QName assocTypeQName, + QName assocQName); + + /** + * Restores a node not currently present in the store, but that has a version + * history. + *

+ * The restored node will be at the head (most resent version). + *

+ * Restoration will fail if there is no version history for the specified node id in + * the specified store. + *

+ * If the node already exists in the store then an exception will be raised. + *

+ * Once the node is restored it is reverted to the head version in the appropriate + * version history tree. If deep is set to true then this will be a deep revert, false + * otherwise. + * + * @param nodeRef the node reference to a node that no longer exists in + * the store + * @param parentNodeRef the new parent of the restored node + * @param assocTypeQName the assoc type qname + * @param assocQName the assoc qname + * @param deep true is a deep revert should be performed once the node has been + * restored, false otherwise + * @return the newly restored node reference + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "parentNodeRef", "assocTypeQName", "assocQName", "deep"}) + public NodeRef restore( + NodeRef nodeRef, + NodeRef parentNodeRef, + QName assocTypeQName, + QName assocQName, + boolean deep); + + /** + * Delete the version history associated with a node reference. + *

+ * This operation is permanent, all versions in the version history are + * deleted and cannot be retrieved. + *

+ * The current version label for the node reference is reset and any subsequent versions + * of the node will result in a new version history being created. + * + * @param nodeRef the node reference + * @throws AspectMissingException thrown if the version aspect is missing + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) + public void deleteVersionHistory(NodeRef nodeRef) + throws AspectMissingException; + + /** + * Delete a specific version associated with a node reference. + *

+ * This operation is permanent, the specific version in the version history is + * deleted and cannot be retrieved. + *

+ * If this is the last version, then the current version label for the node reference is + * reset and any subsequent versions of the node will result in a new version history being created. + * + * @param nodeRef the node reference + * @param version the version to delete + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "version"}) + public void deleteVersion(NodeRef nodeRef, Version version); +}