/* * #%L * Alfresco Records Management Module * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * - * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * - * 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/>. * #L% */ package org.alfresco.module.org_alfresco_module_rm.version; import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementCustomModel.CUSTOM_REF_VERSIONS; import static org.codehaus.plexus.util.StringUtils.isNotBlank; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.rma.type.CmObjectType; import org.alfresco.module.org_alfresco_module_rm.model.security.ModelSecurityService; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.relationship.RelationshipService; import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; import org.alfresco.repo.policy.PolicyScope; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.impl.ExtendedPermissionService; import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.version.Version2ServiceImpl; import org.alfresco.repo.version.VersionModel; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; 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.VersionType; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyMap; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; /** * Recordable version service implementation * * @author Roy Wetherall * @since 2.3 */ public class RecordableVersionServiceImpl extends Version2ServiceImpl implements RecordableVersionModel, RecordableVersionService { /** share logger with version2ServiceImpl */ private static Log logger = LogFactory.getLog(Version2ServiceImpl.class); /** key used to indicate a recordable version */ public static final String KEY_RECORDABLE_VERSION = "recordable-version"; public static final String KEY_FILE_PLAN = "file-plan"; /** version record property */ protected static final String PROP_VERSION_RECORD = "RecordVersion"; protected static final String PROP_RECORDED_VERSION_DESTROYED = "RecordedVersionDestroyed"; /** I18N */ private static final String AUTO_VERSION_ON_RECORD_CREATION = "rm.service.enable-autoversion-on-record-creation"; /** flag that enable auto-version on record creation */ private boolean isEnableAutoVersionOnRecordCreation = false; /** version aspect property names */ private static final String[] VERSION_PROPERTY_NAMES = new String[] { Version2Model.PROP_CREATED_DATE, Version2Model.PROP_VERSION_LABEL, Version2Model.PROP_VERSION_DESCRIPTION, Version2Model.PROP_FROZEN_NODE_DBID, Version2Model.PROP_FROZEN_NODE_REF, Version2Model.PROP_FROZEN_CREATED, Version2Model.PROP_FROZEN_CREATOR, Version2Model.PROP_FROZEN_MODIFIED, Version2Model.PROP_FROZEN_MODIFIER, Version2Model.PROP_FROZEN_ACCESSED }; /** file plan service */ private FilePlanService filePlanService; /** authentication util helper */ private AuthenticationUtil authenticationUtil; /** relationship service */ private RelationshipService relationshipService; /** record service */ private RecordService recordService; /** model security service */ private ModelSecurityService modelSecurityService; /** cm object type */ private CmObjectType cmObjectType; /** extended permission service */ private ExtendedPermissionService extendedPermissionService; /** extended security service */ private ExtendedSecurityService extendedSecurityService; /** * @param filePlanService file plan service */ public void setFilePlanService(FilePlanService filePlanService) { this.filePlanService = filePlanService; } /** * @param authenticationUtil authentication util helper */ public void setAuthenticationUtil(AuthenticationUtil authenticationUtil) { this.authenticationUtil = authenticationUtil; } /** * @param relationshipService relationship service */ public void setRelationshipService(RelationshipService relationshipService) { this.relationshipService = relationshipService; } /** * @param recordService record service */ public void setRecordService(RecordService recordService) { this.recordService = recordService; } /** * @param modelSecurityService model security service */ public void setModelSecurityService(ModelSecurityService modelSecurityService) { this.modelSecurityService = modelSecurityService; } /** * @param cmObjectType the cmObjectType to set */ public void setCmObjectType(CmObjectType cmObjectType) { this.cmObjectType = cmObjectType; } /** * @param extendedPermissionService extended permission service */ public void setExtendedPermissionService(ExtendedPermissionService extendedPermissionService) { this.extendedPermissionService = extendedPermissionService; } /** * @param extendedSecurityService extended security service */ public void setExtendedSecurityService(ExtendedSecurityService extendedSecurityService) { this.extendedSecurityService = extendedSecurityService; } /** * @param isEnableAutoVersionOnRecordCreation */ public void setEnableAutoVersionOnRecordCreation(boolean isEnableAutoVersionOnRecordCreation) { this.isEnableAutoVersionOnRecordCreation = isEnableAutoVersionOnRecordCreation; } public boolean isEnableAutoVersionOnRecordCreation() { return isEnableAutoVersionOnRecordCreation; } /** * @see org.alfresco.repo.version.Version2ServiceImpl#createVersion(org.alfresco.service.cmr.repository.NodeRef, * java.util.Map, int) */ @Override protected Version createVersion(NodeRef nodeRef, Map<String, Serializable> origVersionProperties, int versionNumber) throws ReservedVersionNameException { // TODO we only support recorded versions for sub types of cm:content // create version properties if null if (origVersionProperties == null) { origVersionProperties = new HashMap<String, Serializable>(2); } // only need to check the recordable version policy when the recordable version indicator is missing from the version properties if (!origVersionProperties.containsKey(KEY_RECORDABLE_VERSION)) { // get the version type VersionType versionType = null; if (origVersionProperties != null) { versionType = (VersionType) origVersionProperties.get(VersionModel.PROP_VERSION_TYPE); } // determine whether this is a recorded version or not if (isCreateRecordedVersion(nodeRef, versionType)) { origVersionProperties.put(KEY_RECORDABLE_VERSION, true); if (!origVersionProperties.containsKey(KEY_FILE_PLAN)) { // make sure the file plan is set to the default if not specified origVersionProperties.put(KEY_FILE_PLAN, getFilePlan(nodeRef)); } } } else { if (!origVersionProperties.containsKey(KEY_FILE_PLAN)) { // make sure the file plan is set to the default if not specified origVersionProperties.put(KEY_FILE_PLAN, getFilePlan(nodeRef)); } } return super.createVersion(nodeRef, origVersionProperties, versionNumber); } /** * @param nodeRef node reference * @return {@link NodeRef} associated file plan, default if none */ private NodeRef getFilePlan(NodeRef nodeRef) { NodeRef filePlan = (NodeRef) nodeService.getProperty(nodeRef, PROP_FILE_PLAN); if (filePlan == null) { filePlan = getFilePlan(); } return filePlan; } /** * @return {@link NodeRef} default file plan, exception if none */ private NodeRef getFilePlan() { return authenticationUtil.runAsSystem(new RunAsWork<NodeRef>() { @Override public NodeRef doWork() throws Exception { NodeRef filePlan = filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); if (filePlan == null) { throw new AlfrescoRuntimeException("Can't create a recorded version, because there is no file plan."); } return filePlan; } }); } /** * Determine whether this is a recorded version or not. * * @param nodeRef * @return */ private boolean isCreateRecordedVersion(NodeRef nodeRef, VersionType versionType) { boolean result = false; if (nodeService.hasAspect(nodeRef, RecordableVersionModel.ASPECT_VERSIONABLE)) { String policyString = (String) nodeService.getProperty(nodeRef, PROP_RECORDABLE_VERSION_POLICY); if (policyString != null) { RecordableVersionPolicy policy = RecordableVersionPolicy.valueOf(policyString.toUpperCase()); if (RecordableVersionPolicy.ALL.equals(policy) || (RecordableVersionPolicy.MAJOR_ONLY.equals(policy) && VersionType.MAJOR.equals(versionType))) { result = true; } } } return result; } /** * @see org.alfresco.repo.version.Version2ServiceImpl#createNewVersion(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map, int, org.alfresco.repo.policy.PolicyScope) */ @Override protected NodeRef createNewVersion( QName sourceTypeRef, NodeRef versionHistoryRef, Map<QName, Serializable> standardVersionProperties, Map<String, Serializable> versionProperties, int versionNumber, PolicyScope nodeDetails) { NodeRef version = null; if (versionProperties.containsKey(KEY_RECORDABLE_VERSION) && ((Boolean)versionProperties.get(KEY_RECORDABLE_VERSION)).booleanValue()) { // create a recorded version version = createNewRecordedVersion(sourceTypeRef, versionHistoryRef, standardVersionProperties, versionProperties, versionNumber, nodeDetails); } else { // create a normal version version = super.createNewVersion(sourceTypeRef, versionHistoryRef, standardVersionProperties, versionProperties, versionNumber, nodeDetails); } return version; } /** * Creates a new recorded version * * @param sourceTypeRef source type name * @param versionHistoryRef version history reference * @param standardVersionProperties standard version properties * @param versionProperties version properties * @param versionNumber version number * @param nodeDetails policy scope * @return {@link NodeRef} record version */ protected NodeRef createNewRecordedVersion(QName sourceTypeRef, NodeRef versionHistoryRef, Map<QName, Serializable> standardVersionProperties, Map<String, Serializable> versionProperties, int versionNumber, PolicyScope nodeDetails) { NodeRef versionNodeRef = null; // Disable auto-version behaviour policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE); // disable other behaviours that we don't want to trigger during this process policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); policyBehaviourFilter.disableBehaviour(ContentModel.TYPE_MULTILINGUAL_CONTAINER); // disable model security check modelSecurityService.disable(); // disable property editable check recordService.disablePropertyEditableCheck(); try { // get the destination file plan final NodeRef filePlan = (NodeRef) versionProperties.get(KEY_FILE_PLAN); if (filePlan == null) { throw new AlfrescoRuntimeException("Can't create a new recorded version, because no file plan has been specified in the version properties."); } // create a copy of the source node and place in the file plan final NodeRef nodeRef = (NodeRef) standardVersionProperties.get(Version2Model.PROP_QNAME_FROZEN_NODE_REF); cmObjectType.disableCopy(); try { // create record final NodeRef record = recordService.createRecordFromCopy(filePlan, nodeRef); // apply version record aspect to record final PropertyMap versionRecordProps = new PropertyMap(3); versionRecordProps.put(PROP_VERSIONED_NODEREF, nodeRef); versionRecordProps.put(RecordableVersionModel.PROP_VERSION_LABEL, standardVersionProperties.get( QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.PROP_VERSION_LABEL))); versionRecordProps.put(RecordableVersionModel.PROP_VERSION_DESCRIPTION, standardVersionProperties.get( QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.PROP_VERSION_DESCRIPTION))); // run as system as we can't be sure if the user has add aspect rights authenticationUtil.runAsSystem(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { nodeService.addAspect(record, ASPECT_VERSION_RECORD, versionRecordProps); return null; } }); // wire record up to previous record linkToPreviousVersionRecord(nodeRef, record); // create version nodeRef ChildAssociationRef childAssocRef = dbNodeService.createNode( versionHistoryRef, Version2Model.CHILD_QNAME_VERSIONS, QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.CHILD_VERSIONS + "-" + versionNumber), sourceTypeRef, null); versionNodeRef = childAssocRef.getChildRef(); // add aspect with the standard version properties to the 'version' node nodeService.addAspect(versionNodeRef, Version2Model.ASPECT_VERSION, standardVersionProperties); // add the recordedVersion aspect with link to record nodeService.addAspect(versionNodeRef, ASPECT_RECORDED_VERSION, Collections.singletonMap(PROP_RECORD_NODE_REF, (Serializable) record)); // freeze auditable aspect information freezeAuditableAspect(nodeRef, versionNodeRef); } finally { cmObjectType.enableCopy(); } } finally { // enable model security check modelSecurityService.enable(); // enable property editable check recordService.enablePropertyEditableCheck(); // Enable behaviours this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE); this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); this.policyBehaviourFilter.enableBehaviour(ContentModel.TYPE_MULTILINGUAL_CONTAINER); } // If the auditable aspect is not there then add it to the 'version' node (after original aspects have been frozen) if (!dbNodeService.hasAspect(versionNodeRef, ContentModel.ASPECT_AUDITABLE)) { dbNodeService.addAspect(versionNodeRef, ContentModel.ASPECT_AUDITABLE, null); } if (logger.isTraceEnabled()) { logger.trace("createNewRecordedVersion created (" + versionNumber + ") " + versionNodeRef); } return versionNodeRef; } /** * Helper method to link the record to the previous version record * * @param nodeRef noderef source node reference * @param record record record node reference */ private void linkToPreviousVersionRecord(final NodeRef nodeRef, final NodeRef record) { final NodeRef latestRecordVersion = getLatestVersionRecord(nodeRef); if (latestRecordVersion != null) { authenticationUtil.runAsSystem(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { // indicate that the new record versions the previous record relationshipService.addRelationship(CUSTOM_REF_VERSIONS.getLocalName(), record, latestRecordVersion); return null; } }); } } /** * Helper to get the latest version record for a given document (ie non-record) * * @param nodeRef node reference * @return NodeRef latest version record, null otherwise */ private NodeRef getLatestVersionRecord(NodeRef nodeRef) { NodeRef versionRecord = null; // wire record up to previous record VersionHistory versionHistory = getVersionHistory(nodeRef); if (versionHistory != null) { Collection<Version> previousVersions = versionHistory.getAllVersions(); for (Version previousVersion : previousVersions) { // look for the associated record final NodeRef previousRecord = (NodeRef) previousVersion.getVersionProperties().get(PROP_VERSION_RECORD); if (previousRecord != null && nodeService.exists(previousRecord)) { versionRecord = previousRecord; break; } } } return versionRecord; } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#getRecordedVersion(org.alfresco.service.cmr.repository.NodeRef) */ @Override public Version getRecordedVersion(NodeRef versionRecord) { Version version = null; NodeRef versionedNodeRef = (NodeRef) nodeService.getProperty(versionRecord, RecordableVersionModel.PROP_VERSIONED_NODEREF); if (versionedNodeRef != null) { String versionLabel = (String) nodeService.getProperty(versionRecord, RecordableVersionModel.PROP_VERSION_LABEL); if (StringUtils.isNotBlank(versionLabel)) { VersionHistory versionHistory = getVersionHistory(versionedNodeRef); if (versionHistory != null) { version = versionHistory.getVersion(versionLabel); } } } return version; } /** * Freezes audit aspect properties. * * @param nodeRef * @param versionNodeRef */ private void freezeAuditableAspect(NodeRef nodeRef, NodeRef versionNodeRef) { if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE)) { Map<QName, Serializable> properties = dbNodeService.getProperties(nodeRef); dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_CREATOR, properties.get(ContentModel.PROP_CREATOR)); dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_CREATED, properties.get(ContentModel.PROP_CREATED)); dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_MODIFIER, properties.get(ContentModel.PROP_MODIFIER)); dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_MODIFIED, properties.get(ContentModel.PROP_MODIFIED)); dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_ACCESSED, properties.get(ContentModel.PROP_ACCESSED)); if (properties.get(ContentModel.PROP_OWNER) != null) { dbNodeService.setProperty(versionNodeRef, PROP_FROZEN_OWNER, properties.get(ContentModel.PROP_OWNER)); } } } /** * @see org.alfresco.repo.version.Version2ServiceImpl#getVersion(org.alfresco.service.cmr.repository.NodeRef) */ @Override protected Version getVersion(NodeRef versionRef) { Version version = super.getVersion(versionRef); // place the version record reference in the version properties NodeRef record = (NodeRef) dbNodeService.getProperty(versionRef, PROP_RECORD_NODE_REF); if (record != null) { version.getVersionProperties().put(PROP_VERSION_RECORD, record); } // place information about the destruction of the version record in the properties Boolean destroyed = (Boolean) dbNodeService.getProperty(versionRef, PROP_DESTROYED); if (destroyed == null) { destroyed = Boolean.FALSE; } version.getVersionProperties().put(PROP_RECORDED_VERSION_DESTROYED, destroyed); return version; } /** * @see org.alfresco.repo.version.Version2ServiceImpl#revert(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.version.Version, boolean) */ @Override public void revert(NodeRef nodeRef, Version version, boolean deep) { String versionPolicy = (String) dbNodeService.getProperty(nodeRef, PROP_RECORDABLE_VERSION_POLICY); super.revert(nodeRef, version, deep); if (isNotBlank(versionPolicy)) { dbNodeService.setProperty(nodeRef, PROP_RECORDABLE_VERSION_POLICY, versionPolicy); } } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#isLatestVersionRecorded(org.alfresco.service.cmr.repository.NodeRef) */ @Override public boolean isCurrentVersionRecorded(NodeRef nodeRef) { boolean result = false; Version version = getCurrentVersion(nodeRef); if (version != null) { result = isRecordedVersion(version); } return result; } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#isRecordedVersion(org.alfresco.service.cmr.version.Version) */ @Override public boolean isRecordedVersion(Version version) { NodeRef versionNodeRef = getVersionNodeRef(version); return dbNodeService.hasAspect(versionNodeRef, RecordableVersionModel.ASPECT_RECORDED_VERSION); } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#getVersionRecord(org.alfresco.service.cmr.version.Version) */ @Override public NodeRef getVersionRecord(Version version) { NodeRef result = null; NodeRef versionNodeRef = getVersionNodeRef(version); if (dbNodeService.hasAspect(versionNodeRef, RecordableVersionModel.ASPECT_RECORDED_VERSION)) { // get the version record result = (NodeRef) dbNodeService.getProperty(versionNodeRef, RecordableVersionModel.PROP_RECORD_NODE_REF); // check that the version record exists if (result != null && !dbNodeService.exists(result)) { throw new AlfrescoRuntimeException("Version record node doesn't exist. Indicates version has not been updated " + "when associated version record was deleted. " + "(nodeRef=" + result.toString() + ")"); } } return result; } /** * Create Version Store Ref * * @param store ref * @return store ref for version store */ public StoreRef convertStoreRef(StoreRef storeRef) { return new StoreRef(StoreRef.PROTOCOL_WORKSPACE, storeRef.getIdentifier()); } /** * Convert the incomming node ref (with the version store protocol specified) * to the internal representation with the workspace protocol. * * @param nodeRef the incomming verison protocol node reference * @return the internal version node reference */ public NodeRef convertNodeRef(NodeRef nodeRef) { return new NodeRef(convertStoreRef(nodeRef.getStoreRef()), nodeRef.getId()); } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#createRecordFromLatestVersion(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, autoVersion) */ @Override public NodeRef createRecordFromLatestVersion(final NodeRef filePlan, final NodeRef nodeRef, final boolean isEnableAutoVersionOnRecordCreation) { setEnableAutoVersionOnRecordCreation(isEnableAutoVersionOnRecordCreation); return createRecordFromLatestVersion(filePlan, nodeRef); } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#createRecordFromLatestVersion(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) */ @Override public NodeRef createRecordFromLatestVersion(final NodeRef filePlan, final NodeRef nodeRef) { ParameterCheck.mandatory("filePlan", filePlan); ParameterCheck.mandatory("nodeRef", nodeRef); NodeRef record = null; // check for versionable aspect if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) { createSnapshotVersion(nodeRef); // get the latest version final Version currentVersion = getCurrentVersion(nodeRef); if (currentVersion != null && !isRecordedVersion(currentVersion)) { // create the record from the current frozen state record = authenticationUtil.runAsSystem(new RunAsWork<NodeRef>() { public NodeRef doWork() throws Exception { // get the documents readers and writers Pair<Set<String>, Set<String>> readersAndWriters = extendedPermissionService.getReadersAndWriters(nodeRef); // grab the frozen state NodeRef currentFrozenState = currentVersion.getFrozenStateNodeRef(); // determine the type of the object QName type = nodeService.getType(currentFrozenState); // grab all the properties Map<QName, Serializable> properties = nodeService.getProperties(currentFrozenState); // grab all the aspects Set<QName> aspects = nodeService.getAspects(currentFrozenState); // create the record NodeRef record = recordService.createRecordFromContent( filePlan, (String)properties.get(ContentModel.PROP_NAME), type, properties, null); // apply aspects to record for (QName aspect : aspects) { // add the aspect, properties have already been set nodeService.addAspect(record, aspect, null); } // apply version record aspect to record PropertyMap versionRecordProps = new PropertyMap(3); versionRecordProps.put(PROP_VERSIONED_NODEREF, nodeRef); versionRecordProps.put(RecordableVersionModel.PROP_VERSION_LABEL, currentVersion.getVersionLabel()); versionRecordProps.put(RecordableVersionModel.PROP_VERSION_DESCRIPTION, currentVersion.getDescription()); versionRecordProps.put(ContentModel.PROP_VERSION_TYPE, currentVersion.getVersionType()); nodeService.addAspect(record, ASPECT_VERSION_RECORD, versionRecordProps); // wire record up to previous record linkToPreviousVersionRecord(nodeRef, record); // set the extended security extendedSecurityService.set(record, readersAndWriters); return record; } }); // get the version history NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); // get details from the version before we remove it int versionNumber = getVersionNumber(currentVersion); Map<QName, Serializable> versionProperties = getVersionAspectProperties(currentVersion); QName sourceTypeRef = getVersionType(currentVersion); // patch-up owner information, which needs to be frozen for recorded versions String owner = (String) nodeService.getProperty(currentVersion.getFrozenStateNodeRef(), ContentModel.PROP_OWNER); if (owner != null) { versionProperties.put(PROP_FROZEN_OWNER, owner); } // delete the current version this.dbNodeService.deleteNode(convertNodeRef(currentVersion.getFrozenStateNodeRef())); // create a new version history if we need to if (!nodeService.exists(versionHistoryRef)) { versionHistoryRef = createVersionHistory(nodeRef); } // create recorded version nodeRef ChildAssociationRef childAssocRef = dbNodeService.createNode( versionHistoryRef, Version2Model.CHILD_QNAME_VERSIONS, QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.CHILD_VERSIONS + "-" + versionNumber), sourceTypeRef, null); NodeRef versionNodeRef = childAssocRef.getChildRef(); // add aspect with the standard version properties to the 'version' node nodeService.addAspect(versionNodeRef, Version2Model.ASPECT_VERSION, versionProperties); // add the recordedVersion aspect with link to record nodeService.addAspect(versionNodeRef, ASPECT_RECORDED_VERSION, Collections.singletonMap(PROP_RECORD_NODE_REF, (Serializable) record)); } } return record; } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#isRecordedVersionDestroyed(org.alfresco.service.cmr.version.Version) */ @Override public boolean isRecordedVersionDestroyed(Version version) { boolean result = false; // get the version node reference NodeRef versionNodeRef = getVersionNodeRef(version); // get the destroyed property value Boolean isDestroyed = (Boolean) dbNodeService.getProperty(versionNodeRef, PROP_DESTROYED); if (isDestroyed != null) { result = isDestroyed.booleanValue(); } return result; } /** * @see org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService#destroyRecordedVersion(org.alfresco.service.cmr.version.Version) */ @Override public void destroyRecordedVersion(Version version) { // get the version node reference NodeRef versionNodeRef = getVersionNodeRef(version); // if it's a recorded version if (dbNodeService.hasAspect(versionNodeRef, ASPECT_RECORDED_VERSION)) { // mark it as destroyed dbNodeService.setProperty(versionNodeRef, PROP_DESTROYED, true); // clear the record node reference property dbNodeService.setProperty(versionNodeRef, RecordableVersionModel.PROP_RECORD_NODE_REF, null); } } /** * Helper method to get the version number of a given version by inspecting the * name of the parent association. * * @param version version * @return int version number */ private int getVersionNumber(Version version) { NodeRef versionNodeRef = getVersionNodeRef(version); ChildAssociationRef assoc = dbNodeService.getPrimaryParent(versionNodeRef); String fullVersionNumber = assoc.getQName().getLocalName(); String versionNumber = fullVersionNumber.substring(fullVersionNumber.indexOf("-") + 1); return Integer.parseInt(versionNumber); } /** * Helper method to get all the version aspect properties from an existing version * * @param version version * @return Map<QName, Serializable> property values */ private Map<QName, Serializable> getVersionAspectProperties(Version version) { NodeRef versionNodeRef = getVersionNodeRef(version); Map<QName, Serializable> versionProps = dbNodeService.getProperties(versionNodeRef); Map<QName, Serializable> result = new HashMap<QName, Serializable>(9); for (String propertyName : VERSION_PROPERTY_NAMES) { QName propertyQName = QName.createQName(Version2Model.NAMESPACE_URI, propertyName); result.put(propertyQName, versionProps.get(propertyQName)); if (propertyName.equals(Version2Model.PROP_FROZEN_NODE_DBID)) { System.out.println(versionProps.get(propertyQName)); } } return result; } /** * Helper method to get the type of a versions frozen state * * @param currentVersion * @return */ private QName getVersionType(Version version) { return nodeService.getType(getVersionNodeRef(version)); } /** * Helper method to get the internal node reference of a version * * @param version version * @return NodeRef internal node reference to version */ private NodeRef getVersionNodeRef(Version version) { return convertNodeRef(version.getFrozenStateNodeRef()); } /** * Check if current version of the node is modified compared with versioned version * * @param nodeRef internal node reference * @return boolean true if nodeRef is modified, otherwise false */ public boolean isCurrentVersionDirty(NodeRef nodeRef) { if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) { return false; } // get the latest version Version currentVersion = getCurrentVersion(nodeRef); Date modificationDate = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); if (currentVersion == null) { return true; } // grab the frozen state NodeRef currentFrozenState = currentVersion.getFrozenStateNodeRef(); Date frozenModificationDate = (Date) nodeService.getProperty(currentFrozenState, ContentModel.PROP_MODIFIED); boolean versionStoreOutdated = ((frozenModificationDate != null) && (modificationDate.getTime() > frozenModificationDate.getTime())); return versionStoreOutdated; } /** * @see RecordableVersionService#createSnapshotVersion(NodeRef) */ public void createSnapshotVersion(NodeRef nodeRef) { boolean autoVersion = isEnableAutoVersionOnRecordCreation(); // if the flag autoversion on record creation set, create new version on dirty nodes if (autoVersion && isCurrentVersionDirty(nodeRef)) { Map<String, Serializable> autoVersionProperties = new HashMap<String, Serializable>(2); autoVersionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); autoVersionProperties.put(VersionModel.PROP_DESCRIPTION, I18NUtil.getMessage(AUTO_VERSION_ON_RECORD_CREATION)); createVersion(nodeRef, autoVersionProperties); } } }