2006-08-27 01:01:30 +00:00

1092 lines
43 KiB
Java

/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
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.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.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;
/**
* The version service implementation.
*
* @author Roy Wetheral
*/
public class VersionServiceImpl extends AbstractVersionServiceImpl
implements VersionService, VersionModel
{
/**
* Error message I18N id's
*/
private static final String MSGID_ERR_NOT_FOUND = "version_service.err_not_found";
private static final String MSGID_ERR_NO_BRANCHES = "version_service.err_unsupported";
private static final String MSGID_ERR_RESTORE_EXISTS = "version_service.err_restore_exists";
private static final String MSGID_ERR_ONE_PRECEEDING = "version_service.err_one_preceeding";
private static final String MSGID_ERR_RESTORE_NO_VERSION = "version_service.err_restore_no_version";
private static final String MSGID_ERR_REVERT_MISMATCH = "version_service.err_revert_mismatch";
/**
* The version counter service
*/
private VersionCounterService versionCounterService;
/**
* The db node service, used as the version store implementation
*/
protected NodeService dbNodeService;
/**
* Policy behaviour filter
*/
private BehaviourFilter policyBehaviourFilter;
/**
* The repository searcher
*/
@SuppressWarnings("unused")
private SearchService searcher;
/**
* 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"));
}
/**
* Gets the reference to the version store
*
* @return reference to the version store
*/
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
{
// Get the next version number
int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference());
// Create the version
return createVersion(nodeRef, versionProperties, versionNumber);
}
/**
* 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
{
// Get the next version number
int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference());
// Create the versions
return createVersion(nodeRef, versionProperties, versionChildren, versionNumber);
}
/**
* 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
{
Collection<Version> result = new ArrayList<Version>(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));
}
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
*/
private Version createVersion(
NodeRef nodeRef,
Map<String, Serializable> origVersionProperties,
int versionNumber)
throws ReservedVersionNameException
{
// 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)
{
HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(PROP_QNAME_VERSIONED_NODE_ID, nodeRef.getId());
// Create a new version history node
ChildAssociationRef childAssocRef = this.dbNodeService.createNode(
getRootNode(),
ContentModel.ASSOC_CHILDREN,
CHILD_QNAME_VERSION_HISTORIES,
TYPE_QNAME_VERSION_HISTORY,
props);
versionHistoryRef = childAssocRef.getChildRef();
}
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 = getVersion(newVersionRef);
// Set the new version label on the versioned node
this.nodeService.setProperty(
nodeRef,
ContentModel.PROP_VERSION_LABEL,
version.getVersionLabel());
// Invoke the policy behaviour
invokeAfterCreateVersion(nodeRef, version);
// Return the data object representing the newly created version
return version;
}
/**
* @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);
// Set the version number for the new version
result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_VERSION_NUMBER), Integer.toString(versionNumber));
// Set the versionable node id
result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_ID), nodeRef.getId());
// Set the versionable node store protocol
result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL), nodeRef.getStoreRef().getProtocol());
// Set the versionable node store id
result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID), nodeRef.getStoreRef().getIdentifier());
// Store the current node type
QName nodeType = this.nodeService.getType(nodeRef);
result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_TYPE), nodeType);
// Store the current aspects
Set<QName> aspects = this.nodeService.getAspects(nodeRef);
result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_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(QName.createQName(NAMESPACE_URI, VersionModel.PROP_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);
}
}
/**
* 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
*/
private 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
*/
private VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef)
{
VersionHistory versionHistory = null;
ArrayList<NodeRef> versionHistoryNodeRefs = new ArrayList<NodeRef>();
NodeRef currentVersion = getCurrentVersionNodeRef(versionHistoryRef, nodeRef);
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
*/
private 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
List<ChildAssociationRef> metaData = this.dbNodeService.getChildAssocs(
versionRef,
RegexQNamePattern.MATCH_ALL,
CHILD_QNAME_VERSION_META_DATA);
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);
}
// 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
*/
private NodeRef getVersionHistoryNodeRef(NodeRef nodeRef)
{
NodeRef result = null;
Collection<ChildAssociationRef> versionHistories = this.dbNodeService.getChildAssocs(getRootNode());
for (ChildAssociationRef versionHistory : versionHistories)
{
String nodeId = (String)this.dbNodeService.getProperty(versionHistory.getChildRef(), VersionModel.PROP_QNAME_VERSIONED_NODE_ID);
if (nodeId != null && nodeId.equals(nodeRef.getId()) == true)
{
result = versionHistory.getChildRef();
break;
}
}
return result;
}
/**
* 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#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;
}
/**
* @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);
}
}
}
}