Dave Ward b58125d078 Merged V4.1-BUG-FIX to HEAD
42804: Merged BRANCHES/DEV/BELARUS/V4.1-BUG-FIX-2012_10_17 to BRANCHES/DEV/V4.1-BUG-FIX:
      42748: ALF-14200: Adding Invalid Aspects Via CMIS ATOM API Results in NullPointerException
   42810: Fix for ALF-15276 - sys:locale Attribute No Longer Available From jsnode
   42814: ALF-15276 - small improvement to remove duplicated data from response
   42824: ALF-15048: Merged PATCHES/V4.0.2 to V4.1-BUG-FIX
        42724: ALF-16048: CLONE - Version history doesn't go beyond two versions (0.1 and 0.2) when dragged and dropped via CIFS from Mac Lion OSx
        42739: ALF-16048: New files missing from previous check in
        42742: ALF-16048: Another missing file.
   42839: ALF-16417: Fix "Hybrid Sync - can retain invalid cloud tickets in a local cache"
      - retry once for invalid auth 
      - also externalise the implicit/default cache config
   42849: NodeDAO: Added new method to retrieve specific store ID
    - public Pair<Long, StoreRef> getStore(StoreRef storeRef);
   42857: Merged DEV to V4.1-BUG-FIX
      42821: ALF-13506 : WCMQS Example Application Caching Causes Changes to Inconsistently Appear on the Editorial Web Site
             Concurrency was improved for AssetImpl class.
             The returned values of the collections were made unmodifiable in the classes which implement Resource interface.
   42872: ALF-15601: "Performance issue using CMIS method getChildren() - gets version history"
   - avoids getting the version history (an expensive operation) if possible i.e. in the case of current version (live) nodes like for getChildren
   42900: Merged DEV to V4.1-BUG-FIX
      42734: ALF-15335 : 'external' authentication subsystem debug information too scarce
         Extended debug information in the authentication subsystem.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@42904 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-10-21 18:09:03 +00:00

