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.xmlorg/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.xmlalfresco/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