diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index edb3ee7015..d454ce6684 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -190,6 +190,17 @@ + + + + + + / + alfresco/bootstrap/version2Store.xml + + + + diff --git a/config/alfresco/bootstrap/version2Store.xml b/config/alfresco/bootstrap/version2Store.xml new file mode 100644 index 0000000000..b3e3d3f475 --- /dev/null +++ b/config/alfresco/bootstrap/version2Store.xml @@ -0,0 +1,15 @@ + + + + + + + GROUP_EVERYONE + All + + + + + + \ No newline at end of file diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 8776440911..a3040311ae 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -649,7 +649,7 @@ - + @@ -671,9 +671,15 @@ + + + + + ${version.store.onlyUseDeprecatedV1} + - + @@ -681,6 +687,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -798,7 +825,10 @@ org/alfresco/repo/action/actionModel.xml org/alfresco/repo/rule/ruleModel.xml - org/alfresco/repo/version/version_model.xml + + + org/alfresco/repo/version/version_model.xml + org/alfresco/repo/version/version2_model.xml alfresco/model/emailServerModel.xml diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 4a26ae8442..3b2ed596ba 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -165,7 +165,7 @@ spaces_archive - workspace://lightWeightVersionStore + ${version.store.version2Store} versions @@ -331,7 +331,13 @@ - workspace://lightWeightVersionStore + ${version.store.deprecated.lightWeightVersionStore} + + + + + + ${version.store.version2Store} diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 4c5455bdfb..af03203b8a 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -222,4 +222,7 @@ patch.createSiteStore.description=Create the AVM store for site data structure f patch.createSiteStore.result=Created the AVM site data store. patch.sitePermissionRefactorPatch.description=Create permission groups for sites. -patch.sitePermissionRefactorPatch.result=Groups have been created for all sites and user's allocated accordingly. \ No newline at end of file +patch.sitePermissionRefactorPatch.result=Groups have been created for all sites and user's allocated accordingly. + +patch.migrateVersionStore.description=Migrate from lightWeightVersionStore to version2Store +patch.migrateVersionStore.result=Migrated version store. Created {0} version histories diff --git a/config/alfresco/messages/version-service.properties b/config/alfresco/messages/version-service.properties index 6d7a741cfb..efa43c5e70 100644 --- a/config/alfresco/messages/version-service.properties +++ b/config/alfresco/messages/version-service.properties @@ -1,4 +1,4 @@ -# Rule service externalised display strings +# Version service externalised display strings version_service.err_restore_exists=The node {0} cannot be restored since it already exists. version_service.err_not_found=The current version label of the node does not exist in the version history. @@ -6,3 +6,14 @@ version_service.err_unsupported=The current implementation of the version servic version_service.err_one_preceeding=The current implementation of the version service only supports one preceeding version. version_service.err_restore_no_version=The node {0} cannot be restore since there is no version information available for this node. version_service.err_revert_mismatch=The version provided to revert to does not come from the nodes version history. + + +version_service.migration.patch.noop=Nothing to do (no version histories found in old version store) +version_service.migration.patch.complete=Completed migration of {0} version histories (to new version store) in {1} secs +version_service.migration.patch.warn.skip1=Skipped migration of {0} version histories (migrate failed) +version_service.migration.patch.warn.skip2=Skipped migration of {0} version histories (already migrated) + +version_service.migration.delete.progress=\t\tBackground deletion of migrated version histories (from old version store) {0}% complete, estimated complete at {1} +version_service.migration.delete.complete=Completed deletion of {0} migrated version histories (from old version store) in {1} secs +version_service.migration.delete.warn.skip1=Skipped deletion of {0} version histories (delete failed) +version_service.migration.delete.warn.skip2=Skipped deletion of {0} version histories (not migrated) \ No newline at end of file diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 77bdac87ff..40fd3f4508 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -129,6 +129,11 @@ 5 + + + ${version.store.version2Store} + + @@ -142,6 +147,11 @@ + + + ${version.store.version2Store} + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 1787a46644..66d0aaef0d 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1488,4 +1488,16 @@ + + patch.migrateVersionStore + patch.migrateVersionStore.description + 0 + 129 + 130 + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index bff5db14d0..ae70309917 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -185,6 +185,12 @@ spaces.content_forms.childname=app:forms spaces.user_homes.childname=app:user_homes spaces.sites.childname=st:sites +# ADM VersionStore Configuration +version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore +version.store.version2Store=workspace://version2Store +# WARNING: For non-production testing only !!! Do not change (to avoid version store issues, including possible mismatch). Should be false since lightWeightVersionStore is deprecated. +version.store.onlyUseDeprecatedV1=false + # Folders for storing people system.system_container.childname=sys:system system.people_container.childname=sys:people diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index 6de99529e2..de3cf5d4c9 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -470,4 +470,36 @@ + + + + + + org.alfresco.repo.version.MigrationCleanupJob + + + + + + + + + + + + + + + + + + + + 1 + + + 0 + + + \ No newline at end of file diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 4f5efb1fa0..8e4d71b81a 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=129 \ No newline at end of file +version.schema=130 \ No newline at end of file diff --git a/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java new file mode 100644 index 0000000000..d04efc1561 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.version.VersionMigrator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Migrate version store from workspace://lightWeightVersionStore to workspace://version2Store + */ +public class MigrateVersionStorePatch extends AbstractPatch +{ + private static Log logger = LogFactory.getLog(MigrateVersionStorePatch.class); + + private static final String MSG_SUCCESS = "patch.migrateVersionStore.result"; + + private VersionMigrator versionMigrator; + private int batchSize = 1; + private boolean deleteImmediately = false; + + public void setVersionMigrator(VersionMigrator versionMigrator) + { + this.versionMigrator = versionMigrator; + } + + public void setBatchSize(int batchSize) + { + this.batchSize = batchSize; + } + + public void setDeleteImmediately(boolean deleteImmediately) + { + this.deleteImmediately = deleteImmediately; + } + + public void init() + { + if (batchSize < 1) + { + String errorMessage = "batchSize ("+batchSize+") cannot be less than 1"; + logger.error(errorMessage); + throw new AlfrescoRuntimeException(errorMessage); + } + + super.init(); + } + + @Override + protected String applyInternal() throws Exception + { + int vhCount = versionMigrator.migrateVersions(batchSize, deleteImmediately); + + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, vhCount); + + // done + return msg; + } +} diff --git a/source/java/org/alfresco/repo/model/ml/EditionServiceImpl.java b/source/java/org/alfresco/repo/model/ml/EditionServiceImpl.java index 02bf2c2fbd..14ac2d41cf 100644 --- a/source/java/org/alfresco/repo/model/ml/EditionServiceImpl.java +++ b/source/java/org/alfresco/repo/model/ml/EditionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,9 +31,11 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.version.VersionModel; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.cmr.ml.EditionService; @@ -257,9 +259,29 @@ public class EditionServiceImpl implements EditionService Map properties = versionNodeService.getProperties(mlContainerEdition.getFrozenStateNodeRef()); - // get the serialisation of the version histories in the version properties - List versionHistories = (List) - properties.get(VersionModel.PROP_QNAME_TRANSLATION_VERIONS); + List versionHistories = null; + + // Switch VersionStore depending on configured impl + if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + + // get the serialisation of the version histories in the version properties + versionHistories = (List) + properties.get(Version2Model.PROP_QNAME_TRANSLATION_VERSIONS); + } + else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + + // get the serialisation of the version histories in the version properties + versionHistories = (List) + properties.get(VersionModel.PROP_QNAME_TRANSLATION_VERSIONS); + } + else + { + throw new AlfrescoRuntimeException("Unexpected versionstore: " + versionService.getVersionStoreReference().getIdentifier()); + } if (versionHistories == null) { @@ -296,18 +318,37 @@ public class EditionServiceImpl implements EditionService // properties in which the version histories will be stored Map properties = new HashMap(); - // add the version history of the translation as property of the Edition - properties.put(VersionModel.PROP_QNAME_QNAME, VersionModel.PROP_QNAME_TRANSLATION_VERIONS); - properties.put(VersionModel.PROP_QNAME_IS_MULTI_VALUE, true); - properties.put(VersionModel.PROP_QNAME_MULTI_VALUE, (Serializable) translationVersionHistories); + // Switch VersionStore depending on configured impl + if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + + // add the version history of the translation as property of the Edition + NodeRef versionNodeRef = VersionUtil.convertNodeRef(edition.getFrozenStateNodeRef()); + this.nodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_TRANSLATION_VERSIONS, (Serializable) translationVersionHistories); + } + else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + + // add the version history of the translation as property of the Edition + properties.put(VersionModel.PROP_QNAME_QNAME, VersionModel.PROP_QNAME_TRANSLATION_VERSIONS); + properties.put(VersionModel.PROP_QNAME_IS_MULTI_VALUE, true); + properties.put(VersionModel.PROP_QNAME_MULTI_VALUE, (Serializable) translationVersionHistories); + + // create the versioned property node + this.nodeService.createNode( + VersionUtil.convertNodeRef(edition.getFrozenStateNodeRef()), + VersionModel.CHILD_QNAME_VERSIONED_ATTRIBUTES, + VersionModel.CHILD_QNAME_VERSIONED_ATTRIBUTES, + VersionModel.TYPE_QNAME_VERSIONED_PROPERTY, + properties); + } + else + { + throw new AlfrescoRuntimeException("Unexpected versionstore: " + versionService.getVersionStoreReference().getIdentifier()); + } - // create the versioned property node - this.nodeService.createNode( - VersionUtil.convertNodeRef(edition.getFrozenStateNodeRef()), - VersionModel.CHILD_QNAME_VERSIONED_ATTRIBUTES, - VersionModel.CHILD_QNAME_VERSIONED_ATTRIBUTES, - VersionModel.TYPE_QNAME_VERSIONED_PROPERTY, - properties); } /** diff --git a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java index a1aeb39dcf..fa949f06bf 100644 --- a/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java +++ b/source/java/org/alfresco/repo/node/integrity/IncompleteNodeTagger.java @@ -25,6 +25,7 @@ package org.alfresco.repo.node.integrity; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -79,6 +80,7 @@ public class IncompleteNodeTagger private PolicyComponent policyComponent; private DictionaryService dictionaryService; private NodeService nodeService; + private List storesToIgnore = new ArrayList(0); public IncompleteNodeTagger() { @@ -107,6 +109,15 @@ public class IncompleteNodeTagger { this.nodeService = nodeService; } + + /** + * @param storesToIgnore stores (eg. workspace://version2Store) which will be + * ignored by IncompleteNodeTagger. Note: assumes associations are within a store. + */ + public void setStoresToIgnore(List storesToIgnore) + { + this.storesToIgnore = storesToIgnore; + } /** * Registers the system-level policy behaviours @@ -236,9 +247,12 @@ public class IncompleteNodeTagger */ public void onCreateNode(ChildAssociationRef childAssocRef) { - NodeRef nodeRef = childAssocRef.getChildRef(); - save(nodeRef); - saveAssoc(nodeRef, null); + if (! storesToIgnore.contains(childAssocRef.getChildRef().getStoreRef().toString())) + { + NodeRef nodeRef = childAssocRef.getChildRef(); + save(nodeRef); + saveAssoc(nodeRef, null); + } } /** @@ -249,7 +263,10 @@ public class IncompleteNodeTagger Map before, Map after) { - save(nodeRef); + if (! storesToIgnore.contains(nodeRef.getStoreRef().toString())) + { + save(nodeRef); + } } /** @@ -261,14 +278,17 @@ public class IncompleteNodeTagger */ public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { - if (aspectTypeQName.equals(ContentModel.ASPECT_INCOMPLETE)) + if (! storesToIgnore.contains(nodeRef.getStoreRef().toString())) { - if (logger.isDebugEnabled()) + if (aspectTypeQName.equals(ContentModel.ASPECT_INCOMPLETE)) { - logger.debug("Ignoring aspect addition: " + ContentModel.ASPECT_INCOMPLETE); + if (logger.isDebugEnabled()) + { + logger.debug("Ignoring aspect addition: " + ContentModel.ASPECT_INCOMPLETE); + } } + save(nodeRef); } - save(nodeRef); } /** @@ -276,14 +296,17 @@ public class IncompleteNodeTagger */ public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) { - if (aspectTypeQName.equals(ContentModel.ASPECT_INCOMPLETE)) + if (! storesToIgnore.contains(nodeRef.getStoreRef().toString())) { - if (logger.isDebugEnabled()) + if (aspectTypeQName.equals(ContentModel.ASPECT_INCOMPLETE)) { - logger.debug("Ignoring aspect removal: " + ContentModel.ASPECT_INCOMPLETE); + if (logger.isDebugEnabled()) + { + logger.debug("Ignoring aspect removal: " + ContentModel.ASPECT_INCOMPLETE); + } } + save(nodeRef); } - save(nodeRef); } /** @@ -294,9 +317,12 @@ public class IncompleteNodeTagger */ public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNew) { - if (!isNew) + if (! storesToIgnore.contains(childAssocRef.getChildRef().getStoreRef().toString())) { - saveAssoc(childAssocRef.getParentRef(), childAssocRef.getTypeQName()); + if (!isNew) + { + saveAssoc(childAssocRef.getParentRef(), childAssocRef.getTypeQName()); + } } } @@ -305,7 +331,10 @@ public class IncompleteNodeTagger */ public void onDeleteChildAssociation(ChildAssociationRef childAssocRef) { - saveAssoc(childAssocRef.getParentRef(), childAssocRef.getTypeQName()); + if (! storesToIgnore.contains(childAssocRef.getChildRef().getStoreRef().toString())) + { + saveAssoc(childAssocRef.getParentRef(), childAssocRef.getTypeQName()); + } } /** @@ -313,7 +342,10 @@ public class IncompleteNodeTagger */ public void onCreateAssociation(AssociationRef nodeAssocRef) { - saveAssoc(nodeAssocRef.getSourceRef(), nodeAssocRef.getTypeQName()); + if (! storesToIgnore.contains(nodeAssocRef.getSourceRef().getStoreRef().toString())) + { + saveAssoc(nodeAssocRef.getSourceRef(), nodeAssocRef.getTypeQName()); + } } /** @@ -321,7 +353,10 @@ public class IncompleteNodeTagger */ public void onDeleteAssociation(AssociationRef nodeAssocRef) { - saveAssoc(nodeAssocRef.getSourceRef(), nodeAssocRef.getTypeQName()); + if (! storesToIgnore.contains(nodeAssocRef.getSourceRef().getStoreRef().toString())) + { + saveAssoc(nodeAssocRef.getSourceRef(), nodeAssocRef.getTypeQName()); + } } /** diff --git a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java index 52c726d253..affd8e1bef 100644 --- a/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java +++ b/source/java/org/alfresco/repo/node/integrity/IntegrityChecker.java @@ -107,6 +107,7 @@ public class IntegrityChecker private boolean failOnViolation; private int maxErrorsPerTransaction; private boolean traceOn; + private List storesToIgnore = new ArrayList(0); /** * Downgrade violations to warnings within the current transaction. This is temporary and @@ -206,6 +207,15 @@ public class IntegrityChecker { this.maxErrorsPerTransaction = maxLogNumberPerTransaction; } + + /** + * @param storesToIgnore stores (eg. workspace://version2Store) which will be + * ignored by integrity checker. Note: assumes associations are within a store. + */ + public void setStoresToIgnore(List storesToIgnore) + { + this.storesToIgnore = storesToIgnore; + } /** * Registers the system-level policy behaviours @@ -319,43 +329,46 @@ public class IntegrityChecker */ public void onCreateNode(ChildAssociationRef childAssocRef) { - NodeRef childRef = childAssocRef.getChildRef(); - IntegrityEvent event = null; - // check properties on child node - event = new PropertiesIntegrityEvent( - nodeService, - dictionaryService, - childRef); - save(event); - - // check that the multiplicity and other properties of the new association are allowed - onCreateChildAssociation(childAssocRef, false); - - // check mandatory aspects - event = new AspectsIntegrityEvent(nodeService, dictionaryService, childRef); - save(event); - - // check for associations defined on the new node (child) - QName childNodeTypeQName = nodeService.getType(childRef); - ClassDefinition nodeTypeDef = dictionaryService.getClass(childNodeTypeQName); - if (nodeTypeDef == null) + NodeRef childRef = childAssocRef.getChildRef(); + if (! storesToIgnore.contains(childRef.getStoreRef().toString())) { - throw new DictionaryException("The node type is not recognized: " + childNodeTypeQName); - } - Map childAssocDefs = nodeTypeDef.getAssociations(); - - // check the multiplicity of each association with the node acting as a source - for (AssociationDefinition assocDef : childAssocDefs.values()) - { - QName assocTypeQName = assocDef.getName(); - // check target multiplicity - event = new AssocTargetMultiplicityIntegrityEvent( + IntegrityEvent event = null; + // check properties on child node + event = new PropertiesIntegrityEvent( nodeService, dictionaryService, - childRef, - assocTypeQName, - false); + childRef); save(event); + + // check that the multiplicity and other properties of the new association are allowed + onCreateChildAssociation(childAssocRef, false); + + // check mandatory aspects + event = new AspectsIntegrityEvent(nodeService, dictionaryService, childRef); + save(event); + + // check for associations defined on the new node (child) + QName childNodeTypeQName = nodeService.getType(childRef); + ClassDefinition nodeTypeDef = dictionaryService.getClass(childNodeTypeQName); + if (nodeTypeDef == null) + { + throw new DictionaryException("The node type is not recognized: " + childNodeTypeQName); + } + Map childAssocDefs = nodeTypeDef.getAssociations(); + + // check the multiplicity of each association with the node acting as a source + for (AssociationDefinition assocDef : childAssocDefs.values()) + { + QName assocTypeQName = assocDef.getName(); + // check target multiplicity + event = new AssocTargetMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + childRef, + assocTypeQName, + false); + save(event); + } } } @@ -367,10 +380,13 @@ public class IntegrityChecker Map before, Map after) { - IntegrityEvent event = null; - // check properties on node - event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef); - save(event); + if (! storesToIgnore.contains(nodeRef.getStoreRef().toString())) + { + IntegrityEvent event = null; + // check properties on node + event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef); + save(event); + } } /** @@ -386,31 +402,34 @@ public class IntegrityChecker */ public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { - IntegrityEvent event = null; - // check properties on node - event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef); - save(event); - - // check for associations defined on the aspect - AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); - if (aspectDef == null) + if (! storesToIgnore.contains(nodeRef.getStoreRef().toString())) { - throw new DictionaryException("The aspect type is not recognized: " + aspectTypeQName); - } - Map assocDefs = aspectDef.getAssociations(); - - // check the multiplicity of each association with the node acting as a source - for (AssociationDefinition assocDef : assocDefs.values()) - { - QName assocTypeQName = assocDef.getName(); - // check target multiplicity - event = new AssocTargetMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - nodeRef, - assocTypeQName, - false); + IntegrityEvent event = null; + // check properties on node + event = new PropertiesIntegrityEvent(nodeService, dictionaryService, nodeRef); save(event); + + // check for associations defined on the aspect + AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); + if (aspectDef == null) + { + throw new DictionaryException("The aspect type is not recognized: " + aspectTypeQName); + } + Map assocDefs = aspectDef.getAssociations(); + + // check the multiplicity of each association with the node acting as a source + for (AssociationDefinition assocDef : assocDefs.values()) + { + QName assocTypeQName = assocDef.getName(); + // check target multiplicity + event = new AssocTargetMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + nodeRef, + assocTypeQName, + false); + save(event); + } } } @@ -419,11 +438,13 @@ public class IntegrityChecker */ public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) { - IntegrityEvent event = null; - // check mandatory aspects - event = new AspectsIntegrityEvent(nodeService, dictionaryService, nodeRef); - save(event); - + if (! storesToIgnore.contains(nodeRef.getStoreRef().toString())) + { + IntegrityEvent event = null; + // check mandatory aspects + event = new AspectsIntegrityEvent(nodeService, dictionaryService, nodeRef); + save(event); + } } /** @@ -442,45 +463,48 @@ public class IntegrityChecker return; } - IntegrityEvent event = null; - // check source type - event = new AssocSourceTypeIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getParentRef(), - childAssocRef.getTypeQName()); - save(event); - // check target type - event = new AssocTargetTypeIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getChildRef(), - childAssocRef.getTypeQName()); - save(event); - // check source multiplicity - event = new AssocSourceMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getChildRef(), - childAssocRef.getTypeQName(), - false); - save(event); - // check target multiplicity - event = new AssocTargetMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getParentRef(), - childAssocRef.getTypeQName(), - false); - save(event); - // check target role - event = new AssocTargetRoleIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getParentRef(), - childAssocRef.getTypeQName(), - childAssocRef.getQName()); - save(event); + if (! storesToIgnore.contains(childAssocRef.getChildRef().getStoreRef().toString())) + { + IntegrityEvent event = null; + // check source type + event = new AssocSourceTypeIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getParentRef(), + childAssocRef.getTypeQName()); + save(event); + // check target type + event = new AssocTargetTypeIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getChildRef(), + childAssocRef.getTypeQName()); + save(event); + // check source multiplicity + event = new AssocSourceMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getChildRef(), + childAssocRef.getTypeQName(), + false); + save(event); + // check target multiplicity + event = new AssocTargetMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getParentRef(), + childAssocRef.getTypeQName(), + false); + save(event); + // check target role + event = new AssocTargetRoleIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getParentRef(), + childAssocRef.getTypeQName(), + childAssocRef.getQName()); + save(event); + } } /** @@ -489,23 +513,26 @@ public class IntegrityChecker */ public void onDeleteChildAssociation(ChildAssociationRef childAssocRef) { - IntegrityEvent event = null; - // check source multiplicity - event = new AssocSourceMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getChildRef(), - childAssocRef.getTypeQName(), - true); - save(event); - // check target multiplicity - event = new AssocTargetMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - childAssocRef.getParentRef(), - childAssocRef.getTypeQName(), - true); - save(event); + if (! storesToIgnore.contains(childAssocRef.getChildRef().getStoreRef().toString())) + { + IntegrityEvent event = null; + // check source multiplicity + event = new AssocSourceMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getChildRef(), + childAssocRef.getTypeQName(), + true); + save(event); + // check target multiplicity + event = new AssocTargetMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + childAssocRef.getParentRef(), + childAssocRef.getTypeQName(), + true); + save(event); + } } /** @@ -516,37 +543,40 @@ public class IntegrityChecker */ public void onCreateAssociation(AssociationRef nodeAssocRef) { - IntegrityEvent event = null; - // check source type - event = new AssocSourceTypeIntegrityEvent( - nodeService, - dictionaryService, - nodeAssocRef.getSourceRef(), - nodeAssocRef.getTypeQName()); - save(event); - // check target type - event = new AssocTargetTypeIntegrityEvent( - nodeService, - dictionaryService, - nodeAssocRef.getTargetRef(), - nodeAssocRef.getTypeQName()); - save(event); - // check source multiplicity - event = new AssocSourceMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - nodeAssocRef.getTargetRef(), - nodeAssocRef.getTypeQName(), - false); - save(event); - // check target multiplicity - event = new AssocTargetMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - nodeAssocRef.getSourceRef(), - nodeAssocRef.getTypeQName(), - false); - save(event); + if (! storesToIgnore.contains(nodeAssocRef.getSourceRef().getStoreRef().toString())) + { + IntegrityEvent event = null; + // check source type + event = new AssocSourceTypeIntegrityEvent( + nodeService, + dictionaryService, + nodeAssocRef.getSourceRef(), + nodeAssocRef.getTypeQName()); + save(event); + // check target type + event = new AssocTargetTypeIntegrityEvent( + nodeService, + dictionaryService, + nodeAssocRef.getTargetRef(), + nodeAssocRef.getTypeQName()); + save(event); + // check source multiplicity + event = new AssocSourceMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + nodeAssocRef.getTargetRef(), + nodeAssocRef.getTypeQName(), + false); + save(event); + // check target multiplicity + event = new AssocTargetMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + nodeAssocRef.getSourceRef(), + nodeAssocRef.getTypeQName(), + false); + save(event); + } } /** @@ -555,23 +585,26 @@ public class IntegrityChecker */ public void onDeleteAssociation(AssociationRef nodeAssocRef) { - IntegrityEvent event = null; - // check source multiplicity - event = new AssocSourceMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - nodeAssocRef.getTargetRef(), - nodeAssocRef.getTypeQName(), - true); - save(event); - // check target multiplicity - event = new AssocTargetMultiplicityIntegrityEvent( - nodeService, - dictionaryService, - nodeAssocRef.getSourceRef(), - nodeAssocRef.getTypeQName(), - true); - save(event); + if (! storesToIgnore.contains(nodeAssocRef.getSourceRef().getStoreRef().toString())) + { + IntegrityEvent event = null; + // check source multiplicity + event = new AssocSourceMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + nodeAssocRef.getTargetRef(), + nodeAssocRef.getTypeQName(), + true); + save(event); + // check target multiplicity + event = new AssocTargetMultiplicityIntegrityEvent( + nodeService, + dictionaryService, + nodeAssocRef.getSourceRef(), + nodeAssocRef.getTypeQName(), + true); + save(event); + } } /** diff --git a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java index 1c402826b1..ccb2c80b4e 100644 --- a/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java +++ b/source/java/org/alfresco/repo/version/BaseVersionStoreTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -55,7 +55,7 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.BaseSpringTest; import org.alfresco.util.TestWithUserUtils; -public abstract class BaseVersionStoreTest extends BaseSpringTest +public abstract class BaseVersionStoreTest extends BaseSpringTest { /* * Services used by the tests @@ -133,6 +133,11 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest this.dictionaryDAO = dictionaryDAO; } + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + /** * Called during the transaction setup */ @@ -148,7 +153,6 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest // Get the services by name from the application context this.dbNodeService = (NodeService)applicationContext.getBean("dbNodeService"); - this.versionService = (VersionService)applicationContext.getBean("versionService"); this.versionCounterDaoService = (VersionCounterService)applicationContext.getBean("versionCounterService"); this.contentService = (ContentService)applicationContext.getBean("contentService"); this.authenticationService = (AuthenticationService)applicationContext.getBean("authenticationService"); @@ -158,6 +162,8 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest this.nodeArchiveService = (NodeArchiveService) applicationContext.getBean("nodeArchiveService"); this.nodeService = (NodeService)applicationContext.getBean("nodeService"); + setVersionService((VersionService)applicationContext.getBean("versionService")); + authenticationService.clearCurrentSecurityContext(); // Create the test model @@ -214,6 +220,16 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest * @return the node reference */ protected NodeRef createNewVersionableNode() + { + return createNode(true); + } + + protected NodeRef createNewNode() + { + return createNode(false); + } + + protected NodeRef createNode(boolean versionable) { // Use this map to retrive the versionable nodes in later tests this.versionableNodes = new HashMap(); @@ -224,8 +240,11 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest ContentModel.ASSOC_CHILDREN, QName.createQName("{test}MyVersionableNode"), TEST_TYPE_QNAME, - this.nodeProperties).getChildRef(); - this.dbNodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, new HashMap()); + this.nodeProperties).getChildRef(); + if (versionable) + { + this.dbNodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, new HashMap()); + } assertNotNull(nodeRef); this.versionableNodes.put(nodeRef.getId(), nodeRef); @@ -241,7 +260,12 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest TEST_CHILD_ASSOC_1, TEST_TYPE_QNAME, this.nodeProperties).getChildRef(); - this.dbNodeService.addAspect(child1, ContentModel.ASPECT_VERSIONABLE, new HashMap()); + + if (versionable) + { + this.dbNodeService.addAspect(child1, ContentModel.ASPECT_VERSIONABLE, new HashMap()); + } + assertNotNull(child1); this.versionableNodes.put(child1.getId(), child1); NodeRef child2 = this.dbNodeService.createNode( @@ -250,7 +274,12 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest TEST_CHILD_ASSOC_2, TEST_TYPE_QNAME, this.nodeProperties).getChildRef(); - this.dbNodeService.addAspect(child2, ContentModel.ASPECT_VERSIONABLE, new HashMap()); + + if (versionable) + { + this.dbNodeService.addAspect(child2, ContentModel.ASPECT_VERSIONABLE, new HashMap()); + } + assertNotNull(child2); this.versionableNodes.put(child2.getId(), child2); @@ -303,6 +332,25 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest // Return the new version return newVersion; } + + protected Collection createVersion(NodeRef versionableNode, Map versionProperties, boolean versionChildren) + { + // Get the next version number + int nextVersion = peekNextVersionNumber(); + String nextVersionLabel = peekNextVersionLabel(versionableNode, nextVersion, versionProperties); + + // Snap-shot the date-time + long beforeVersionTime = System.currentTimeMillis(); + + // Now lets create new version for this node (optionally with children) + Collection versions = versionService.createVersion(versionableNode, this.versionProperties, versionChildren); + + // Check the returned versions are correct + checkVersionCollection(nextVersion, nextVersionLabel, beforeVersionTime, versions); + + // Return the new versions + return versions; + } /** * Gets the next version label @@ -322,15 +370,17 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest * @param newVersion the new version * @param versionableNode the versioned node */ - protected void checkNewVersion(long beforeVersionTime, int expectedVersionNumber, String expectedVersionLabel, Version newVersion, NodeRef versionableNode) + protected void checkVersion(long beforeVersionTime, int expectedVersionNumber, String expectedVersionLabel, Version newVersion, NodeRef versionableNode) { assertNotNull(newVersion); // Check the version label and version number + assertEquals( "The expected version number was not used.", Integer.toString(expectedVersionNumber), - newVersion.getVersionProperty(VersionModel.PROP_VERSION_NUMBER).toString()); + newVersion.getVersionProperty(VersionBaseModel.PROP_VERSION_NUMBER).toString()); + assertEquals( "The expected version label was not used.", expectedVersionLabel, @@ -347,28 +397,53 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest // Check the creator assertEquals(USER_NAME, newVersion.getCreator()); - // Check the properties of the verison + // Check the metadata properties of the version Map props = newVersion.getVersionProperties(); assertNotNull("The version properties collection should not be null.", props); - // TODO sort this out - need to check for the reserved properties too - //assertEquals(versionProperties.size(), props.size()); - for (String key : versionProperties.keySet()) + if (versionProperties != null) { - assertEquals( - versionProperties.get(key), - newVersion.getVersionProperty(key)); + // TODO sort this out - need to check for the reserved properties too + //assertEquals(versionProperties.size(), props.size()); + for (String key : versionProperties.keySet()) + { + assertEquals( + versionProperties.get(key), + newVersion.getVersionProperty(key)); + } } // Check that the node reference is correct NodeRef nodeRef = newVersion.getFrozenStateNodeRef(); assertNotNull(nodeRef); - assertEquals( - VersionModel.STORE_ID, - nodeRef.getStoreRef().getIdentifier()); - assertEquals( - VersionModel.STORE_PROTOCOL, - nodeRef.getStoreRef().getProtocol()); - assertNotNull(nodeRef.getId()); + + // Switch VersionStore depending on configured impl + if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + assertEquals( + Version2Model.STORE_ID, + nodeRef.getStoreRef().getIdentifier()); + assertEquals( + Version2Model.STORE_PROTOCOL, + nodeRef.getStoreRef().getProtocol()); + assertNotNull(nodeRef.getId()); + } + else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + assertEquals( + VersionModel.STORE_ID, + nodeRef.getStoreRef().getIdentifier()); + assertEquals( + VersionModel.STORE_PROTOCOL, + nodeRef.getStoreRef().getProtocol()); + assertNotNull(nodeRef.getId()); + } + } + + protected void checkNewVersion(long beforeVersionTime, int expectedVersionNumber, String expectedVersionLabel, Version newVersion, NodeRef versionableNode) + { + checkVersion(beforeVersionTime, expectedVersionNumber, expectedVersionLabel, newVersion, versionableNode); // TODO: How do we check the frozen attributes ?? @@ -379,6 +454,43 @@ public abstract class BaseVersionStoreTest extends BaseSpringTest assertEquals(newVersion.getVersionLabel(), currentVersionLabel); } + /** + * Helper method to check the validity of the list of newly created versions. + * + * @param expectedVersionNumber the expected version number that all the versions should have + * @param beforeVersionTime the time before the versions where created + * @param versions the collection of version objects + */ + private void checkVersionCollection(int expectedVersionNumber, String expectedVersionLabel, long beforeVersionTime, Collection versions) + { + for (Version version : versions) + { + // Get the frozen id from the version + String frozenNodeId = null; + + // Switch VersionStore depending on configured impl + if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + frozenNodeId = ((NodeRef)version.getVersionProperty(Version2Model.PROP_FROZEN_NODE_REF)).getId(); + } + else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + frozenNodeId = (String)version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID); + } + + assertNotNull("Unable to retrieve the frozen node id from the created version.", frozenNodeId); + + // Get the original node ref (based on the forzen node) + NodeRef origionaNodeRef = this.versionableNodes.get(frozenNodeId); + assertNotNull("The versionable node ref that relates to the frozen node id can not be found.", origionaNodeRef); + + // Check the new version + checkNewVersion(beforeVersionTime, expectedVersionNumber, expectedVersionLabel, version, origionaNodeRef); + } + } + /** * Returns the next version number without affecting the version counter. * diff --git a/source/java/org/alfresco/repo/version/MigrationCleanupJob.java b/source/java/org/alfresco/repo/version/MigrationCleanupJob.java new file mode 100644 index 0000000000..20a27152f1 --- /dev/null +++ b/source/java/org/alfresco/repo/version/MigrationCleanupJob.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Cleanup of Version Store Migration - to delete old/migrated version histories from old version store. Typically this is configured to run once on startup. + */ +public class MigrationCleanupJob implements Job +{ + private static Log logger = LogFactory.getLog(MigrationCleanupJob.class); + + private static final String KEY_COMPONENT = "versionMigrator"; + private static final String KEY_BATCHSIZE = "batchSize"; + + private int batchSize = 1; + + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + VersionMigrator migrationCleanup = (VersionMigrator)jobData.get(KEY_COMPONENT); + if (migrationCleanup == null) + { + throw new JobExecutionException("Missing job data: " + KEY_COMPONENT); + } + + String batchSizeStr = (String)jobData.get(KEY_BATCHSIZE); + if (batchSizeStr == null) + { + try + { + batchSize = new Integer(batchSizeStr); + } + catch (Exception e) + { + logger.warn("Invalid batchsize, using default: " + batchSize, e); + } + } + + if (batchSize < 1) + { + String errorMessage = "batchSize ("+batchSize+") cannot be less than 1"; + logger.error(errorMessage); + throw new AlfrescoRuntimeException(errorMessage); + } + + // perform the cleanup of the old version store + migrationCleanup.executeCleanup(batchSize); + } +} diff --git a/source/java/org/alfresco/repo/version/Node2ServiceImpl.java b/source/java/org/alfresco/repo/version/Node2ServiceImpl.java new file mode 100644 index 0000000000..6cfd436899 --- /dev/null +++ b/source/java/org/alfresco/repo/version/Node2ServiceImpl.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +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.namespace.QName; +import org.alfresco.service.namespace.QNamePattern; + + +/** + * The version2 store node service implementation + */ +public class Node2ServiceImpl extends NodeServiceImpl implements NodeService, Version2Model +{ + /** + * The name of the spoofed root association + */ + private static final QName rootAssocName = QName.createQName(Version2Model.NAMESPACE_URI, "versionedState"); + + + /** + * Type translation for version store + */ + public QName getType(NodeRef nodeRef) throws InvalidNodeRefException + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getType(nodeRef); + } + + // frozen node type -> replaced by actual node type of the version node + return (QName)this.dbNodeService.getType(VersionUtil.convertNodeRef(nodeRef)); + } + + /** + * Translation for version store + */ + public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getAspects(nodeRef); + } + + Set aspects = this.dbNodeService.getAspects(VersionUtil.convertNodeRef(nodeRef)); + aspects.remove(Version2Model.ASPECT_VERSION); + return aspects; + } + + /** + * Property translation for version store + */ + public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getProperties(nodeRef); + } + + Map props = dbNodeService.getProperties(VersionUtil.convertNodeRef(nodeRef)); + VersionUtil.convertFrozenToOriginalProps(props); + + return props; + } + + /** + * Property translation for version store + */ + public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getProperty(nodeRef, qname); + } + + // TODO optimise - get property directly and convert if needed + Map properties = getProperties(VersionUtil.convertNodeRef(nodeRef)); + return properties.get(qname); + } + + /** + * The node will apprear to be attached to the root of the version store + * + * @see NodeService#getParentAssocs(NodeRef, QNamePattern, QNamePattern) + */ + public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getParentAssocs(nodeRef, typeQNamePattern, qnamePattern); + } + + List result = new ArrayList(); + if (qnamePattern.isMatch(rootAssocName) == true) + { + result.add(new ChildAssociationRef( + ContentModel.ASSOC_CHILDREN, + dbNodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID)), + rootAssocName, + nodeRef)); + } + return result; + } + + /** + * Performs conversion from version store properties to real associations + */ + public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) throws InvalidNodeRefException + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getChildAssocs(nodeRef, typeQNamePattern, qnamePattern); + } + + // Get the child assocs from the version store + List childAssocRefs = this.dbNodeService.getChildAssocs( + VersionUtil.convertNodeRef(nodeRef), + typeQNamePattern, qnamePattern); + List result = new ArrayList(childAssocRefs.size()); + for (ChildAssociationRef childAssocRef : childAssocRefs) + { + // Get the child reference + NodeRef childRef = childAssocRef.getChildRef(); + NodeRef referencedNode = (NodeRef)this.dbNodeService.getProperty(childRef, ContentModel.PROP_REFERENCE); + + // Build a child assoc ref to add to the returned list + ChildAssociationRef newChildAssocRef = new ChildAssociationRef( + childAssocRef.getTypeQName(), + childAssocRef.getParentRef(), + childAssocRef.getQName(), + referencedNode, + childAssocRef.isPrimary(), + childAssocRef.getNthSibling()); + result.add(newChildAssocRef); + } + + // sort the results so that the order appears to be exactly as it was originally + Collections.sort(result); + + return result; + } + + /** + * Simulates the node begin attached to the root node of the version store. + */ + public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException + { + if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getPrimaryParent(nodeRef); + } + + return new ChildAssociationRef( + ContentModel.ASSOC_CHILDREN, + dbNodeService.getRootNode(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID)), + rootAssocName, + nodeRef); + } + + /** + * @throws UnsupportedOperationException always + */ + @Override + public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) + { + if (sourceRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + return super.getTargetAssocs(sourceRef, qnamePattern); + } + + // This operation is not supported for a version2 store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } +} diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index c9750547b0..4b76d88688 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -36,6 +36,7 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidAspectException; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -54,6 +55,8 @@ import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** @@ -63,10 +66,12 @@ import org.alfresco.service.namespace.RegexQNamePattern; */ public class NodeServiceImpl implements NodeService, VersionModel { + private static Log logger = LogFactory.getLog(NodeServiceImpl.class); + /** * Error messages */ - private final static String MSG_UNSUPPORTED = + protected final static String MSG_UNSUPPORTED = "This operation is not supported by a version store implementation of the node service."; /** @@ -331,7 +336,23 @@ public class NodeServiceImpl implements NodeService, VersionModel if (isMultiValue.booleanValue() == false) { value = this.dbNodeService.getProperty(versionedAttribute, PROP_QNAME_VALUE); - value = (Serializable)DefaultTypeConverter.INSTANCE.convert(propDef.getDataType(), value); + + if (propDef != null) + { + DataTypeDefinition dataTypeDef = propDef.getDataType(); + if (dataTypeDef != null) + { + value = (Serializable)DefaultTypeConverter.INSTANCE.convert(dataTypeDef, value); + } + else + { + logger.warn("Null dataTypeDefinition for: " + propDef); + } + } + else + { + logger.warn("Null propertyDefinition for: " + qName); + } } else { diff --git a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java index 3acd5fb676..8c6214e08c 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -119,13 +119,25 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Get the properties of the versioned state Map versionedProperties = this.lightWeightVersionStoreNodeService.getProperties(version.getFrozenStateNodeRef()); - //assertEquals(origProps.size(), versionedProperties.size()); + + if (logger.isDebugEnabled()) + { + logger.debug("original ("+origProps.size()+"): " + origProps.keySet()); + logger.debug("versioned ("+versionedProperties.size()+"): " + versionedProperties.keySet()); + } + for (QName key : origProps.keySet()) { assertTrue(versionedProperties.containsKey(key)); - assertEquals(origProps.get(key), versionedProperties.get(key)); + assertEquals(""+key, origProps.get(key), versionedProperties.get(key)); } + // NOTE: cm:versionLabel is an expected additional property + //assertEquals(origProps.size(), versionedProperties.size()); + + // check version label + assertEquals("1.0", versionedProperties.get(ContentModel.PROP_VERSION_LABEL)); + // TODO do futher versioning and check by changing values } @@ -163,46 +175,47 @@ public class NodeServiceImplTest extends BaseVersionStoreTest */ public void testGetChildAssocs() { - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { // Let's have a look at the version store .. - System.out.println(NodeStoreInspector.dumpNodeStore( + logger.trace(NodeStoreInspector.dumpNodeStore( this.dbNodeService, this.versionService.getVersionStoreReference()) + "\n\n"); - logger.debug(""); + logger.trace(""); } // Create a new versionable node NodeRef versionableNode = createNewVersionableNode(); - Collection origionalChildren = this.dbNodeService.getChildAssocs(versionableNode); - assertNotNull(origionalChildren); + Collection originalChildren = this.dbNodeService.getChildAssocs(versionableNode); + assertNotNull(originalChildren); - // Store the origional children in a map for easy navigation later - HashMap origionalChildAssocRefs = new HashMap(); - for (ChildAssociationRef ref : origionalChildren) + // Store the original children in a map for easy navigation later + HashMap originalChildAssocRefs = new HashMap(); + for (ChildAssociationRef ref : originalChildren) { - origionalChildAssocRefs.put(ref.getChildRef().getId(), ref); + originalChildAssocRefs.put(ref.getChildRef().getId(), ref); } // Create a new version Version version = createVersion(versionableNode, this.versionProperties); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { // Let's have a look at the version store .. - System.out.println(NodeStoreInspector.dumpNodeStore( - this.dbNodeService, + logger.trace(NodeStoreInspector.dumpNodeStore( + this.dbNodeService, this.versionService.getVersionStoreReference())); + logger.trace(""); } // Get the children of the versioned node Collection versionedChildren = this.lightWeightVersionStoreNodeService.getChildAssocs(version.getFrozenStateNodeRef()); assertNotNull(versionedChildren); - assertEquals(origionalChildren.size(), versionedChildren.size()); + assertEquals(originalChildren.size(), versionedChildren.size()); for (ChildAssociationRef versionedChildRef : versionedChildren) { - ChildAssociationRef origChildAssocRef = origionalChildAssocRefs.get(versionedChildRef.getChildRef().getId()); + ChildAssociationRef origChildAssocRef = originalChildAssocRefs.get(versionedChildRef.getChildRef().getId()); assertNotNull(origChildAssocRef); assertEquals( @@ -219,25 +232,51 @@ public class NodeServiceImplTest extends BaseVersionStoreTest /** * Test getAssociationTargets + * + * @deprecated */ public void testGetAssociationTargets() { - // Create a new versionable node - NodeRef versionableNode = createNewVersionableNode(); - - // Store the current details of the target associations - List origAssocs = this.dbNodeService.getTargetAssocs( - versionableNode, - RegexQNamePattern.MATCH_ALL); - - // Create a new version - Version version = createVersion(versionableNode, this.versionProperties); - - List assocs = this.lightWeightVersionStoreNodeService.getTargetAssocs( - version.getFrozenStateNodeRef(), - RegexQNamePattern.MATCH_ALL); - assertNotNull(assocs); - assertEquals(origAssocs.size(), assocs.size()); + // Switch VersionStore depending on configured impl + if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + try + { + this.lightWeightVersionStoreNodeService.getTargetAssocs( + dummyNodeRef, + RegexQNamePattern.MATCH_ALL); + fail("This operation is not supported."); + } + catch (UnsupportedOperationException exception) + { + if (exception.getMessage() != MSG_ERR) + { + fail("Unexpected exception raised during method excution: " + exception.getMessage()); + } + } + } + else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + + // Create a new versionable node + NodeRef versionableNode = createNewVersionableNode(); + + // Store the current details of the target associations + List origAssocs = this.dbNodeService.getTargetAssocs( + versionableNode, + RegexQNamePattern.MATCH_ALL); + + // Create a new version + Version version = createVersion(versionableNode, this.versionProperties); + + List assocs = this.lightWeightVersionStoreNodeService.getTargetAssocs( + version.getFrozenStateNodeRef(), + RegexQNamePattern.MATCH_ALL); + assertNotNull(assocs); + assertEquals(origAssocs.size(), assocs.size()); + } } /** @@ -277,7 +316,10 @@ public class NodeServiceImplTest extends BaseVersionStoreTest Set aspects = this.lightWeightVersionStoreNodeService.getAspects(version.getFrozenStateNodeRef()); assertEquals(origAspects.size(), aspects.size()); - // TODO check that the set's contain the same items + for (QName origAspect : origAspects) + { + assertTrue(origAspect+"",aspects.contains(origAspect)); + } } /** diff --git a/source/java/org/alfresco/repo/version/Version2Model.java b/source/java/org/alfresco/repo/version/Version2Model.java new file mode 100644 index 0000000000..58e369bd82 --- /dev/null +++ b/source/java/org/alfresco/repo/version/Version2Model.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import org.alfresco.service.namespace.QName; + +/** + * Version2 Model Constants used by version2Store implementation + */ +public interface Version2Model extends VersionBaseModel +{ + /** + * Namespace + */ + public static final String NAMESPACE_URI = "http://www.alfresco.org/model/versionstore/2.0"; + + /** + * The store id + */ + public static final String STORE_ID = "version2Store"; + + /** The version store root aspect */ + public static final QName ASPECT_VERSION_STORE_ROOT = QName.createQName(NAMESPACE_URI, ASPECT_LOCALNAME_VERSION_STORE_ROOT); + + /** + * Version history type + */ + public static final QName TYPE_QNAME_VERSION_HISTORY = QName.createQName(NAMESPACE_URI, TYPE_VERSION_HISTORY); + + /** + * Version history properties and associations + */ + public static final QName PROP_QNAME_VERSIONED_NODE_ID = QName.createQName(NAMESPACE_URI, PROP_VERSIONED_NODE_ID); + public static final QName ASSOC_ROOT_VERSION = QName.createQName(NAMESPACE_URI, ASSOC_LOCALNAME_ROOT_VERSION); + + /** + * Version aspect + aspect properties + */ + public static final String ASPECT_LOCALNAME_VERSION = "version"; + public static final QName ASPECT_VERSION = QName.createQName(NAMESPACE_URI, ASPECT_LOCALNAME_VERSION); + + public static final String PROP_VERSION_DESCRIPTION = "versionDescription"; // maps from description + + public static final QName PROP_QNAME_VERSION_LABEL = QName.createQName(NAMESPACE_URI, PROP_VERSION_LABEL); + public static final QName PROP_QNAME_VERSION_NUMBER = QName.createQName(NAMESPACE_URI, PROP_VERSION_NUMBER); + public static final QName PROP_QNAME_VERSION_DESCRIPTION = QName.createQName(NAMESPACE_URI, PROP_VERSION_DESCRIPTION); + + // frozen sys:referenceable properties (x4) + + public static final String PROP_FROZEN_NODE_REF = "frozenNodeRef"; + public static final QName PROP_QNAME_FROZEN_NODE_REF = QName.createQName(NAMESPACE_URI, PROP_FROZEN_NODE_REF); + + public static final String PROP_FROZEN_NODE_DBID = "frozenNodeDbId"; + public static final QName PROP_QNAME_FROZEN_NODE_DBID = QName.createQName(NAMESPACE_URI, PROP_FROZEN_NODE_DBID); + + // frozen cm:auditable properties (x5) + + public static final String PROP_FROZEN_CREATOR = "frozenCreator"; + public static final QName PROP_QNAME_FROZEN_CREATOR = QName.createQName(NAMESPACE_URI, PROP_FROZEN_CREATOR); + + public static final String PROP_FROZEN_CREATED = "frozenCreated"; + public static final QName PROP_QNAME_FROZEN_CREATED = QName.createQName(NAMESPACE_URI, PROP_FROZEN_CREATED); + + public static final String PROP_FROZEN_MODIFIER = "frozenModifer"; + public static final QName PROP_QNAME_FROZEN_MODIFIER = QName.createQName(NAMESPACE_URI, PROP_FROZEN_MODIFIER); + + public static final String PROP_FROZEN_MODIFIED = "frozenModified"; + public static final QName PROP_QNAME_FROZEN_MODIFIED = QName.createQName(NAMESPACE_URI, PROP_FROZEN_MODIFIED); + + public static final String PROP_FROZEN_ACCESSED = "frozenAccessed"; + public static final QName PROP_QNAME_FROZEN_ACCESSED = QName.createQName(NAMESPACE_URI, PROP_FROZEN_ACCESSED); + + + + public static final QName ASSOC_SUCCESSOR = QName.createQName(NAMESPACE_URI, "successor"); + + public static final String PROP_METADATA_PREFIX = "metadata-"; + + public static final String PROP_VERSION_TYPE = "versionType"; + + /** + * Child relationship names + */ + public static final QName CHILD_QNAME_VERSION_HISTORIES = QName.createQName(NAMESPACE_URI, CHILD_VERSION_HISTORIES); + public static final QName CHILD_QNAME_VERSIONS = QName.createQName(NAMESPACE_URI, CHILD_VERSIONS); + + + // Used by ML service + + /** + * Created version associated to the deleted translations of an mlContainer + */ + + public static final QName PROP_QNAME_TRANSLATION_VERSIONS = QName.createQName(VersionModel.NAMESPACE_URI, PROP_TRANSLATION_VERSIONS); +} + diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java new file mode 100644 index 0000000000..2cac7d5fa6 --- /dev/null +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -0,0 +1,1069 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.policy.PolicyScope; +import org.alfresco.repo.version.common.VersionHistoryImpl; +import org.alfresco.repo.version.common.VersionImpl; +import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.service.cmr.repository.AspectMissingException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.PermissionService; +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.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Version2 Service - implements version2Store (a lighter implementation of the lightWeightVersionStore) + */ +public class Version2ServiceImpl extends VersionServiceImpl implements VersionService, Version2Model +{ + private static Log logger = LogFactory.getLog(Version2ServiceImpl.class); + + protected boolean useDeprecatedV1 = false; // bypass V2, only use V1 + + private PermissionService permissionService; + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setOnlyUseDeprecatedV1(boolean useDeprecatedV1) + { + this.useDeprecatedV1 = useDeprecatedV1; + } + + /** + * Initialise method + */ + @Override + public void initialise() + { + super.initialise(); + + if (useDeprecatedV1) + { + logger.warn("version.store.onlyUseDeprecatedV1=true - using deprecated 'lightWeightVersionStore' by default (not 'version2Store')"); + } + } + + + /* (non-Javadoc) + * @see org.alfresco.repo.service.cmr.version.VersionService#getVersionStoreReference() + */ + @Override + public StoreRef getVersionStoreReference() + { + if (useDeprecatedV1) + { + return super.getVersionStoreReference(); + } + + return new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.service.cmr.version.VersionService#createVersion(org.alfresco.service.cmr.repository.NodeRef, java.util.Map) + */ + public Version createVersion( + NodeRef nodeRef, + Map versionProperties) + throws ReservedVersionNameException, AspectMissingException + { + if (useDeprecatedV1) + { + return super.createVersion(nodeRef, versionProperties); + } + + long startTime = System.currentTimeMillis(); + + // Get the next version number + int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); + + // Create the version + Version version = createVersion(nodeRef, versionProperties, versionNumber); + + if (logger.isDebugEnabled()) + { + logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in " + (System.currentTimeMillis()-startTime) + " ms"); + } + + return version; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.service.cmr.version.VersionService#createVersion(java.util.Collection, java.util.Map) + */ + public Collection createVersion( + Collection nodeRefs, + Map versionProperties) + throws ReservedVersionNameException, AspectMissingException + { + /* + * 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 version history. + */ + if (useDeprecatedV1) + { + return super.createVersion(nodeRefs, versionProperties); + } + + long startTime = System.currentTimeMillis(); + + Collection result = new ArrayList(nodeRefs.size()); + + // Get the next version number + int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); + + // Version each node in the list + for (NodeRef nodeRef : nodeRefs) + { + result.add(createVersion(nodeRef, versionProperties, versionNumber)); + } + + if (logger.isDebugEnabled()) + { + logger.debug("created version list (" + versionNumber + " - " + getVersionStoreReference() + ") in "+ (System.currentTimeMillis()-startTime) +" ms (with " + nodeRefs.size() + " nodes)"); + } + + return result; + } + + protected Version createVersion( + NodeRef nodeRef, + Map origVersionProperties, + int versionNumber) + throws ReservedVersionNameException + { + if (useDeprecatedV1) + { + return super.createVersion(nodeRef, origVersionProperties, versionNumber); + } + + long startTime = System.currentTimeMillis(); + + // Copy the version properties (to prevent unexpected side effects to the caller) + Map versionProperties = new HashMap(); + if (origVersionProperties != null) + { + versionProperties.putAll(origVersionProperties); + } + + // If the version aspect is not there then add it to the 'live' (versioned) node + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + } + + // Call the policy behaviour + invokeBeforeCreateVersion(nodeRef); + + // version "description" property is added as a standard version property (if not null) rather than a metadata version property + String versionDescription = (String)versionProperties.get(Version.PROP_DESCRIPTION); + versionProperties.remove(Version.PROP_DESCRIPTION); + + // don't freeze previous version label + versionProperties.remove(ContentModel.PROP_VERSION_LABEL); + + // 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 existing 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); + + // Calculate the version label + Version preceedingVersion = getVersion(currentVersionRef); + String versionLabel = invokeCalculateVersionLabel(classRef, preceedingVersion, versionNumber, versionProperties); + + // Extract Type Definition + QName sourceTypeRef = nodeService.getType(nodeRef); + + long nodeDbId = (Long)this.nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID); + Set nodeAspects = this.nodeService.getAspects(nodeRef); + + // Create the new version node (child of the version history) + NodeRef newVersionRef = createNewVersion( + sourceTypeRef, + versionHistoryRef, + getStandardVersionProperties(nodeRef, nodeDbId, nodeAspects, versionNumber, versionLabel, versionDescription), + versionProperties, + versionNumber, + nodeDetails); + + if (currentVersionRef == null) + { + // Set the new version to be the root version in the version history + this.dbNodeService.createAssociation( + versionHistoryRef, + newVersionRef, + Version2Model.ASSOC_ROOT_VERSION); + } + + // Create the version data object + Version version = getVersion(newVersionRef); + + // Set the new version label on the 'live' (versioned) node + this.nodeService.setProperty( + nodeRef, + ContentModel.PROP_VERSION_LABEL, + version.getVersionLabel()); + + // Invoke the policy behaviour + invokeAfterCreateVersion(nodeRef, version); + + if (logger.isTraceEnabled()) + { + logger.trace("created version (" + versionNumber + " - " + getVersionStoreReference() + ") " + newVersionRef + " " + (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 + */ + protected NodeRef createVersionHistory(NodeRef nodeRef) + { + HashMap props = new HashMap(); + props.put(ContentModel.PROP_NAME, nodeRef.getId()); + props.put(Version2Model.PROP_QNAME_VERSIONED_NODE_ID, nodeRef.getId()); + + // Create a new version history node + ChildAssociationRef childAssocRef = this.dbNodeService.createNode( + getRootNode(), + Version2Model.CHILD_QNAME_VERSION_HISTORIES, + QName.createQName(Version2Model.NAMESPACE_URI, nodeRef.getId()), + Version2Model.TYPE_QNAME_VERSION_HISTORY, + props); + + if (logger.isTraceEnabled()) + { + logger.trace("created version history nodeRef: " + childAssocRef.getChildRef() + " for " + nodeRef); + } + + return childAssocRef.getChildRef(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.service.cmr.version.VersionService#getVersionHistory(org.alfresco.service.cmr.repository.NodeRef) + */ + public VersionHistory getVersionHistory(NodeRef nodeRef) + { + if (useDeprecatedV1) + { + return super.getVersionHistory(nodeRef); + } + + VersionHistory versionHistory = null; + + // get version history, if the 'live' (versioned) node exists + if (this.nodeService.exists(nodeRef) == true) + { + NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryRef != null) + { + versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); + } + } + + return versionHistory; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.service.cmr.version.VersionService#getCurrentVersion(org.alfresco.service.cmr.repository.NodeRef) + */ + public Version getCurrentVersion(NodeRef nodeRef) + { + if (useDeprecatedV1) + { + return super.getCurrentVersion(nodeRef); + } + + Version version = null; + + // get the current version, if the 'live' (versioned) node has the "versionable" aspect + 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 nodeRef the node reference + * @return the standard version properties + */ + protected Map getStandardVersionProperties(NodeRef nodeRef, long nodeDbId, Set nodeAspects, int versionNumber, String versionLabel, String versionDescription) + { + Map result = new HashMap(10); + + // Set the version number + result.put(Version2Model.PROP_QNAME_VERSION_NUMBER, Integer.toString(versionNumber)); + + // Set the version label + result.put(Version2Model.PROP_QNAME_VERSION_LABEL, versionLabel); + + // Set the version description (can be null) + result.put(Version2Model.PROP_QNAME_VERSION_DESCRIPTION, versionDescription); + + // Set the versionable node store id + result.put(Version2Model.PROP_QNAME_FROZEN_NODE_REF, nodeRef); + + // Set the versionable node store id + result.put(Version2Model.PROP_QNAME_FROZEN_NODE_DBID, nodeDbId); + + 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 + */ + protected NodeRef createNewVersion( + QName sourceTypeRef, + NodeRef versionHistoryRef, + Map standardVersionProperties, + Map versionProperties, + int versionNumber, + PolicyScope nodeDetails) + { + ChildAssociationRef childAssocRef = null; + + // Disable auto-version behaviour + this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE); + + this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_MULTILINGUAL_DOCUMENT); + this.policyBehaviourFilter.disableBehaviour(ContentModel.TYPE_MULTILINGUAL_CONTAINER); + + NodeRef versionNodeRef = null; + + try + { + // "copy" type and properties + childAssocRef = this.dbNodeService.createNode( + versionHistoryRef, + Version2Model.CHILD_QNAME_VERSIONS, + QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.CHILD_VERSIONS+"-"+versionNumber), // TODO - testing - note: all children (of a versioned node) will have the same version number, maybe replace with a version sequence of some sort 001-...00n + sourceTypeRef, + nodeDetails.getProperties()); + + versionNodeRef = childAssocRef.getChildRef(); + + // NOTE: special ML case - see also MultilingualContentServiceImpl.makeMLContainer + if (sourceTypeRef.equals(ContentModel.TYPE_MULTILINGUAL_CONTAINER)) + { + // Set the permissions to allow anything by anyone + permissionService.setPermission( + versionNodeRef, + PermissionService.ALL_AUTHORITIES, + PermissionService.ALL_PERMISSIONS, true); + permissionService.setPermission( + versionNodeRef, + PermissionService.GUEST_AUTHORITY, + PermissionService.ALL_PERMISSIONS, true); + } + + // add aspect with the standard version properties to the 'version' node + nodeService.addAspect(versionNodeRef, Version2Model.ASPECT_VERSION, standardVersionProperties); + + // store the meta data + storeVersionMetaData(versionNodeRef, versionProperties); + + freezeChildAssociations(versionNodeRef, nodeDetails.getChildAssociations()); + freezeAspects(nodeDetails, versionNodeRef, nodeDetails.getAspects()); + + } + finally + { + // Enable auto-version behaviour + 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) == false) + { + dbNodeService.addAspect(versionNodeRef, ContentModel.ASPECT_AUDITABLE, null); + } + + if (logger.isTraceEnabled()) + { + logger.trace("newVersion created (" + versionNumber + ") " + versionNodeRef); + } + + // Return the created node reference + return versionNodeRef; + } + + /** + * Store the version meta data + * + * @param versionNodeRef the version node reference + * @param versionProperties the version properties + */ + private void storeVersionMetaData(NodeRef versionNodeRef, Map versionProperties) + { + // TODO - these are stored as "residual" properties (ie. without property type) - see also NodeBrowser + // - need to review, eg. how can we store arbitrary map of metadata properties, that could be indexed/searched (if configured against versionStore) + for (Map.Entry entry : versionProperties.entrySet()) + { + dbNodeService.setProperty(versionNodeRef, QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.PROP_METADATA_PREFIX+entry.getKey()), entry.getValue()); + } + } + + /** + * Freeze the aspects + * + * @param nodeDetails the node details + * @param versionNodeRef the version node reference + * @param aspects the set of aspects + */ + private void freezeAspects(PolicyScope nodeDetails, NodeRef versionNodeRef, Set aspects) + { + for (QName aspect : aspects) + { + if (logger.isTraceEnabled()) + { + logger.trace("freezeAspect: " + versionNodeRef + " " + aspect); + } + + if (aspect.equals(ContentModel.ASPECT_AUDITABLE)) + { + // freeze auditable aspect properties (eg. created, creator, modifed, modifier, accessed) + for (Map.Entry entry : nodeDetails.getProperties(aspect).entrySet()) + { + if (entry.getKey().equals(ContentModel.PROP_CREATOR)) + { + dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_CREATOR, entry.getValue()); + } + else if (entry.getKey().equals(ContentModel.PROP_CREATED)) + { + dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_CREATED, entry.getValue()); + } + else if (entry.getKey().equals(ContentModel.PROP_MODIFIER)) + { + dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_MODIFIER, entry.getValue()); + } + else if (entry.getKey().equals(ContentModel.PROP_MODIFIED)) + { + dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_MODIFIED, entry.getValue()); + } + else if (entry.getKey().equals(ContentModel.PROP_ACCESSED)) + { + dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_FROZEN_ACCESSED, entry.getValue()); + } + else + { + throw new AlfrescoRuntimeException("Unexpected auditable property: " + entry.getKey()); + } + } + } + else + { + // Freeze the details of the aspect + dbNodeService.addAspect(versionNodeRef, aspect, nodeDetails.getProperties(aspect)); + } + } + } + + private void freezeChildAssociations(NodeRef versionNodeRef, List childAssociations) + { + for (ChildAssociationRef childAssocRef : childAssociations) + { + HashMap properties = new HashMap(); + + QName sourceTypeRef = nodeService.getType(childAssocRef.getChildRef()); + + // 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, + childAssocRef.getTypeQName(), + childAssocRef.getQName(), + sourceTypeRef, + properties); + } + } + + /** + * Builds a version history object from the version history reference. + *