1326 lines
53 KiB
Java

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.version;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.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.VersionServicePolicies.CalculateVersionLabelPolicy;
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.service.cmr.dictionary.AspectDefinition;
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.cmr.version.VersionType;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.ParameterCheck;
/**
* Version1 Service - implements lightWeightVersionStore
*
* @author Roy Wetheral
*
* NOTE: deprecated since 3.1 (migrate and use Version2 Service)
*/
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 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;
}
/**
* Set the policy behaviour filter
*
* @param policyBehaviourFilter the policy behaviour filter
*/
public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter)
{
this.policyBehaviourFilter = policyBehaviourFilter;
}
/**
* Register version label policy for the specified type
*
* @param typeQName
* @param policy
*/
public void registerVersionLabelPolicy(QName typeQName, CalculateVersionLabelPolicy policy)
{
// Register the serial version label behaviour
this.policyComponent.bindClassBehaviour(
QName.createQName(NamespaceService.ALFRESCO_URI, "calculateVersionLabel"),
typeQName,
new JavaBehaviour(policy, "calculateVersionLabel"));
}
/**
* Initialise method
*/
@Override
public void initialise()
{
super.initialise();
}
// 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<String, Serializable> versionProperties)
throws ReservedVersionNameException, AspectMissingException
{
long startTime = System.currentTimeMillis();
int versionNumber = 0; // deprecated (unused)
// Create the version
Version version = createVersion(nodeRef, versionProperties, versionNumber);
if (logger.isDebugEnabled())
{
logger.debug("created version (" + 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<Version> createVersion(
NodeRef nodeRef,
Map<String, Serializable> versionProperties,
boolean versionChildren)
throws ReservedVersionNameException, AspectMissingException
{
long startTime = System.currentTimeMillis();
int versionNumber = 0; // deprecated (unused)
// Create the versions
Collection<Version> 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 (" + 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<Version> createVersion(
NodeRef nodeRef,
Map<String, Serializable> versionProperties,
boolean versionChildren,
int versionNumber)
throws ReservedVersionNameException, AspectMissingException
{
Collection<Version> result = new ArrayList<Version>();
if (versionChildren == true)
{
// Get the children of the node
Collection<ChildAssociationRef> children = this.dbNodeService.getChildAssocs(nodeRef);
for (ChildAssociationRef childAssoc : children)
{
// Recurse into this method to version all the children with the same version number
Collection<Version> 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<Version> createVersion(
Collection<NodeRef> nodeRefs,
Map<String, Serializable> versionProperties)
throws ReservedVersionNameException, AspectMissingException
{
long startTime = System.currentTimeMillis();
Collection<Version> result = new ArrayList<Version>(nodeRefs.size());
int versionNumber = 0; // deprecated (unused)
// Version each node in the list
for (NodeRef nodeRef : nodeRefs)
{
result.add(createVersion(nodeRef, versionProperties, versionNumber));
}
if (logger.isDebugEnabled())
{
logger.debug("created version list (" + 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<String, Serializable> origVersionProperties,
int versionNumber)
throws ReservedVersionNameException
{
long startTime = System.currentTimeMillis();
// Copy the version properties (to prevent unexpected side effects to the caller)
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>();
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<QName, Serializable> versionLabelAsMap = new HashMap<QName, Serializable>(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 (" + 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<QName, Serializable> props = new HashMap<QName, Serializable>();
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<QName, Serializable> getStandardVersionProperties(Map<String, Serializable> versionProperties, NodeRef nodeRef, NodeRef preceedingNodeRef, int versionNumber)
{
Map<QName, Serializable> result = new HashMap<QName, Serializable>(10);
// deprecated (unused)
//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<QName> 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<QName, Serializable> standardVersionProperties,
Map<String, Serializable> 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<String, Serializable> versionProperties)
{
for (Map.Entry<String, Serializable> entry : versionProperties.entrySet())
{
HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();
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<String, Serializable> getVersionMetaData(NodeRef versionNodeRef)
{
// Get the meta data
List<ChildAssociationRef> metaData = this.dbNodeService.getChildAssocs(
versionNodeRef,
RegexQNamePattern.MATCH_ALL,
CHILD_QNAME_VERSION_META_DATA);
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>(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<QName> 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<AssociationRef> associations)
{
for (AssociationRef targetAssoc : associations)
{
HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();
// 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<ChildAssociationRef> childAssociations)
{
for (ChildAssociationRef childAssocRef : childAssociations)
{
HashMap<QName, Serializable> properties = new HashMap<QName, Serializable>();
// 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<QName, Serializable> properties)
{
// Copy the property values from the node onto the version node
for (Map.Entry<QName, Serializable> entry : properties.entrySet())
{
// Get the property values
HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();
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.
* <p>
* 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<NodeRef> versionHistoryNodeRefs = new ArrayList<NodeRef>();
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<AssociationRef> 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<String, Serializable> versionProperties = new HashMap<String, Serializable>();
// Get the standard node details
Map<QName, Serializable> 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<String, Serializable> 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<ChildAssociationRef> 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#ensureVersioningEnabled(NodeRef,Map)
*/
public void ensureVersioningEnabled(NodeRef nodeRef, Map<QName, Serializable> versionProperties)
{
// Don't alter the auditable aspect!
boolean disableAuditable = policyBehaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE);
if(disableAuditable)
{
policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
// Do we need to apply the aspect?
if (! nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE))
{
// Only apply new properties that are version ones
AspectDefinition versionable =
dictionaryService.getAspect(ContentModel.ASPECT_VERSIONABLE);
Set<QName> versionAspectProperties =
versionable.getProperties().keySet();
Map<QName,Serializable> props = new HashMap<QName, Serializable>();
if(versionProperties != null &&! versionProperties.isEmpty())
{
for(QName prop : versionProperties.keySet())
{
if(versionAspectProperties.contains(prop))
{
// This property is one from the versionable aspect
props.put(prop, versionProperties.get(prop));
}
}
}
// Add the aspect
nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props);
}
// Do we need to create the initial version history entry? By convention this is always a major version.
if(getVersionHistory(nodeRef) == null)
{
createVersion(nodeRef, Collections.<String,Serializable>singletonMap(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR));
}
// Put Auditable back
if(disableAuditable)
{
policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
/**
* @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<QName> aspects = new HashSet<QName>(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<ChildAssociationRef> children = new ArrayList<ChildAssociationRef>(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<QName, Serializable> props = new HashMap<QName, Serializable>(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<ChildAssociationRef> versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS);
for (ChildAssociationRef versionAssoc : versionsAssoc)
{
NodeRef versionNodeRef = versionAssoc.getChildRef();
List<AssociationRef> 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<ChildAssociationRef> versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS);
for (ChildAssociationRef versionAssoc : versionsAssoc)
{
NodeRef versionNodeRef = versionAssoc.getChildRef();
List<AssociationRef> 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<String, Serializable> versionProperties,
PolicyScope nodeDetails)
{
ClassDefinition classDefinition = this.dictionaryService.getClass(classRef);
if (classDefinition != null)
{
boolean wasMLAware = MLPropertyInterceptor.setMLAware(true);
try
{
// Copy the properties
Map<QName,PropertyDefinition> 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<QName, AssociationDefinition> assocDefs = classDefinition.getAssociations();
// TODO: Need way of getting child assocs of a given type
if (classDefinition.isContainer())
{
List<ChildAssociationRef> 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<AssociationRef> nodeAssocRefs = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL);
for (AssociationRef nodeAssocRef : nodeAssocRefs)
{
if (assocDefs.containsKey(nodeAssocRef.getTypeQName()))
{
nodeDetails.addAssociation(classDefinition.getName(), nodeAssocRef);
}
}
}
}
@Override
public boolean isAVersion(NodeRef nodeRef)
{
throw new UnsupportedOperationException();
}
@Override
public boolean isVersioned(NodeRef nodeRef)
{
return this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE);
}
}