+ * The node ref is passed to enable the version history to be scoped to the + * appropriate branch in the version history. + * + * @param versionHistoryRef the node ref for the version history + * @param nodeRef the node reference + * @return a constructed version history object + */ + protected VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef) + { + if (useDeprecatedV1) + { + return super.buildVersionHistory(versionHistoryRef, nodeRef); + } + + VersionHistory versionHistory = null; + + List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryRef, Version2Model.CHILD_QNAME_VERSIONS, RegexQNamePattern.MATCH_ALL); + + Map versionHistoryMap = new HashMap(versionsAssoc.size()); + for (ChildAssociationRef versionAssoc : versionsAssoc) + { + String localName = versionAssoc.getQName().getLocalName(); + if (localName.indexOf(Version2Model.CHILD_VERSIONS+"-") != -1) // TODO - could remove this belts-and-braces, should match correctly above ! + { + int versionNumber = Integer.parseInt(localName.substring((Version2Model.CHILD_VERSIONS+"-").length())); + versionHistoryMap.put(versionNumber, versionAssoc.getChildRef()); + } + } + + Map sortedMap = new TreeMap(versionHistoryMap); + + // Build the version history object + boolean isRoot = true; + Version preceeding = null; + for (NodeRef versionRef : sortedMap.values()) + { + 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 (useDeprecatedV1) + { + return super.getVersion(versionRef); + } + + if (versionRef == null) + { + return null; + } + Map versionProperties = new HashMap(); + + // Get the standard node details and get the meta data + Map nodeProperties = this.dbNodeService.getProperties(versionRef); + + if (logger.isTraceEnabled()) + { + logger.trace("getVersion: " + versionRef + " " + nodeProperties.keySet()); + } + + // TODO consolidate with VersionUtil.convertFrozenToOriginalProps + for (QName key : nodeProperties.keySet()) + { + Serializable value = nodeProperties.get(key); + + String keyName = key.getLocalName(); + int idx = keyName.indexOf(Version2Model.PROP_METADATA_PREFIX); + if (idx == 0) + { + // versioned metadata property - additional (optional) metadata, set during versioning + versionProperties.put(keyName.substring(Version2Model.PROP_METADATA_PREFIX.length()), value); + } + else + { + if (key.equals(Version2Model.PROP_QNAME_VERSION_DESCRIPTION)) + { + versionProperties.put(Version.PROP_DESCRIPTION, (String)value); + } + else if (key.equals(Version2Model.PROP_QNAME_VERSION_LABEL)) + { + versionProperties.put(VersionBaseModel.PROP_VERSION_LABEL, (String)value); + } + else if (key.equals(Version2Model.PROP_QNAME_VERSION_NUMBER)) + { + versionProperties.put(VersionBaseModel.PROP_VERSION_NUMBER, (String)value); + } + else + { + // all other properties + versionProperties.put(keyName, value); + } + } + } + + // Create and return the version object + NodeRef newNodeRef = new NodeRef(new StoreRef(Version2Model.STORE_PROTOCOL, Version2Model.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 (useDeprecatedV1) + { + return super.getVersionHistoryNodeRef(nodeRef); + } + + return this.dbNodeService.getChildByName(getRootNode(), Version2Model.CHILD_QNAME_VERSION_HISTORIES, nodeRef.getId()); + } + + /** + * Gets a reference to the node for the current version of the passed node ref. + * + * This uses the version label as a mechanism for looking up the version node in + * the version history. + * + * @param nodeRef a node reference + * @return a reference to a version reference + */ + private NodeRef getCurrentVersionNodeRef(NodeRef versionHistory, NodeRef nodeRef) + { + NodeRef result = null; + String versionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + + Collection versions = this.dbNodeService.getChildAssocs(versionHistory); + for (ChildAssociationRef version : versions) + { + String tempLabel = (String)this.dbNodeService.getProperty(version.getChildRef(), Version2Model.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) + { + if (useDeprecatedV1) + { + super.revert(nodeRef, getCurrentVersion(nodeRef), true); + } + else + { + 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) + { + if (useDeprecatedV1) + { + super.revert(nodeRef, getCurrentVersion(nodeRef), deep); + } + else + { + 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) + { + if (useDeprecatedV1) + { + super.revert(nodeRef, version, true); + } + else + { + 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) + { + if (useDeprecatedV1) + { + super.revert(nodeRef, version, deep); + } + else + { + // 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(((NodeRef)version.getVersionProperty(Version2Model.PROP_FROZEN_NODE_REF)).getId()) == 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 + Map props = this.nodeService.getProperties(versionNodeRef); + VersionUtil.convertFrozenToOriginalProps(props); + + this.nodeService.setProperties(nodeRef, props); + + // Apply/remove the aspects as required + Set aspects = new HashSet(this.nodeService.getAspects(nodeRef)); + for (QName versionAspect : this.nodeService.getAspects(versionNodeRef)) + { + if (aspects.contains(versionAspect) == false) + { + this.nodeService.addAspect(nodeRef, versionAspect, null); + } + else + { + aspects.remove(versionAspect); + } + } + for (QName aspect : aspects) + { + this.nodeService.removeAspect(nodeRef, aspect); + } + + // Re-add the versionable aspect to the reverted node + if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + } + + // Re-set the version label property (since it should not be modified from the original) + this.nodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, currentVersionLabel); + + // Add/remove the child nodes + List children = new ArrayList(this.nodeService.getChildAssocs(nodeRef)); + for (ChildAssociationRef versionedChild : this.nodeService.getChildAssocs(versionNodeRef)) + { + if (children.contains(versionedChild) == false) + { + if (this.nodeService.exists(versionedChild.getChildRef()) == true) + { + // The node was a primary child of the parent, but that is no longer the case. Dispite this + // the node still exits so this means it has been moved. + // The best thing to do in this situation will be to re-add the node as a child, but it will not + // be a primary child. + this.nodeService.addChild(nodeRef, versionedChild.getChildRef(), versionedChild.getTypeQName(), versionedChild.getQName()); + } + else + { + if (versionedChild.isPrimary() == true) + { + // Only try to resotre missing children if we are doing a deep revert + // Look and see if we have a version history for the child node + if (deep == true && getVersionHistoryNodeRef(versionedChild.getChildRef()) != null) + { + // We're going to try and restore the missing child node and recreate the assoc + restore( + versionedChild.getChildRef(), + nodeRef, + versionedChild.getTypeQName(), + versionedChild.getQName()); + } + // else the deleted child did not have a version history so we can't restore the child + // and so we can't revert the association + } + + // else + // Since this was never a primary assoc and the child has been deleted we won't recreate + // the missing node as it was never owned by the node and we wouldn't know where to put it. + } + } + else + { + children.remove(versionedChild); + } + } + for (ChildAssociationRef ref : children) + { + this.nodeService.removeChild(nodeRef, ref.getChildRef()); + } + } + finally + { + // Turn auto-version policies back on + this.policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); + } + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.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) + { + if (useDeprecatedV1) + { + return super.restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true); + } + + return restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, true); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.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) + { + if (useDeprecatedV1) + { + return super.restore(nodeRef, parentNodeRef, assocTypeQName, assocQName, deep); + } + + NodeRef restoredNodeRef = null; + + // Check that the node does not exist + if (this.nodeService.exists(nodeRef) == true) + { + // Error since you can not restore a node that already exists + throw new VersionServiceException(MSGID_ERR_RESTORE_EXISTS, new Object[]{nodeRef.toString()}); + } + + // Try and get the version details that we want to restore to + Version version = getHeadVersion(nodeRef); + if (version == null) + { + // Error since there is no version information available to restore the node from + throw new VersionServiceException(MSGID_ERR_RESTORE_NO_VERSION, new Object[]{nodeRef.toString()}); + } + + // Set the uuid of the new node + Map props = new HashMap(1); + props.put(ContentModel.PROP_NODE_UUID, ((NodeRef)version.getVersionProperty(Version2Model.PROP_FROZEN_NODE_REF)).getId()); + + // Get the type of the frozen node + QName type = (QName)dbNodeService.getType(VersionUtil.convertNodeRef(version.getFrozenStateNodeRef())); + + // Disable auto-version behaviour + this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE); + try + { + // Create the restored node + restoredNodeRef = this.nodeService.createNode( + parentNodeRef, + assocTypeQName, + assocQName, + type, + props).getChildRef(); + } + finally + { + // Enable auto-version behaviour + this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE); + } + + // Now we need to revert the newly restored node + revert(restoredNodeRef, version, deep); + + return restoredNodeRef; + } + + /** + * Get the head version given a node reference + * + * @param nodeRef the node reference + * @return the 'head' version + */ + private Version getHeadVersion(NodeRef nodeRef) + { + Version version = null; + StoreRef storeRef = nodeRef.getStoreRef(); + + NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryNodeRef != null) + { + List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, Version2Model.CHILD_QNAME_VERSIONS, RegexQNamePattern.MATCH_ALL); + for (ChildAssociationRef versionAssoc : versionsAssoc) + { + NodeRef versionNodeRef = versionAssoc.getChildRef(); + List successors = this.dbNodeService.getTargetAssocs(versionNodeRef, Version2Model.ASSOC_SUCCESSOR); + if (successors.size() == 0) + { + NodeRef versionedNodeRef = (NodeRef)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.PROP_FROZEN_NODE_REF)); + StoreRef versionStoreRef = versionedNodeRef.getStoreRef(); + if (storeRef.equals(versionStoreRef) == true) + { + version = getVersion(versionNodeRef); + } + } + } + } + + return version; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.service.cmr.version.VersionService#deleteVersionHistory(org.alfresco.service.cmr.repository.NodeRef) + */ + public void deleteVersionHistory(NodeRef nodeRef) + throws AspectMissingException + { + if (useDeprecatedV1) + { + super.deleteVersionHistory(nodeRef); + } + else + { + // 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); + } + } + } + } +} diff --git a/source/java/org/alfresco/repo/version/VersionBaseModel.java b/source/java/org/alfresco/repo/version/VersionBaseModel.java new file mode 100644 index 0000000000..c34c285086 --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionBaseModel.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.version.VersionService; + +/** + * Base Version Model interface containing the common local names (and other constants) + * used by the lightWeightVersionStore and version2Store implementations + */ +public interface VersionBaseModel +{ + /** + * The store protocol + */ + public static final String STORE_PROTOCOL = VersionService.VERSION_STORE_PROTOCOL; + + public static final String PROP_DESCRIPTION = "description"; + + public static final String PROP_VERSION_DESCRIPTION = "versionDescription"; + + public static final String PROP_VERSION_LABEL = "versionLabel"; + public static final String PROP_CREATED_DATE = ContentModel.PROP_CREATED.getLocalName(); + public static final String PROP_CREATOR = ContentModel.PROP_CREATOR.getLocalName(); + public static final String PROP_VERSION_TYPE = "versionType"; + public static final String PROP_VERSION_NUMBER = "versionNumber"; + + /** The version store root aspect localname*/ + public static final String ASPECT_LOCALNAME_VERSION_STORE_ROOT = "versionStoreRoot"; + + /** + * Version history type + */ + public static final String TYPE_VERSION_HISTORY = "versionHistory"; + + /** + * Version history properties and associations + */ + public static final String PROP_VERSIONED_NODE_ID = "versionedNodeId"; + public static final String ASSOC_LOCALNAME_ROOT_VERSION = "rootVersion"; + + + /** + * Child relationship names + */ + public static final String CHILD_VERSION_HISTORIES = "versionHistory"; + public static final String CHILD_VERSIONS = "version"; + + // Used by ML service + + /** + * Created version associated to the deleted translations of an mlContainer + */ + public static final String PROP_TRANSLATION_VERSIONS = "translationVersions"; +} diff --git a/source/java/org/alfresco/repo/version/VersionMigrator.java b/source/java/org/alfresco/repo/version/VersionMigrator.java new file mode 100644 index 0000000000..710fde2fd5 --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionMigrator.java @@ -0,0 +1,659 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager; +import org.alfresco.repo.node.MLPropertyInterceptor; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.PolicyScope; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +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.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Version2 Migrator + */ +public class VersionMigrator +{ + protected static Log logger = LogFactory.getLog(VersionMigrator.class); + + public static final StoreRef VERSION_STORE_REF_OLD = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, VersionModel.STORE_ID); + public static final StoreRef VERSION_STORE_REF_NEW = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID); + + /** track completion * */ + int percentComplete; + + /** start time * */ + long startTime; + + + private static final String MSG_PATCH_NOOP = "version_service.migration.patch.noop"; + private static final String MSG_PATCH_COMPLETE = "version_service.migration.patch.complete"; + private static final String MSG_PATCH_SKIP1 = "version_service.migration.patch.warn.skip1"; + private static final String MSG_PATCH_SKIP2 = "version_service.migration.patch.warn.skip2"; + + private static final String MSG_DELETE_PROGRESS = "version_service.migration.delete.progress"; + private static final String MSG_DELETE_COMPLETE = "version_service.migration.delete.complete"; + private static final String MSG_DELETE_SKIP1 = "version_service.migration.delete.warn.skip1"; + private static final String MSG_DELETE_SKIP2 = "version_service.migration.delete.warn.skip2"; + + private static final String MSG_PATCH_PROGRESS = "patch.progress"; + + private static final long RANGE_10 = 1000 * 60 * 90; + private static final long RANGE_5 = 1000 * 60 * 60 * 4; + private static final long RANGE_2 = 1000 * 60 * 90 * 10; + + private static boolean busy = false; + + public final static String PREFIX_MIGRATED = "migrated-"; + + private VersionServiceImpl version1Service = new VersionServiceImpl(); + + private Version2ServiceImpl version2Service; + private NodeService dbNodeService; + private BehaviourFilter policyBehaviourFilter; + private DictionaryService dictionaryService; + private TransactionService transactionService; + private NodeService versionNodeService; // NodeService impl which redirects to appropriate VersionService + + public void setVersion2ServiceImpl(Version2ServiceImpl versionService) + { + this.version2Service = versionService; + } + + public void setDbNodeService(NodeService nodeService) + { + this.dbNodeService = nodeService; + } + + public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) + { + this.policyBehaviourFilter = policyBehaviourFilter; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setVersionNodeService(NodeService versionNodeService) + { + this.versionNodeService = versionNodeService; + } + + public void init() + { + version1Service.setNodeService(dbNodeService); + version1Service.setDbNodeService(dbNodeService); + + version2Service.setDbNodeService(dbNodeService); + } + + public NodeRef migrateVersionHistory(NodeRef oldVHNodeRef, NodeRef versionedNodeRef) + { + if (logger.isTraceEnabled()) + { + logger.trace("migrateVersionHistory: oldVersionHistoryRef = " + oldVHNodeRef); + } + + VersionHistory vh = v1BuildVersionHistory(oldVHNodeRef, versionedNodeRef); + + // create new version history node + NodeRef newVHNodeRef = v2CreateVersionHistory(versionedNodeRef); + + Version[] oldVersions = (Version[])vh.getAllVersions().toArray(new Version[]{}); + + // Disable auditable behaviour - so that migrated versions maintain their original auditable properties (eg. created, creator) + this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); + + try + { + for (int i = (oldVersions.length-1); i >= 0; i--) + { + // migrate versions + v2CreateNewVersion(newVHNodeRef, oldVersions[i]); + } + } + finally + { + // Enable auditable behaviour + this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + + return newVHNodeRef; + } + + private NodeRef v2CreateVersionHistory(NodeRef nodeRef) + { + return version2Service.createVersionHistory(nodeRef); + } + + private NodeRef v2CreateNewVersion(NodeRef newVersionHistoryRef, Version oldVersion) + { + NodeRef versionedNodeRef = oldVersion.getVersionedNodeRef(); // nodeRef to versioned node in live store + NodeRef frozenStateNodeRef = oldVersion.getFrozenStateNodeRef(); // nodeRef to version node in version store + + if (logger.isTraceEnabled()) + { + logger.trace("v2CreateNewVersion: oldVersionRef = " + frozenStateNodeRef + " " + oldVersion); + } + + String versionLabel = oldVersion.getVersionLabel(); + String versionDescription = oldVersion.getDescription(); + + QName sourceType = versionNodeService.getType(frozenStateNodeRef); + Set nodeAspects = versionNodeService.getAspects(frozenStateNodeRef); + Map nodeProperties = versionNodeService.getProperties(frozenStateNodeRef); + + long nodeDbId = (Long)nodeProperties.get(ContentModel.PROP_NODE_DBID); + nodeProperties.remove(ContentModel.PROP_NODE_UUID); // else will try to persist with this node uuid (see AbstractNodeServiceImpl.generateGuid) + + int versionNumber = (Integer)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), VersionModel.PROP_QNAME_VERSION_NUMBER); + + // get oldVersion auditable properties (of the version node itself, rather than the live versioned node) + Date versionCreated = (Date)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_CREATED); + String versionCreator = (String)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_CREATOR); + Date versionModified = (Date)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_MODIFIED); + String versionModifier = (String)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_MODIFIER); + Date versionAccessed = (Date)dbNodeService.getProperty(VersionUtil.convertNodeRef(frozenStateNodeRef), ContentModel.PROP_ACCESSED); + + Map versionMetaDataProperties = version1Service.getVersionMetaData(VersionUtil.convertNodeRef(frozenStateNodeRef)); + + // Create the node details + PolicyScope nodeDetails = new PolicyScope(sourceType); + + // add properties + for (Map.Entry entry : nodeProperties.entrySet()) + { + nodeDetails.addProperty(sourceType, entry.getKey(), entry.getValue()); + } + + // add aspects + for (QName aspect : nodeAspects) + { + // add aspect + nodeDetails.addAspect(aspect); + + // copy the aspect properties + ClassDefinition classDefinition = dictionaryService.getClass(aspect); + if (classDefinition != null) + { + Map propertyDefinitions = classDefinition.getProperties(); + for (QName propertyName : propertyDefinitions.keySet()) + { + Serializable propValue = nodeProperties.get(propertyName); + nodeDetails.addProperty(aspect, propertyName, propValue); + } + } + } + + NodeRef newVersionRef = version2Service.createNewVersion( + sourceType, + newVersionHistoryRef, + version2Service.getStandardVersionProperties(versionedNodeRef, nodeDbId, nodeAspects, versionNumber, versionLabel, versionDescription), + versionMetaDataProperties, + versionNumber, + nodeDetails); + + // set newVersion auditable properties (of the version node itself, rather than the live versioned node) + Map props = dbNodeService.getProperties(newVersionRef); + props.put(ContentModel.PROP_CREATED, versionCreated); + props.put(ContentModel.PROP_CREATOR, versionCreator); + props.put(ContentModel.PROP_MODIFIED, versionModified); + props.put(ContentModel.PROP_MODIFIER, versionModifier); + props.put(ContentModel.PROP_ACCESSED, versionAccessed); + dbNodeService.setProperties(newVersionRef, props); + + return newVersionRef; + } + + protected NodeRef v1GetVersionedNodeRef(NodeRef oldVersionHistoryRef) + { + NodeRef versionedNodeRef = null; + + // Get versioned nodeRef from one of the versions - note: assumes all versions refer to the same versioned nodeRef + Collection versions = dbNodeService.getChildAssocs(oldVersionHistoryRef); + if (versions.size() > 0) + { + Iterator itr = versions.iterator(); + ChildAssociationRef childAssocRef = itr.next(); + NodeRef versionRef = childAssocRef.getChildRef(); + + Version version = version1Service.getVersion(versionRef); + + versionedNodeRef = version.getVersionedNodeRef(); + } + + return versionedNodeRef; + } + + private VersionHistory v1BuildVersionHistory(NodeRef oldVersionHistoryRef, NodeRef versionedNodeRef) + { + return version1Service.buildVersionHistory(oldVersionHistoryRef, versionedNodeRef); + } + + protected void v1DeleteVersionHistory(NodeRef oldVersionHistoryRef) + { + dbNodeService.deleteNode(oldVersionHistoryRef); + } + + private void v1MarkVersionHistory(NodeRef oldVersionHistoryRef) + { + String migratedName = PREFIX_MIGRATED+oldVersionHistoryRef.getId(); + dbNodeService.setProperty(oldVersionHistoryRef, ContentModel.PROP_NAME, migratedName); + } + + public List getVersionHistories(final NodeRef rootNodeRef) + { + return dbNodeService.getChildAssocs(rootNodeRef); + } + + public int migrateVersions(final int batchSize, final boolean deleteImmediately) + { + final NodeRef oldRootNodeRef = dbNodeService.getRootNode(VersionMigrator.VERSION_STORE_REF_OLD); + + final List childAssocRefs = getVersionHistories(oldRootNodeRef); + + int toDo = childAssocRefs.size(); + + if (toDo == 0) + { + logger.info(I18NUtil.getMessage(MSG_PATCH_NOOP)); + return 0; + } + + if (logger.isDebugEnabled()) + { + logger.debug("Found "+toDo+" version histories in old version store"); + } + + // note: assumes patch runs before cleanup starts + startTime = System.currentTimeMillis(); + percentComplete = 0; + + int vhCount = 0; + int alreadyMigratedCount = 0; + int failCount = 0; + + RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); + + boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); + SessionSizeResourceManager.setDisableInTransaction(); + + try + { + int batchCount = 0; + int totalCount = 0; + + final List tmpBatch = new ArrayList(batchSize); + + for (final ChildAssociationRef childAssocRef : childAssocRefs) + { + reportProgress(MSG_PATCH_PROGRESS, toDo, totalCount); + totalCount++; + + if (((String)dbNodeService.getProperty(childAssocRef.getChildRef(), ContentModel.PROP_NAME)).startsWith(VersionMigrator.PREFIX_MIGRATED)) + { + // skip - already migrated + alreadyMigratedCount++; + continue; + } + + if (batchCount < batchSize) + { + tmpBatch.add(childAssocRef.getChildRef()); + batchCount++; + } + + if ((batchCount == batchSize) || (totalCount == childAssocRefs.size())) + { + while (tmpBatch.size() != 0) + { + txHelper.setMaxRetries(1); + + NodeRef failed = txHelper.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + if (logger.isTraceEnabled()) + { + logger.trace("Attempt to migrate batch of "+tmpBatch.size()+" version histories"); + } + + long startTime = System.currentTimeMillis(); + + for (NodeRef oldVHNodeRef : tmpBatch) + { + try + { + NodeRef versionedNodeRef = v1GetVersionedNodeRef(oldVHNodeRef); + migrateVersionHistory(oldVHNodeRef, versionedNodeRef); + + if (deleteImmediately) + { + // delete old version history node + v1DeleteVersionHistory(oldVHNodeRef); + } + else + { + // mark old version history node for later cleanup + v1MarkVersionHistory(oldVHNodeRef); + } + } + catch (Throwable t) + { + logger.error("Skipping migration of: " + oldVHNodeRef, t); + return oldVHNodeRef; + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("Migrated batch of "+tmpBatch.size()+" version histories in "+(System.currentTimeMillis()-startTime)+ " ms"); + } + + return null; + } + }, false, true); + + if (failed != null) + { + tmpBatch.remove(failed); // retry batch without the failed node + failCount++; + } + else + { + vhCount = vhCount + tmpBatch.size(); + tmpBatch.clear(); + batchCount = 0; + } + } + } + } + } + finally + { + MLPropertyInterceptor.setMLAware(wasMLAware); + SessionSizeResourceManager.setEnableInTransaction(); + } + + if (failCount > 0) + { + logger.warn(I18NUtil.getMessage(MSG_PATCH_SKIP1, failCount)); + } + else if (alreadyMigratedCount > 0) + { + logger.warn(I18NUtil.getMessage(MSG_PATCH_SKIP2, alreadyMigratedCount)); + } + + logger.info(I18NUtil.getMessage(MSG_PATCH_COMPLETE, vhCount, ((System.currentTimeMillis()-startTime)/1000))); + + return vhCount; + } + + + public void executeCleanup(final int batchSize) + { + if (! busy) + { + try + { + busy = true; + + final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); + txHelper.setMaxRetries(1); + txHelper.doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + NodeRef oldRootNodeRef = dbNodeService.getRootNode(VersionMigrator.VERSION_STORE_REF_OLD); + List childAssocRefs = getVersionHistories(oldRootNodeRef); + + int toDo = childAssocRefs.size(); + + if (toDo > 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("Found "+toDo+" version histories in old version store"); + } + + // note: assumes cleanup runs after patch has completed + startTime = System.currentTimeMillis(); + percentComplete = 0; + + int deletedCount = 0; + int failCount = 0; + int notMigratedCount = 0; + + boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); + SessionSizeResourceManager.setDisableInTransaction(); + + try + { + int batchCount = 0; + int totalCount = 0; + + final List tmpBatch = new ArrayList(batchSize); + + for (final ChildAssociationRef childAssocRef : childAssocRefs) + { + reportProgress(MSG_DELETE_PROGRESS, toDo, totalCount); + totalCount++; + + if (((String)dbNodeService.getProperty(childAssocRef.getChildRef(), ContentModel.PROP_NAME)).startsWith(VersionMigrator.PREFIX_MIGRATED)) + { + if (batchCount < batchSize) + { + tmpBatch.add(childAssocRef.getChildRef()); + batchCount++; + } + + if ((batchCount == batchSize) || (totalCount == childAssocRefs.size())) + { + while (tmpBatch.size() != 0) + { + txHelper.setMaxRetries(1); + NodeRef failed = txHelper.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + if (logger.isTraceEnabled()) + { + logger.trace("Attempt to delete batch of "+tmpBatch.size()+" migrated version histories"); + } + + long startTime = System.currentTimeMillis(); + + for (NodeRef oldVHNodeRef : tmpBatch) + { + try + { + // delete old version history node + v1DeleteVersionHistory(oldVHNodeRef); + } + catch (Throwable t) + { + logger.error("Skipping deletion of: " + oldVHNodeRef, t); + return oldVHNodeRef; + } + } + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted batch of "+tmpBatch.size()+" migrated version histories in "+(System.currentTimeMillis()-startTime)+ " ms"); + } + + return null; + } + }, false, true); + + if (failed != null) + { + tmpBatch.remove(failed); // retry batch without the failed node + failCount++; + } + else + { + deletedCount = deletedCount + tmpBatch.size(); + tmpBatch.clear(); + batchCount = 0; + } + } + } + } + else + { + notMigratedCount++; + } + } + } + finally + { + MLPropertyInterceptor.setMLAware(wasMLAware); + SessionSizeResourceManager.setEnableInTransaction(); + } + + if (failCount > 0) + { + logger.warn(I18NUtil.getMessage(MSG_DELETE_SKIP1, failCount)); + } + + if (notMigratedCount > 0) + { + logger.warn(I18NUtil.getMessage(MSG_DELETE_SKIP2, notMigratedCount)); + } + + logger.info(I18NUtil.getMessage(MSG_DELETE_COMPLETE, deletedCount, ((System.currentTimeMillis()-startTime)/1000))); + } + + return null; + } + }, true, true); + } + finally + { + busy = false; + } + } + } + + /** + * Support to report % completion and estimated completion time. + * + * @param estimatedTotal + * @param currentInteration + */ + protected void reportProgress(String msgKey, long estimatedTotal, long currentInteration) + { + if (currentInteration == 0) + { + percentComplete = 0; + } + else if (currentInteration * 100l / estimatedTotal > percentComplete) + { + int previous = percentComplete; + percentComplete = (int) (currentInteration * 100l / estimatedTotal); + + if (percentComplete < 100) + { + // conditional report + long currentTime = System.currentTimeMillis(); + long timeSoFar = currentTime - startTime; + long timeRemaining = timeSoFar * (100 - percentComplete) / percentComplete; + + int report = -1; + + if (timeRemaining > 60000) + { + int reportInterval = getreportingInterval(timeSoFar, timeRemaining); + + for (int i = previous + 1; i <= percentComplete; i++) + { + if (i % reportInterval == 0) + { + report = i; + } + } + if (report > 0) + { + Date end = new Date(currentTime + timeRemaining); + logger.info(I18NUtil.getMessage(msgKey, report, end)); + } + } + } + } + } + + private int getreportingInterval(long soFar, long toGo) + { + long total = soFar + toGo; + if (total < RANGE_10) + { + return 10; + } + else if (total < RANGE_5) + { + return 5; + } + else if (total < RANGE_2) + { + return 2; + } + else + { + return 1; + } + } +} diff --git a/source/java/org/alfresco/repo/version/VersionMigratorTest.java b/source/java/org/alfresco/repo/version/VersionMigratorTest.java new file mode 100644 index 0000000000..b9e74e2899 --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionMigratorTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.version; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Test simple version store migration + */ +public class VersionMigratorTest extends BaseVersionStoreTest +{ + private static Log logger = LogFactory.getLog(VersionMigratorTest.class); + + protected VersionServiceImpl version1Service = new VersionServiceImpl(); + + protected Version2ServiceImpl version2Service; + protected NodeService versionNodeService; + + protected VersionMigrator versionMigrator; + protected PolicyComponent policyComponent; + protected DictionaryService dictionaryService; + + public VersionMigratorTest() + { + //super.setDefaultRollback(false); // default is true + } + + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + + this.versionMigrator = (VersionMigrator)applicationContext.getBean("versionMigrator"); + this.policyComponent = (PolicyComponent)applicationContext.getBean("policyComponent"); + this.dictionaryService = (DictionaryService)applicationContext.getBean("dictionaryService"); + this.version2Service = (Version2ServiceImpl)applicationContext.getBean("versionService"); + this.versionNodeService = (NodeService)applicationContext.getBean("versionNodeService"); // note: auto-switches between V1 and V2 + + // Version1Service is used to create the version nodes in Version1Store (workspace://lightWeightVersionStore) + version1Service.setDbNodeService(dbNodeService); + version1Service.setNodeService(dbNodeService); + version1Service.setVersionCounterService(versionCounterDaoService); + version1Service.setPolicyComponent(policyComponent); + version1Service.setDictionaryService(dictionaryService); + version1Service.initialiseWithoutBind(); // TODO - temp - if use intialise, get: "More than one CalculateVersionLabelPolicy behaviour has been registered for the type {http://www.alfresco.org/model/content/1.0}content" + + super.setVersionService(version1Service); + } + + /** + * Test migration of a simple versioned node (one version, no children) + */ + public void testMigrateOneVersion() throws Exception + { + if (version2Service.useDeprecatedV1 == true) + { + logger.info("testMigrateOneVersion: skip"); + return; + } + + NodeRef versionableNode = createNewVersionableNode(); + + logger.info("testMigrateOneVersion: versionedNodeRef = " + versionableNode); + + // Get the next version number + int nextVersion = peekNextVersionNumber(); + String nextVersionLabel = peekNextVersionLabel(versionableNode, nextVersion, versionProperties); + + // Snap-shot the date-time + Date beforeVersionDate = new Date(); + long beforeVersionTime = beforeVersionDate.getTime(); + logger.info("beforeVersion Date/Time: " + beforeVersionDate + " [" + beforeVersionTime + "]"); + + Version oldVersion = createVersion(versionableNode); + + // get and store old version details for later comparison - versionNodeService will retrieve these from the old version store + + QName oldVersionType = versionNodeService.getType(oldVersion.getFrozenStateNodeRef()); + Set oldVersionAspects = versionNodeService.getAspects(oldVersion.getFrozenStateNodeRef()); + Map oldVersionProps = versionNodeService.getProperties(oldVersion.getFrozenStateNodeRef()); + + logger.info("oldVersion props: " + oldVersion); + logger.info("oldVersion created: " + oldVersion.getCreatedDate() + " [" + oldVersion.getCreatedDate().getTime()+"]"); + + logger.info("oldVersion props via versionNodeService: " + oldVersionProps); + + VersionHistory vh = version1Service.getVersionHistory(versionableNode); + assertEquals(1, vh.getAllVersions().size()); + + NodeRef oldVHNodeRef = version1Service.getVersionHistoryNodeRef(versionableNode); + + // Migrate and delete old version history ! + NodeRef versionedNodeRef = versionMigrator.v1GetVersionedNodeRef(oldVHNodeRef); + NodeRef newVHNodeRef = versionMigrator.migrateVersionHistory(oldVHNodeRef, versionedNodeRef); + versionMigrator.v1DeleteVersionHistory(oldVHNodeRef); + + VersionHistory vh2 = version2Service.getVersionHistory(versionableNode); + assertEquals(1, vh2.getAllVersions().size()); + + Version newVersion = vh2.getRootVersion(); + + logger.info("newVersion props: " + newVersion); + logger.info("newVersion created: " + newVersion.getCreatedDate() + " [" + newVersion.getCreatedDate().getTime()+"]"); + + // check new version - switch to new version service to do the check + super.setVersionService(version2Service); + checkNewVersion(beforeVersionTime, nextVersion, nextVersionLabel, newVersion, versionableNode); + + // get and compare new version details - - versionNodeService will retrieve these from the new version store + + QName newVersionType = versionNodeService.getType(newVersion.getFrozenStateNodeRef()); + Set newVersionAspects = versionNodeService.getAspects(newVersion.getFrozenStateNodeRef()); + Map newVersionProps = versionNodeService.getProperties(newVersion.getFrozenStateNodeRef()); + + logger.info("newVersion props via versionNodeService: " + newVersionProps); + + assertEquals(oldVersionType, newVersionType); + + assertEquals(oldVersionAspects.size(), newVersionAspects.size()); + for (QName key : oldVersionAspects) + { + assertTrue(""+key, newVersionAspects.contains(key)); + } + + assertEquals(oldVersionProps.size(), newVersionProps.size()); + for (QName key : oldVersionProps.keySet()) + { + assertEquals(""+key, oldVersionProps.get(key), newVersionProps.get(key)); + } + + logger.info("testMigrateOneVersion: Migrated from oldVHNodeRef = " + oldVHNodeRef + " to newVHNodeRef = " + newVHNodeRef); + } + + /** + * Test migration of a multiple versioned nodes + */ + public void testMigrateMultipleVersions() throws Exception + { + if (version2Service.useDeprecatedV1 == true) + { + logger.info("testMigrateOneVersion: skip"); + return; + } + + NodeRef versionableNode = createNewVersionableNode(); + + // Get the next version number, next version label and snapshot the date-time + int nextVersion1 = peekNextVersionNumber(); + String nextVersionLabel1 = peekNextVersionLabel(versionableNode, nextVersion1, versionProperties); + long beforeVersionTime1 = System.currentTimeMillis(); + + Version version1 = createVersion(versionableNode); + logger.info(version1); + + // Get the next version number, next version label and snapshot the date-time + int nextVersion2 = peekNextVersionNumber(); + String nextVersionLabel2 = peekNextVersionLabel(versionableNode, nextVersion2, versionProperties); + long beforeVersionTime2 = System.currentTimeMillis(); + + Version version2 = createVersion(versionableNode); + logger.info(version2); + + // Get the next version number, next version label and snapshot the date-time + int nextVersion3 = peekNextVersionNumber(); + String nextVersionLabel3 = peekNextVersionLabel(versionableNode, nextVersion3, versionProperties); + long beforeVersionTime3 = System.currentTimeMillis(); + + Version version3 = createVersion(versionableNode); + logger.info(version3); + + VersionHistory vh1 = version1Service.getVersionHistory(versionableNode); + assertEquals(3, vh1.getAllVersions().size()); + + logger.info("testMigrateMultipleVersions: versionedNodeRef = " + versionableNode); + + NodeRef oldVHNodeRef = version1Service.getVersionHistoryNodeRef(versionableNode); + + // Migrate and delete old version history ! + NodeRef versionedNodeRef = versionMigrator.v1GetVersionedNodeRef(oldVHNodeRef); + NodeRef newVHNodeRef = versionMigrator.migrateVersionHistory(oldVHNodeRef, versionedNodeRef); + versionMigrator.v1DeleteVersionHistory(oldVHNodeRef); + + VersionHistory vh2 = version2Service.getVersionHistory(versionableNode); + assertEquals(3, vh2.getAllVersions().size()); + + // TODO move check version history into BaseVersionStoreTest + // check new versions - switch to new version service to do the check + super.setVersionService(version2Service); + + Version[] newVersions = vh2.getAllVersions().toArray(new Version[]{}); + + checkVersion(beforeVersionTime1, nextVersion1, nextVersionLabel1, newVersions[2], versionableNode); + checkVersion(beforeVersionTime2, nextVersion2, nextVersionLabel2, newVersions[1], versionableNode); + checkNewVersion(beforeVersionTime3, nextVersion3, nextVersionLabel3, newVersions[0], versionableNode); + + logger.info("testMigrateMultipleVersions: Migrated from oldVHNodeRef = " + oldVHNodeRef + " to newVHNodeRef = " + newVHNodeRef); + } +} diff --git a/source/java/org/alfresco/repo/version/VersionModel.java b/source/java/org/alfresco/repo/version/VersionModel.java index b28b74a38b..6c810fc730 100644 --- a/source/java/org/alfresco/repo/version/VersionModel.java +++ b/source/java/org/alfresco/repo/version/VersionModel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,60 +24,38 @@ */ package org.alfresco.repo.version; -import org.alfresco.model.ContentModel; -import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.QName; /** - * interface conating the constants used by the light weight - * version store implementation + * Version1 Model Constants used by lightWeightVersionStore implementation * * @author Roy Wetherall */ -public interface VersionModel +public interface VersionModel extends VersionBaseModel { /** * Namespace */ public static final String NAMESPACE_URI = "http://www.alfresco.org/model/versionstore/1.0"; - /** - * The store protocol - */ - public static final String STORE_PROTOCOL = VersionService.VERSION_STORE_PROTOCOL; - /** * The store id */ public static final String STORE_ID = "lightWeightVersionStore"; - - public static final String PROP_VERSION_LABEL = "versionLabel"; - public static final String PROP_CREATED_DATE = ContentModel.PROP_CREATED.getLocalName(); - public static final String PROP_CREATOR = ContentModel.PROP_CREATOR.getLocalName(); - public static final String PROP_VERSION_TYPE = "versionType"; - public static final String PROP_VERSION_NUMBER = "versionNumber"; - public static final String PROP_FROZEN_NODE_ID = "frozenNodeId"; - public static final String PROP_FROZEN_NODE_TYPE = "frozenNodeType"; - public static final String PROP_FROZEN_NODE_STORE_PROTOCOL = "frozenNodeStoreProtocol"; - public static final String PROP_FROZEN_NODE_STORE_ID = "frozenNodeStoreId"; - public static final String PROP_FROZEN_ASPECTS = "frozenAspects"; - /** The version store root aspect */ - public static final QName ASPECT_VERSION_STORE_ROOT = QName.createQName(NAMESPACE_URI, "versionStoreRoot"); + public static final QName ASPECT_VERSION_STORE_ROOT = QName.createQName(NAMESPACE_URI, ASPECT_LOCALNAME_VERSION_STORE_ROOT); /** * Version history type */ - public static final String TYPE_VERSION_HISTORY = "versionHistory"; public static final QName TYPE_QNAME_VERSION_HISTORY = QName.createQName(NAMESPACE_URI, TYPE_VERSION_HISTORY); /** * Version history properties and associations */ - public static final String PROP_VERSIONED_NODE_ID = "versionedNodeId"; public static final QName PROP_QNAME_VERSIONED_NODE_ID = QName.createQName(NAMESPACE_URI, PROP_VERSIONED_NODE_ID); - public static final QName ASSOC_ROOT_VERSION = QName.createQName(NAMESPACE_URI, "rootVersion"); + public static final QName ASSOC_ROOT_VERSION = QName.createQName(NAMESPACE_URI, ASSOC_LOCALNAME_ROOT_VERSION); /** * Verison type @@ -88,6 +66,12 @@ public interface VersionModel /** * Version type properties and associations */ + public static final String PROP_FROZEN_NODE_ID = "frozenNodeId"; + public static final String PROP_FROZEN_NODE_STORE_PROTOCOL = "frozenNodeStoreProtocol"; + public static final String PROP_FROZEN_NODE_STORE_ID = "frozenNodeStoreId"; + public static final String PROP_FROZEN_NODE_TYPE = "frozenNodeType"; + public static final String PROP_FROZEN_ASPECTS = "frozenAspects"; + public static final QName PROP_QNAME_VERSION_LABEL = QName.createQName(NAMESPACE_URI, PROP_VERSION_LABEL); public static final QName PROP_QNAME_VERSION_NUMBER = QName.createQName(NAMESPACE_URI, PROP_VERSION_NUMBER); public static final QName PROP_QNAME_FROZEN_NODE_ID = QName.createQName(NAMESPACE_URI, PROP_FROZEN_NODE_ID); @@ -156,8 +140,6 @@ public interface VersionModel /** * Child relationship names */ - public static final String CHILD_VERSION_HISTORIES = "versionHistory"; - public static final String CHILD_VERSIONS = "version"; public static final String CHILD_VERSIONED_ATTRIBUTES = "versionedAttributes"; public static final String CHILD_VERSIONED_CHILD_ASSOCS = "versionedChildAssocs"; public static final String CHILD_VERSIONED_ASSOCS = "versionedAssocs"; @@ -173,7 +155,5 @@ public interface VersionModel /** * Created version associated to the deleted translations of an mlContainer */ - public static final String PROP_TRANSLATION_VERIONS = "translationVersions"; - public static final QName PROP_QNAME_TRANSLATION_VERIONS = QName.createQName(VersionModel.NAMESPACE_URI, PROP_TRANSLATION_VERIONS); - + public static final QName PROP_QNAME_TRANSLATION_VERSIONS = QName.createQName(VersionModel.NAMESPACE_URI, PROP_TRANSLATION_VERSIONS); } diff --git a/source/java/org/alfresco/repo/version/VersionServiceImpl.java b/source/java/org/alfresco/repo/version/VersionServiceImpl.java index b18a24e901..97b6ecb3e4 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -34,6 +34,7 @@ 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; @@ -43,6 +44,9 @@ import org.alfresco.repo.version.common.VersionImpl; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.repo.version.common.counter.VersionCounterService; import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.AspectMissingException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -59,29 +63,32 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** - * The version service implementation. + * Version1 Service - implements lightWeightVersionStore * * @author Roy Wetheral */ -public class VersionServiceImpl extends AbstractVersionServiceImpl - implements VersionService, VersionModel +public class VersionServiceImpl extends AbstractVersionServiceImpl implements VersionService, VersionModel { + private static Log logger = LogFactory.getLog(VersionServiceImpl.class); + /** * 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"; + protected static final String MSGID_ERR_NOT_FOUND = "version_service.err_not_found"; + protected static final String MSGID_ERR_NO_BRANCHES = "version_service.err_unsupported"; + protected static final String MSGID_ERR_RESTORE_EXISTS = "version_service.err_restore_exists"; + protected static final String MSGID_ERR_ONE_PRECEEDING = "version_service.err_one_preceeding"; + protected static final String MSGID_ERR_RESTORE_NO_VERSION = "version_service.err_restore_no_version"; + protected static final String MSGID_ERR_REVERT_MISMATCH = "version_service.err_revert_mismatch"; /** * The version counter service */ - private VersionCounterService versionCounterService; + protected VersionCounterService versionCounterService; /** * The db node service, used as the version store implementation @@ -91,13 +98,12 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl /** * Policy behaviour filter */ - private BehaviourFilter policyBehaviourFilter; + protected BehaviourFilter policyBehaviourFilter; /** * The repository searcher */ - @SuppressWarnings("unused") - private SearchService searcher; + protected SearchService searcher; // unused /** * Sets the db node service, used as the version store implementation @@ -155,12 +161,19 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl ContentModel.TYPE_MULTILINGUAL_CONTAINER, new JavaBehaviour(new SerialVersionLabelPolicy(), "calculateVersionLabel")); } + + // TODO - temp + protected void initialiseWithoutBind() + { + super.initialise(); + } /** * Gets the reference to the version store * * @return reference to the version store */ + @Override public StoreRef getVersionStoreReference() { return new StoreRef( @@ -176,11 +189,20 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl Map versionProperties) throws ReservedVersionNameException, AspectMissingException { + long startTime = System.currentTimeMillis(); + // Get the next version number int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); // Create the version - return createVersion(nodeRef, versionProperties, versionNumber); + Version version = createVersion(nodeRef, versionProperties, versionNumber); + + if (logger.isDebugEnabled()) + { + logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in " + (System.currentTimeMillis()-startTime) + " ms"); + } + + return version; } /** @@ -194,11 +216,22 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl boolean versionChildren) throws ReservedVersionNameException, AspectMissingException { + long startTime = System.currentTimeMillis(); + // Get the next version number int versionNumber = this.versionCounterService.nextVersionNumber(getVersionStoreReference()); // Create the versions - return createVersion(nodeRef, versionProperties, versionChildren, versionNumber); + Collection versions = createVersion(nodeRef, versionProperties, versionChildren, versionNumber); + + if (logger.isDebugEnabled()) + { + Version[] versionsArray = versions.toArray(new Version[0]); + Version version = versionsArray[versionsArray.length -1]; // last item is the new parent version + logger.debug("created version (" + versionNumber + " - " + VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()) + ") in "+ (System.currentTimeMillis()-startTime) +" ms "+(versionChildren ? "(with " + (versions.size() - 1) + " children)" : "")); + } + + return versions; } /** @@ -257,6 +290,8 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl Map versionProperties) throws ReservedVersionNameException, AspectMissingException { + long startTime = System.currentTimeMillis(); + Collection result = new ArrayList(nodeRefs.size()); // Get the next version number @@ -268,6 +303,11 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl result.add(createVersion(nodeRef, versionProperties, versionNumber)); } + if (logger.isDebugEnabled()) + { + logger.debug("created version list (" + versionNumber + " - " + getVersionStoreReference() + ") in "+ (System.currentTimeMillis()-startTime) +" ms (with " + nodeRefs.size() + " nodes)"); + } + return result; } @@ -282,12 +322,13 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl * @throws ReservedVersionNameException * thrown if there is a name clash in the version properties */ - private Version createVersion( + protected Version createVersion( NodeRef nodeRef, Map origVersionProperties, int versionNumber) throws ReservedVersionNameException { + long startTime = System.currentTimeMillis(); // Copy the version properties (to prevent unexpected side effects to the caller) Map versionProperties = new HashMap(); @@ -370,7 +411,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl } // Create the version data object - Version version = getVersion(newVersionRef); + Version version = this.getVersion(newVersionRef); // Set the new version label on the versioned node this.nodeService.setProperty( @@ -386,6 +427,11 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl // Invoke the policy behaviour invokeAfterCreateVersion(nodeRef, version); + if (logger.isTraceEnabled()) + { + logger.trace("created Version (" + versionNumber + " - " + getVersionStoreReference() + ") " + nodeRef + " in " + (System.currentTimeMillis()-startTime) +" ms"); + } + // Return the data object representing the newly created version return version; } @@ -465,30 +511,30 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl Map result = new HashMap(10); // Set the version number for the new version - result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_VERSION_NUMBER), Integer.toString(versionNumber)); + result.put(VersionModel.PROP_QNAME_VERSION_NUMBER, Integer.toString(versionNumber)); // Set the versionable node id - result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_ID), nodeRef.getId()); + result.put(VersionModel.PROP_QNAME_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()); + result.put(VersionModel.PROP_QNAME_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()); + 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(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_TYPE), nodeType); + result.put(VersionModel.PROP_QNAME_FROZEN_NODE_TYPE, nodeType); // Store the current aspects Set aspects = this.nodeService.getAspects(nodeRef); - result.put(QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_ASPECTS), (Serializable)aspects); + 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(QName.createQName(NAMESPACE_URI, VersionModel.PROP_VERSION_LABEL), versionLabel); + result.put(VersionModel.PROP_QNAME_VERSION_LABEL, versionLabel); return result; } @@ -556,6 +602,27 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl properties); } } + + protected Map getVersionMetaData(NodeRef versionNodeRef) + { + // Get the meta data + List metaData = this.dbNodeService.getChildAssocs( + versionNodeRef, + RegexQNamePattern.MATCH_ALL, + CHILD_QNAME_VERSION_META_DATA); + + Map versionProperties = new HashMap(metaData.size()); + + for (ChildAssociationRef ref : metaData) + { + NodeRef metaDataValue = (NodeRef)ref.getChildRef(); + String name = (String)this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_NAME); + Serializable value = this.dbNodeService.getProperty(metaDataValue, PROP_QNAME_META_DATA_VALUE); + versionProperties.put(name, value); + } + + return versionProperties; + } /** * Freeze the aspects @@ -675,7 +742,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl * * @return the node ref to the root node of the version store */ - private NodeRef getRootNode() + protected NodeRef getRootNode() { // Get the version store root node reference return this.dbNodeService.getRootNode(getVersionStoreReference()); @@ -691,12 +758,21 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl * @param nodeRef the node reference * @return a constructed version history object */ - private VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef) + protected VersionHistory buildVersionHistory(NodeRef versionHistoryRef, NodeRef nodeRef) { VersionHistory versionHistory = null; ArrayList versionHistoryNodeRefs = new ArrayList(); - NodeRef currentVersion = getCurrentVersionNodeRef(versionHistoryRef, nodeRef); + + NodeRef currentVersion; + if (this.nodeService.exists(nodeRef)) + { + currentVersion = getCurrentVersionNodeRef(versionHistoryRef, nodeRef); + } + else + { + currentVersion = VersionUtil.convertNodeRef(getLatestVersion(nodeRef).getFrozenStateNodeRef()); + } while (currentVersion != null) { @@ -722,7 +798,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl currentVersion = null; } } - + // Build the version history object boolean isRoot = true; Version preceeding = null; @@ -751,7 +827,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl * @param versionRef the version reference * @return object containing verison data */ - private Version getVersion(NodeRef versionRef) + protected Version getVersion(NodeRef versionRef) { if (versionRef == null) { @@ -768,18 +844,9 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl } // Get the meta data - List 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); - } - + Map versionMetaDataProperties = getVersionMetaData(versionRef); + versionProperties.putAll(versionMetaDataProperties); + // Create and return the version object NodeRef newNodeRef = new NodeRef(new StoreRef(STORE_PROTOCOL, STORE_ID), versionRef.getId()); Version result = new VersionImpl(versionProperties, newNodeRef); @@ -793,7 +860,7 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl * @param nodeRef a node reference * @return a reference to the version history node, null of none */ - private NodeRef getVersionHistoryNodeRef(NodeRef nodeRef) + protected NodeRef getVersionHistoryNodeRef(NodeRef nodeRef) { return this.dbNodeService.getChildByName(getRootNode(), CHILD_QNAME_VERSION_HISTORIES, nodeRef.getId()); } @@ -1085,6 +1152,39 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl return version; } + + private Version getLatestVersion(NodeRef nodeRef) + { + Version version = null; + StoreRef storeRef = nodeRef.getStoreRef(); + + NodeRef versionHistoryNodeRef = getVersionHistoryNodeRef(nodeRef); + if (versionHistoryNodeRef != null) + { + List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryNodeRef, RegexQNamePattern.MATCH_ALL, VersionModel.CHILD_QNAME_VERSIONS); + for (ChildAssociationRef versionAssoc : versionsAssoc) + { + NodeRef versionNodeRef = versionAssoc.getChildRef(); + List predecessors = this.dbNodeService.getSourceAssocs(versionNodeRef, VersionModel.ASSOC_SUCCESSOR); + if (predecessors.size() == 0) + { + String storeProtocol = (String)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL)); + String storeId = (String)this.dbNodeService.getProperty( + versionNodeRef, + QName.createQName(NAMESPACE_URI, VersionModel.PROP_FROZEN_NODE_STORE_ID)); + StoreRef versionStoreRef = new StoreRef(storeProtocol, storeId); + if (storeRef.equals(versionStoreRef) == true) + { + version = getVersion(versionNodeRef); + } + } + } + } + + return version; + } /** * @see org.alfresco.cms.version.VersionService#deleteVersionHistory(NodeRef) @@ -1107,4 +1207,58 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl } } } + + @Override + protected void defaultOnCreateVersion( + QName classRef, + NodeRef nodeRef, + Map versionProperties, + PolicyScope nodeDetails) + { + ClassDefinition classDefinition = this.dictionaryService.getClass(classRef); + if (classDefinition != null) + { + boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); + try + { + // Copy the properties + Map propertyDefinitions = classDefinition.getProperties(); + for (QName propertyName : propertyDefinitions.keySet()) + { + Serializable propValue = this.nodeService.getProperty(nodeRef, propertyName); + nodeDetails.addProperty(classRef, propertyName, propValue); + } + } + finally + { + MLPropertyInterceptor.setMLAware(wasMLAware); + } + + // Version the associations (child and target) + Map assocDefs = classDefinition.getAssociations(); + + // TODO: Need way of getting child assocs of a given type + if (classDefinition.isContainer()) + { + List childAssocRefs = this.nodeService.getChildAssocs(nodeRef); + for (ChildAssociationRef childAssocRef : childAssocRefs) + { + if (assocDefs.containsKey(childAssocRef.getTypeQName())) + { + nodeDetails.addChildAssociation(classDefinition.getName(), childAssocRef); + } + } + } + + // TODO: Need way of getting assocs of a given type + List nodeAssocRefs = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); + for (AssociationRef nodeAssocRef : nodeAssocRefs) + { + if (assocDefs.containsKey(nodeAssocRef.getTypeQName())) + { + nodeDetails.addAssociation(classDefinition.getName(), nodeAssocRef); + } + } + } + } } diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 8073813c43..76aaa0c51c 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -70,14 +70,19 @@ public class VersionServiceImplTest extends BaseVersionStoreTest private static final String UPDATED_CONTENT_1 = "updatedContent1"; private static final String UPDATED_CONTENT_2 = "updatedContent2"; + public void testSetup() + { + // NOOP + } + /** * Tests the creation of the initial version of a versionable node */ public void testCreateIntialVersion() { NodeRef versionableNode = createNewVersionableNode(); - createVersion(versionableNode); - } + createVersion(versionableNode); + } /** * Test creating a version history with many versions from the same workspace @@ -90,9 +95,39 @@ public class VersionServiceImplTest extends BaseVersionStoreTest createVersion(versionableNode); // TODO mess with some of the properties and stuff as you version createVersion(versionableNode); + + VersionHistory vh = this.versionService.getVersionHistory(versionableNode); + assertNotNull(vh); + assertEquals(3, vh.getAllVersions().size()); + + // TODO check list of versions ... ! } - // TODO test versioning a non versionable node ie: no version apsect + /** + * Tests the creation of multiple versions of a versionable node with null version properties + */ + public void testCreateManyVersionsWithNullVersionProperties() + { + this.versionProperties = null; + + NodeRef versionableNode = createNewVersionableNode(); + createVersion(versionableNode); + createVersion(versionableNode); + createVersion(versionableNode); + + VersionHistory vh = this.versionService.getVersionHistory(versionableNode); + assertNotNull(vh); + assertEquals(3, vh.getAllVersions().size()); + } + + /** + * Test versioning a non versionable node ie: no version apsect + */ + public void testCreateInitialVersionWhenNotVersionable() + { + NodeRef node = createNewNode(); // not marked as versionable + createVersion(node); + } // TODO test versioning numberious times with branchs implies by different workspaces @@ -155,15 +190,28 @@ public class VersionServiceImplTest extends BaseVersionStoreTest for (Version version : versions) { // Get the frozen id from the version - String frozenNodeId = (String)version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID); + String frozenNodeId = null; + + // Switch VersionStore depending on configured impl + if (versionService.getVersionStoreReference().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + frozenNodeId = ((NodeRef)version.getVersionProperty(Version2Model.PROP_FROZEN_NODE_REF)).getId(); + } + else if (versionService.getVersionStoreReference().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + frozenNodeId = (String)version.getVersionProperty(VersionModel.PROP_FROZEN_NODE_ID); + } + assertNotNull("Unable to retrieve the frozen node id from the created version.", frozenNodeId); // Get the origional node ref (based on the forzen node) - NodeRef origionaNodeRef = this.versionableNodes.get(frozenNodeId); - assertNotNull("The versionable node ref that relates to the frozen node id can not be found.", origionaNodeRef); + NodeRef originalNodeRef = this.versionableNodes.get(frozenNodeId); + assertNotNull("The versionable node ref that relates to the frozen node id can not be found.", originalNodeRef); // Check the new version - checkNewVersion(beforeVersionTime, expectedVersionNumber, expectedVersionLabel, version, origionaNodeRef); + checkNewVersion(beforeVersionTime, expectedVersionNumber, expectedVersionLabel, version, originalNodeRef); } } @@ -476,12 +524,12 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Check that the initial version has not been created - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(versionableNode); + VersionHistory versionHistory = versionService.getVersionHistory(versionableNode); assertNotNull(versionHistory); assertEquals(1, versionHistory.getAllVersions().size()); // Add some content - ContentWriter contentWriter = VersionServiceImplTest.this.contentService.getWriter(versionableNode, ContentModel.PROP_CONTENT, true); + ContentWriter contentWriter = contentService.getWriter(versionableNode, ContentModel.PROP_CONTENT, true); assertNotNull(contentWriter); contentWriter.putContent(UPDATED_CONTENT_1); @@ -494,7 +542,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest { public Object execute() throws Exception { - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(versionableNode); + VersionHistory versionHistory = versionService.getVersionHistory(versionableNode); assertNotNull(versionHistory); assertEquals(2, versionHistory.getAllVersions().size()); @@ -520,7 +568,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Add some content - ContentWriter contentWriter = VersionServiceImplTest.this.contentService.getWriter(versionableNode, ContentModel.PROP_CONTENT, true); + ContentWriter contentWriter = contentService.getWriter(versionableNode, ContentModel.PROP_CONTENT, true); assertNotNull(contentWriter); contentWriter.putContent(UPDATED_CONTENT_1); @@ -533,7 +581,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest { public Object execute() throws Exception { - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(versionableNode); + VersionHistory versionHistory = versionService.getVersionHistory(versionableNode); assertNotNull(versionHistory); assertEquals(1, versionHistory.getAllVersions().size()); @@ -567,7 +615,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest { public Object execute() throws Exception { - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); assertNull(versionHistory); return null; @@ -596,12 +644,12 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Check that the version history has been created - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); assertNotNull(versionHistory); assertEquals(1, versionHistory.getAllVersions().size()); // Remove the versionable aspect - VersionServiceImplTest.this.dbNodeService.removeAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE); + dbNodeService.removeAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE); return null; } @@ -612,11 +660,11 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Check that the version history has been removed - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); assertNull(versionHistory); // Re-add the versionable aspect - VersionServiceImplTest.this.dbNodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + dbNodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); return null; } @@ -627,7 +675,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Check that the version history has been created - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); assertNotNull(versionHistory); assertEquals(1, versionHistory.getAllVersions().size()); @@ -658,12 +706,12 @@ public class VersionServiceImplTest extends BaseVersionStoreTest { public Object execute() throws Exception { - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); assertNotNull(versionHistory); assertEquals(1, versionHistory.getAllVersions().size()); // Delete the node - VersionServiceImplTest.this.dbNodeService.deleteNode(nodeRef); + dbNodeService.deleteNode(nodeRef); return null; } @@ -674,15 +722,15 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Get the archived noderef - NodeRef archivedNodeRef = VersionServiceImplTest.this.nodeArchiveService.getArchivedNode(nodeRef); + NodeRef archivedNodeRef = nodeArchiveService.getArchivedNode(nodeRef); // The archived noderef should still have a link to the version history - VersionHistory versionHistory = VersionServiceImplTest.this.versionService.getVersionHistory(archivedNodeRef); + VersionHistory versionHistory = versionService.getVersionHistory(archivedNodeRef); assertNotNull(versionHistory); assertEquals(1, versionHistory.getAllVersions().size()); // Delete the node for good - VersionServiceImplTest.this.dbNodeService.deleteNode(archivedNodeRef); + dbNodeService.deleteNode(archivedNodeRef); return null; } @@ -693,12 +741,12 @@ public class VersionServiceImplTest extends BaseVersionStoreTest public Object execute() throws Exception { // Get the archived noderef - NodeRef archivedNodeRef = VersionServiceImplTest.this.nodeArchiveService.getArchivedNode(nodeRef); + NodeRef archivedNodeRef = nodeArchiveService.getArchivedNode(nodeRef); // Check that the version histories have been deleted - VersionHistory versionHistory12 = VersionServiceImplTest.this.versionService.getVersionHistory(nodeRef); + VersionHistory versionHistory12 = versionService.getVersionHistory(nodeRef); assertNull(versionHistory12); - VersionHistory versionHistory23 = VersionServiceImplTest.this.versionService.getVersionHistory(archivedNodeRef); + VersionHistory versionHistory23 = versionService.getVersionHistory(archivedNodeRef); assertNull(versionHistory23); return null; @@ -761,8 +809,15 @@ public class VersionServiceImplTest extends BaseVersionStoreTest final AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); authenticationComponent.setSystemUserAsCurrentUser(); + + // TEMP - for migration testing - force V1 store (override repository property) + final Version2ServiceImpl version2ServiceImpl = (Version2ServiceImpl)ctx.getBean("versionService"); + version2ServiceImpl.setOnlyUseDeprecatedV1(true); + + System.out.println("Using: " + versionService.getVersionStoreReference()); + // Create a new store - StoreRef storeRef = new StoreRef("test", "VersionServiceImplTest.main"); + StoreRef storeRef = new StoreRef("test", "VersionServiceImplTest-main-"+System.currentTimeMillis()); if (!nodeService.exists(storeRef)) { nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); @@ -819,5 +874,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest lastReport = now; } } + + System.out.println("Finished: " + fileCount); } } diff --git a/source/java/org/alfresco/repo/version/VersionTestSuite.java b/source/java/org/alfresco/repo/version/VersionTestSuite.java index 2fa2c0c7f0..c75087f948 100644 --- a/source/java/org/alfresco/repo/version/VersionTestSuite.java +++ b/source/java/org/alfresco/repo/version/VersionTestSuite.java @@ -54,6 +54,7 @@ public class VersionTestSuite extends TestSuite suite.addTestSuite(VersionServiceImplTest.class); suite.addTestSuite(NodeServiceImplTest.class); suite.addTestSuite(ContentServiceImplTest.class); + suite.addTestSuite(VersionMigratorTest.class); return suite; } } diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index 2757f12d3b..8a68bf8d7b 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -205,6 +205,7 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate // Queue create version action Map versionDetails = new HashMap(1); versionDetails.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_INITIAL_VERSION)); + this.versionService.createVersion(nodeRef, versionDetails); } } diff --git a/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java b/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java index 55e7a7776a..1322030528 100644 --- a/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,11 +26,9 @@ package org.alfresco.repo.version.common; import java.io.Serializable; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Set; -import org.alfresco.repo.node.MLPropertyInterceptor; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; @@ -39,18 +37,13 @@ import org.alfresco.repo.version.VersionServicePolicies.AfterCreateVersionPolicy import org.alfresco.repo.version.VersionServicePolicies.BeforeCreateVersionPolicy; import org.alfresco.repo.version.VersionServicePolicies.CalculateVersionLabelPolicy; import org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -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.version.Version; import org.alfresco.service.cmr.version.VersionServiceException; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; /** * Abstract version service implementation. @@ -223,58 +216,11 @@ public abstract class AbstractVersionServiceImpl * @param versionProperties * @param nodeDetails */ - protected void defaultOnCreateVersion( + abstract protected void defaultOnCreateVersion( QName classRef, NodeRef nodeRef, Map versionProperties, - PolicyScope nodeDetails) - { - ClassDefinition classDefinition = this.dictionaryService.getClass(classRef); - if (classDefinition != null) - { - boolean wasMLAware = MLPropertyInterceptor.setMLAware(true); - try - { - // Copy the properties - Map propertyDefinitions = classDefinition.getProperties(); - for (QName propertyName : propertyDefinitions.keySet()) - { - Serializable propValue = this.nodeService.getProperty(nodeRef, propertyName); - nodeDetails.addProperty(classRef, propertyName, propValue); - } - } - finally - { - MLPropertyInterceptor.setMLAware(wasMLAware); - } - - // Version the associations (child and target) - Map assocDefs = classDefinition.getAssociations(); - - // TODO: Need way of getting child assocs of a given type - if (classDefinition.isContainer()) - { - List childAssocRefs = this.nodeService.getChildAssocs(nodeRef); - for (ChildAssociationRef childAssocRef : childAssocRefs) - { - if (assocDefs.containsKey(childAssocRef.getTypeQName())) - { - nodeDetails.addChildAssociation(classDefinition.getName(), childAssocRef); - } - } - } - - // TODO: Need way of getting assocs of a given type - List nodeAssocRefs = this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); - for (AssociationRef nodeAssocRef : nodeAssocRefs) - { - if (assocDefs.containsKey(nodeAssocRef.getTypeQName())) - { - nodeDetails.addAssociation(classDefinition.getName(), nodeAssocRef); - } - } - } - } + PolicyScope nodeDetails); /** * Invoke the calculate version label policy behaviour @@ -313,5 +259,6 @@ public abstract class AbstractVersionServiceImpl return versionLabel; } - + + abstract public StoreRef getVersionStoreReference(); } diff --git a/source/java/org/alfresco/repo/version/common/VersionHistoryImplTest.java b/source/java/org/alfresco/repo/version/common/VersionHistoryImplTest.java index ed943bd3dc..ba5941f294 100644 --- a/source/java/org/alfresco/repo/version/common/VersionHistoryImplTest.java +++ b/source/java/org/alfresco/repo/version/common/VersionHistoryImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -225,11 +225,11 @@ public class VersionHistoryImplTest extends TestCase } } - Collection versions2 = vh.getSuccessors(this.childVersion1); + Collection versions2 = vh.getSuccessors(this.childVersion1); assertNotNull(versions2); assertTrue(versions2.isEmpty()); - Collection versions3 = vh.getSuccessors(this.childVersion2); + Collection versions3 = vh.getSuccessors(this.childVersion2); assertNotNull(versions3); assertTrue(versions3.isEmpty()); } diff --git a/source/java/org/alfresco/repo/version/common/VersionImpl.java b/source/java/org/alfresco/repo/version/common/VersionImpl.java index 8e81ab92f0..f63fd8e59d 100644 --- a/source/java/org/alfresco/repo/version/common/VersionImpl.java +++ b/source/java/org/alfresco/repo/version/common/VersionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,6 +28,8 @@ import java.io.Serializable; import java.util.Date; import java.util.Map; +import org.alfresco.repo.version.Version2Model; +import org.alfresco.repo.version.VersionBaseModel; import org.alfresco.repo.version.VersionModel; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -45,6 +47,7 @@ import org.alfresco.service.cmr.version.VersionType; * * @author Roy Wetherall */ + public class VersionImpl implements Version { /** @@ -98,16 +101,21 @@ public class VersionImpl implements Version /** * Helper method to get the created date from the version property data. * - * @return the date the version was created + * @return the date the version was created (note: not the date of the original node) */ public Date getCreatedDate() { - return (Date)this.versionProperties.get(VersionModel.PROP_CREATED_DATE); + return (Date)this.versionProperties.get(VersionBaseModel.PROP_CREATED_DATE); } + /** + * Helper method to get the creator from the version property data. + * + * @return the creator of the version (note: not the creator of the original node) + */ public String getCreator() { - return (String)this.versionProperties.get(VersionModel.PROP_CREATOR); + return (String)this.versionProperties.get(VersionBaseModel.PROP_CREATOR); } /** @@ -117,7 +125,7 @@ public class VersionImpl implements Version */ public String getVersionLabel() { - return (String)this.versionProperties.get(VersionModel.PROP_VERSION_LABEL); + return (String)this.versionProperties.get(VersionBaseModel.PROP_VERSION_LABEL); } /** @@ -127,7 +135,7 @@ public class VersionImpl implements Version */ public VersionType getVersionType() { - return (VersionType)this.versionProperties.get(VersionModel.PROP_VERSION_TYPE); + return (VersionType)this.versionProperties.get(VersionBaseModel.PROP_VERSION_TYPE); } /** @@ -137,7 +145,7 @@ public class VersionImpl implements Version */ public String getDescription() { - return (String)this.versionProperties.get(PROP_DESCRIPTION); + return (String)this.versionProperties.get(Version.PROP_DESCRIPTION); } /** @@ -166,10 +174,24 @@ public class VersionImpl implements Version */ public NodeRef getVersionedNodeRef() { - String storeProtocol = (String)this.versionProperties.get(VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL); - String storeId = (String)this.versionProperties.get(VersionModel.PROP_FROZEN_NODE_STORE_ID); - String nodeId = (String)this.versionProperties.get(VersionModel.PROP_FROZEN_NODE_ID); - return new NodeRef(new StoreRef(storeProtocol, storeId), nodeId); + NodeRef versionedNodeRef = null; + + // Switch VersionStore depending on configured impl + if (nodeRef.getStoreRef().getIdentifier().equals(Version2Model.STORE_ID)) + { + // V2 version store (eg. workspace://version2Store) + versionedNodeRef = (NodeRef)this.versionProperties.get(Version2Model.PROP_FROZEN_NODE_REF); + } + else if (nodeRef.getStoreRef().getIdentifier().equals(VersionModel.STORE_ID)) + { + // Deprecated V1 version store (eg. workspace://lightWeightVersionStore) + String storeProtocol = (String)this.versionProperties.get(VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL); + String storeId = (String)this.versionProperties.get(VersionModel.PROP_FROZEN_NODE_STORE_ID); + String nodeId = (String)this.versionProperties.get(VersionModel.PROP_FROZEN_NODE_ID); + versionedNodeRef = new NodeRef(new StoreRef(storeProtocol, storeId), nodeId); + } + + return versionedNodeRef; } /** diff --git a/source/java/org/alfresco/repo/version/common/VersionUtil.java b/source/java/org/alfresco/repo/version/common/VersionUtil.java index 33a806f525..4b2a44061d 100644 --- a/source/java/org/alfresco/repo/version/common/VersionUtil.java +++ b/source/java/org/alfresco/repo/version/common/VersionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,12 +24,22 @@ */ package org.alfresco.repo.version.common; +import java.io.Serializable; import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.version.Version2Model; +import org.alfresco.repo.version.VersionBaseModel; import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; 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.namespace.QName; /** * Helper class containing helper methods for the versioning services. @@ -42,14 +52,21 @@ public class VersionUtil * Reserved property names */ public static final String[] RESERVED_PROPERTY_NAMES = new String[]{ - VersionModel.PROP_CREATED_DATE, - VersionModel.PROP_FROZEN_NODE_ID, - VersionModel.PROP_FROZEN_NODE_STORE_ID, - VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL, - VersionModel.PROP_FROZEN_NODE_TYPE, - VersionModel.PROP_FROZEN_ASPECTS, - VersionModel.PROP_VERSION_LABEL, - VersionModel.PROP_VERSION_NUMBER}; + VersionModel.PROP_FROZEN_NODE_ID, // deprecated + VersionModel.PROP_FROZEN_NODE_STORE_ID, // deprecated + VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL, // deprecated + VersionModel.PROP_FROZEN_NODE_TYPE, // deprecated + VersionModel.PROP_FROZEN_ASPECTS, // deprecated + VersionBaseModel.PROP_CREATED_DATE, + VersionBaseModel.PROP_VERSION_LABEL, + VersionBaseModel.PROP_VERSION_NUMBER, + VersionBaseModel.PROP_VERSION_DESCRIPTION, + Version2Model.PROP_FROZEN_NODE_DBID, + Version2Model.PROP_FROZEN_CREATED, + Version2Model.PROP_FROZEN_CREATOR, + Version2Model.PROP_FROZEN_MODIFIED, + Version2Model.PROP_FROZEN_MODIFIER, + Version2Model.PROP_FROZEN_ACCESSED}; /** * Checks that the names of the additional version properties are valid and that they do not clash @@ -93,4 +110,81 @@ public class VersionUtil { return new NodeRef(convertStoreRef(nodeRef.getStoreRef()), nodeRef.getId()); } + + + public static void convertFrozenToOriginalProps(Map props) throws InvalidNodeRefException + { + if (props != null) + { + props.remove(Version2Model.PROP_QNAME_VERSION_DESCRIPTION); + props.remove(Version2Model.PROP_QNAME_VERSION_NUMBER); + + Set keys = new HashSet(props.keySet()); + for (QName key : keys) + { + String keyName = key.getLocalName(); + int idx = keyName.indexOf(Version2Model.PROP_METADATA_PREFIX); + if (idx == 0) + { + props.remove(key); + } + } + + String versionLabel = (String)props.get(Version2Model.PROP_QNAME_VERSION_LABEL); + props.put(ContentModel.PROP_VERSION_LABEL, versionLabel); + props.remove(Version2Model.PROP_QNAME_VERSION_LABEL); + + // Convert frozen sys:referenceable properties + NodeRef nodeRef = (NodeRef)props.get(Version2Model.PROP_QNAME_FROZEN_NODE_REF); + if (nodeRef != null) + { + props.put(ContentModel.PROP_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol()); + props.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier()); + props.put(ContentModel.PROP_NODE_UUID, nodeRef.getId()); + } + props.remove(Version2Model.PROP_QNAME_FROZEN_NODE_REF); + + Long dbid = (Long)props.get(Version2Model.PROP_QNAME_FROZEN_NODE_DBID); + props.put(ContentModel.PROP_NODE_DBID, dbid); + props.remove(Version2Model.PROP_QNAME_FROZEN_NODE_DBID); + + // Convert frozen cm:auditable properties + + String creator = (String)props.get(Version2Model.PROP_QNAME_FROZEN_CREATOR); + if (creator != null) + { + props.put(ContentModel.PROP_CREATOR, creator); + } + props.remove(Version2Model.PROP_QNAME_FROZEN_CREATOR); + + Date created = (Date)props.get(Version2Model.PROP_QNAME_FROZEN_CREATED); + if (created != null) + { + props.put(ContentModel.PROP_CREATED, created); + } + props.remove(Version2Model.PROP_QNAME_FROZEN_CREATED); + + // TODO - check use-cases for get version, revert, restore .... + String modifier = (String)props.get(Version2Model.PROP_QNAME_FROZEN_MODIFIER); + if (modifier != null) + { + props.put(ContentModel.PROP_MODIFIER, modifier); + } + props.remove(Version2Model.PROP_QNAME_FROZEN_MODIFIER); + + Date modified = (Date)props.get(Version2Model.PROP_QNAME_FROZEN_MODIFIED); + if (modified != null) + { + props.put(ContentModel.PROP_MODIFIED, modified); + } + props.remove(Version2Model.PROP_QNAME_FROZEN_MODIFIED); + + Date accessed = (Date)props.get(Version2Model.PROP_QNAME_FROZEN_ACCESSED); + if (accessed != null) + { + props.put(ContentModel.PROP_ACCESSED, accessed); + } + props.remove(Version2Model.PROP_QNAME_FROZEN_ACCESSED); + } + } } diff --git a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java index be2a498f5f..8dfcb9d79a 100644 --- a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java +++ b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java @@ -62,7 +62,12 @@ public class SerialVersionLabelPolicy { serialVersionNumber = new SerialVersionLabel(preceedingVersion.getVersionLabel()); - VersionType versionType = (VersionType)versionProperties.get(VersionModel.PROP_VERSION_TYPE); + VersionType versionType = null; + if (versionProperties != null) + { + versionType = (VersionType)versionProperties.get(VersionModel.PROP_VERSION_TYPE); + } + if (VersionType.MAJOR.equals(versionType) == true) { serialVersionNumber.majorIncrement(); diff --git a/source/java/org/alfresco/repo/version/version2_model.xml b/source/java/org/alfresco/repo/version/version2_model.xml new file mode 100644 index 0000000000..e5136147f9 --- /dev/null +++ b/source/java/org/alfresco/repo/version/version2_model.xml @@ -0,0 +1,140 @@ + + + Alfresco Version2 Store Model + Alfresco + 2008-07-18 + 2.0 + + + + + + + + + + + + + + + + cm:cmobject + + + + d:text + + + + + + + sys:base + + + + + sys:base + + + + + + + + + + + Version Store Root + + + + false + true + + + ver2:versionHistory + false + true + + false + + + + + + Version + + + + d:int + + + d:text + + + d:text + + + d:qname + + + d:qname + true + + + + + + d:text + true + + + d:text + true + + + d:text + true + + + d:long + true + + + + + + Created + d:datetime + true + + + Creator + d:text + true + + + Modified + d:datetime + true + + + Modifier + d:text + true + + + Accessed + d:datetime + true + + + + + + + + + diff --git a/source/java/org/alfresco/repo/version/version_model.xml b/source/java/org/alfresco/repo/version/version_model.xml index de7143bac0..7c88143c7a 100644 --- a/source/java/org/alfresco/repo/version/version_model.xml +++ b/source/java/org/alfresco/repo/version/version_model.xml @@ -1,5 +1,7 @@ + + Alfresco Version Store Model Alfresco 2005-05-30 diff --git a/source/java/org/alfresco/service/cmr/version/Version.java b/source/java/org/alfresco/service/cmr/version/Version.java index 866b0772ec..2415ec4d72 100644 --- a/source/java/org/alfresco/service/cmr/version/Version.java +++ b/source/java/org/alfresco/service/cmr/version/Version.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.util.Date; import java.util.Map; +import org.alfresco.repo.version.VersionBaseModel; import org.alfresco.service.cmr.repository.NodeRef; @@ -44,7 +45,7 @@ public interface Version extends Serializable /** * Names of the system version properties */ - public static final String PROP_DESCRIPTION = "description"; + public static final String PROP_DESCRIPTION = VersionBaseModel.PROP_DESCRIPTION; /** * Helper method to get the created date from the version property data. @@ -103,7 +104,7 @@ public interface Version extends Serializable * Note that this reference will be to the current state of the versioned * node which may now correspond to a later version. * - * @return a node reference + * @return a node reference (to the versioned node in the 'live' store) */ public NodeRef getVersionedNodeRef(); @@ -111,7 +112,7 @@ public interface Version extends Serializable * Gets the reference to the node that contains the frozen state of the * version. * - * @return a node reference + * @return a node reference (to the version node in the 'version' store) */ public NodeRef getFrozenStateNodeRef(); }