diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties
index 40134471ca..ede402b625 100644
--- a/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties
@@ -71,3 +71,9 @@ rm.completerecord.mandatorypropertiescheck.enabled=true
# deprecated model properties in the rma namespace.
#
rm.patch.v22.convertToStandardFilePlan=false
+
+#
+# Extended auto-version behaviour. If true and other auto-version properties are satisified, then
+# a document will be auto-versioned when it's type is changed.
+#
+version.store.enableAutoVersionOnTypeChange=false
diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml
index c9921401f7..bffe192a53 100644
--- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml
@@ -44,6 +44,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/AlfrescoTransactionSupport.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/AlfrescoTransactionSupport.java
index d2f9796300..8ac49d5ded 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/AlfrescoTransactionSupport.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/AlfrescoTransactionSupport.java
@@ -18,6 +18,7 @@
*/
package org.alfresco.module.org_alfresco_module_rm.util;
+
/**
* Alfresco Transaction Support delegation bean.
*
@@ -42,4 +43,12 @@ public class AlfrescoTransactionSupport
{
org.alfresco.repo.transaction.AlfrescoTransactionSupport.unbindResource(key);
}
+
+ /**
+ * @see org.alfresco.repo.transaction.AlfrescoTransactionSupport#getResource(Object)
+ */
+ public R getResource(Object key)
+ {
+ return org.alfresco.repo.transaction.AlfrescoTransactionSupport.getResource(key);
+ }
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/ExtendedVersionableAspect.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/ExtendedVersionableAspect.java
new file mode 100644
index 0000000000..27c1257166
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/ExtendedVersionableAspect.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2005-2015 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.module.org_alfresco_module_rm.version;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.module.org_alfresco_module_rm.util.AlfrescoTransactionSupport;
+import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil;
+import org.alfresco.repo.lock.LockUtils;
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
+import org.alfresco.repo.policy.annotation.Behaviour;
+import org.alfresco.repo.policy.annotation.BehaviourBean;
+import org.alfresco.repo.policy.annotation.BehaviourKind;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.version.Version;
+import org.alfresco.service.cmr.version.VersionService;
+import org.alfresco.service.namespace.QName;
+import org.springframework.extensions.surf.util.I18NUtil;
+
+/**
+ * Extend versionable aspect auto-version behaviour to allow versions to be
+ * created when the content type is changed.
+ *
+ * Note: this behaviour should be merged into core asap
+ *
+ * @author Roy Wetherall
+ * @since 2.3.1
+ */
+@BehaviourBean
+public class ExtendedVersionableAspect implements NodeServicePolicies.OnSetNodeTypePolicy
+{
+ /** The i18n'ized messages */
+ private static final String MSG_AUTO_VERSION = "create_version.auto_version";
+
+ /** Transaction resource key */
+ private static final String KEY_VERSIONED_NODEREFS = "versioned_noderefs";
+
+ /** node service */
+ private NodeService nodeService;
+
+ /** version service */
+ private VersionService versionService;
+
+ /** lock service */
+ private LockService lockService;
+
+ /** alfresco transaction support */
+ private AlfrescoTransactionSupport alfrescoTransactionSupport;
+
+ /** authentication util */
+ private AuthenticationUtil authenticationUtil;
+
+ /** indicates whether auto version should be triggered on type change */
+ private boolean isAutoVersionOnTypeChange = false;
+
+ /**
+ * @param nodeService node service
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * @param versionService version service
+ */
+ public void setVersionService(VersionService versionService)
+ {
+ this.versionService = versionService;
+ }
+
+ /**
+ * @param lockService lock service
+ */
+ public void setLockService(LockService lockService)
+ {
+ this.lockService = lockService;
+ }
+
+ /**
+ * @param alfrescoTransactionSupport alfresco transaction support
+ */
+ public void setAlfrescoTransactionSupport(AlfrescoTransactionSupport alfrescoTransactionSupport)
+ {
+ this.alfrescoTransactionSupport = alfrescoTransactionSupport;
+ }
+
+ /**
+ * @param authenticationUtil authentication util
+ */
+ public void setAuthenticationUtil(AuthenticationUtil authenticationUtil)
+ {
+ this.authenticationUtil = authenticationUtil;
+ }
+
+ /**
+ * @param isAutoVersionOnTypeChange true if auto version on type change, false otherwise
+ */
+ public void setAutoVersionOnTypeChange(boolean isAutoVersionOnTypeChange)
+ {
+ this.isAutoVersionOnTypeChange = isAutoVersionOnTypeChange;
+ }
+
+ /**
+ * On set node type behaviour
+ *
+ * @param nodeRef node reference
+ * @param oldType old type
+ * @param newType new type
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ @Behaviour
+ (
+ type="cm:versionable",
+ kind=BehaviourKind.CLASS,
+ notificationFrequency=NotificationFrequency.TRANSACTION_COMMIT
+ )
+ public void onSetNodeType(NodeRef nodeRef, QName oldType, QName newType)
+ {
+ if (isAutoVersionOnTypeChange &&
+ nodeService.exists(nodeRef) == true &&
+ !LockUtils.isLockedAndReadOnly(nodeRef, lockService) &&
+ nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true &&
+ nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY) == false)
+ {
+ Map versionedNodeRefs = (Map)alfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS);
+ if (versionedNodeRefs == null || versionedNodeRefs.containsKey(nodeRef) == false)
+ {
+ // Determine whether the node is auto versionable (for content updates) or not
+ boolean autoVersion = false;
+ Boolean value = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_AUTO_VERSION);
+ if (value != null)
+ {
+ // If the value is not null then
+ autoVersion = value.booleanValue();
+ }
+
+ // NOTE: auto version on type change is a global setting, if thins extension was moved into the
+ // core then cm:versionable could be extended with a property consistent with the current
+ // implementation
+
+ if (autoVersion)
+ {
+ // Create the auto-version
+ Map versionProperties = new HashMap(1);
+ versionProperties.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_AUTO_VERSION));
+
+ createVersionImpl(nodeRef, versionProperties);
+ }
+ }
+ }
+ }
+
+ /**
+ * On create version implementation method.
+ *
+ * @param nodeRef node reference
+ * @param versionProperties version properties
+ */
+ private void createVersionImpl(final NodeRef nodeRef, final Map versionProperties)
+ {
+ authenticationUtil.runAsSystem(new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+ recordCreateVersion(nodeRef, null);
+ versionService.createVersion(nodeRef, versionProperties);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Record that the new version has been created
+ *
+ * @param versionableNode versionable node reference
+ * @param version version
+ */
+ @SuppressWarnings("unchecked")
+ private void recordCreateVersion(NodeRef versionableNode, Version version)
+ {
+ Map versionedNodeRefs = (Map)alfrescoTransactionSupport.getResource(KEY_VERSIONED_NODEREFS);
+ if (versionedNodeRefs == null)
+ {
+ versionedNodeRefs = new HashMap();
+ alfrescoTransactionSupport.bindResource(KEY_VERSIONED_NODEREFS, versionedNodeRefs);
+ }
+ versionedNodeRefs.put(versionableNode, versionableNode);
+ }
+}
diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/AllTestSuite.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/AllTestSuite.java
index eefca5f1c4..2be90e2dab 100644
--- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/AllTestSuite.java
+++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/AllTestSuite.java
@@ -18,12 +18,11 @@
*/
package org.alfresco.module.org_alfresco_module_rm.test;
-import org.alfresco.module.org_alfresco_module_rm.test.integration.IntegrationTestSuite;
-import org.alfresco.module.org_alfresco_module_rm.test.legacy.LegacyTestSuite;
+import org.junit.extensions.cpsuite.ClasspathSuite;
+import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters;
+import org.junit.extensions.cpsuite.ClasspathSuite.SuiteTypes;
+import org.junit.extensions.cpsuite.SuiteType;
import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-import org.junit.runners.Suite.SuiteClasses;
-
/**
* Convenience test suite that runs all the tests.
@@ -31,11 +30,27 @@ import org.junit.runners.Suite.SuiteClasses;
* @author Roy Wetherall
* @since 2.1
*/
-@RunWith(Suite.class)
-@SuiteClasses(
-{
- LegacyTestSuite.class,
- IntegrationTestSuite.class
+@RunWith(ClasspathSuite.class)
+@SuiteTypes({SuiteType.TEST_CLASSES, SuiteType.RUN_WITH_CLASSES, SuiteType.JUNIT38_TEST_CLASSES})
+@ClassnameFilters({
+ // Execute all test classes ending with "Test"
+ ".*Test",
+ // Exclude the ones ending with "UnitTest"
+ "!.*UnitTest",
+ // Put the test classes you want to exclude here
+ "!.*DataLoadSystemTest",
+ "!.*RM2072Test",
+ "!.*RM2190Test",
+ "!.*RM981SystemTest",
+ "!.*RecordsManagementEventServiceImplTest",
+ "!.*RmRestApiTest",
+ "!.*NotificationServiceHelperSystemTest",
+ "!.*RetryingTransactionHelperBaseTest",
+ "!.*RMCaveatConfigServiceImplTest",
+ // This test is running successfully locally but not on bamboo (if executed as a single test).
+ // The problem can be reproduced if the whole test suite is run locally as well.
+ // Tests should not be dependent on other test classes and should run in any order without any problems.
+ "!.*EmailMapScriptTest"
})
public class AllTestSuite
{
diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/version/AutoVersionTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/version/AutoVersionTest.java
index 99c6eec74d..bad197926f 100755
--- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/version/AutoVersionTest.java
+++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/version/AutoVersionTest.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map;
import org.alfresco.model.ContentModel;
+import org.alfresco.module.org_alfresco_module_rm.version.ExtendedVersionableAspect;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.namespace.QName;
@@ -115,5 +116,117 @@ public class AutoVersionTest extends RecordableVersionsBaseTest
});
}
+ /**
+ * Given a versionable document with initial version turned off
+ * And auto version on type change is set on
+ * When I specialise the type of the document
+ * Then the version history contains the initial version
+ */
+ public void testSpecialisedNodeInitialVersionNotCreatedOnTypeChangeOn()
+ {
+ doBehaviourDrivenTest(new BehaviourDrivenTest(dmCollaborator)
+ {
+ private ExtendedVersionableAspect extendedVersionableAspect;
+ private NodeRef myDocument;
+
+ public void given() throws Exception
+ {
+ // turn auto version on type change on
+ extendedVersionableAspect = (ExtendedVersionableAspect)applicationContext.getBean("rm.extendedVersionableAspect");
+ assertNotNull(extendedVersionableAspect);
+ extendedVersionableAspect.setAutoVersionOnTypeChange(true);
+
+ // create a document
+ myDocument = fileFolderService.create(dmFolder, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
+
+ // make versionable
+ Map props = new HashMap(1);
+ props.put(ContentModel.PROP_INITIAL_VERSION, false);
+ nodeService.addAspect(myDocument, ContentModel.ASPECT_VERSIONABLE, props);
+ }
+
+ public void when()
+ {
+ // specialise document
+ nodeService.setType(myDocument, TYPE_CUSTOM_TYPE);
+ }
+
+ public void then()
+ {
+ VersionHistory versionHistory = versionService.getVersionHistory(myDocument);
+ assertNotNull(versionHistory);
+ assertEquals(1, versionHistory.getAllVersions().size());
+
+ NodeRef frozenState = versionHistory.getHeadVersion().getFrozenStateNodeRef();
+ assertEquals(TYPE_CUSTOM_TYPE, nodeService.getType(frozenState));
+ assertEquals(TYPE_CUSTOM_TYPE, nodeService.getType(myDocument));
+ }
+
+ public void after() throws Exception
+ {
+ // reset auto version on type to default off
+ extendedVersionableAspect.setAutoVersionOnTypeChange(false);
+ }
+ });
+ }
+
+ /**
+ * Given a versionable document with initial version turned on
+ * And auto version on type change is set on
+ * When I specialise the type of the document
+ * Then the version history contains the initial version
+ */
+ public void testSpecialisedNodeInitialVersionCreatedOnTypeChangeOn()
+ {
+ doBehaviourDrivenTest(new BehaviourDrivenTest(dmCollaborator)
+ {
+ private ExtendedVersionableAspect extendedVersionableAspect;
+ private NodeRef myDocument;
+
+ public void given() throws Exception
+ {
+ // turn auto version on type change on
+ extendedVersionableAspect = (ExtendedVersionableAspect)applicationContext.getBean("rm.extendedVersionableAspect");
+ assertNotNull(extendedVersionableAspect);
+ extendedVersionableAspect.setAutoVersionOnTypeChange(true);
+
+ // create a document
+ myDocument = fileFolderService.create(dmFolder, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
+
+ // make versionable
+ Map props = new HashMap(1);
+ props.put(ContentModel.PROP_INITIAL_VERSION, true);
+ nodeService.addAspect(myDocument, ContentModel.ASPECT_VERSIONABLE, props);
+ }
+
+ public void when()
+ {
+ // specialise document
+ nodeService.setType(myDocument, TYPE_CUSTOM_TYPE);
+ }
+
+ public void then()
+ {
+ VersionHistory versionHistory = versionService.getVersionHistory(myDocument);
+ assertNotNull(versionHistory);
+ assertEquals(2, versionHistory.getAllVersions().size());
+
+ NodeRef frozenState = versionHistory.getHeadVersion().getFrozenStateNodeRef();
+ assertEquals(TYPE_CUSTOM_TYPE, nodeService.getType(frozenState));
+ assertEquals(TYPE_CUSTOM_TYPE, nodeService.getType(myDocument));
+
+ frozenState = versionHistory.getVersion("1.0").getFrozenStateNodeRef();
+ assertEquals(ContentModel.TYPE_CONTENT, nodeService.getType(frozenState));
+ assertEquals(TYPE_CUSTOM_TYPE, nodeService.getType(myDocument));
+ }
+
+ public void after() throws Exception
+ {
+ // reset auto version on type to default off
+ extendedVersionableAspect.setAutoVersionOnTypeChange(false);
+ }
+ });
+ }
+
}
diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/version/ExtendedVersionableAspectUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/version/ExtendedVersionableAspectUnitTest.java
new file mode 100644
index 0000000000..8b6acb4195
--- /dev/null
+++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/version/ExtendedVersionableAspectUnitTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2005-2015 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.module.org_alfresco_module_rm.version;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.times;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
+import org.alfresco.module.org_alfresco_module_rm.util.AlfrescoTransactionSupport;
+import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.lock.LockStatus;
+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.VersionService;
+import org.alfresco.service.namespace.QName;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.extensions.webscripts.GUID;
+
+/**
+ * Extended versionable aspect unit test.
+ *
+ * @author Roy Wetherall
+ * @since 2.3.1
+ */
+public class ExtendedVersionableAspectUnitTest implements RecordsManagementModel
+{
+ /** Transaction resource key */
+ private static final String KEY_VERSIONED_NODEREFS = "versioned_noderefs";
+
+ /** test data */
+ private NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate());
+ private NodeRef anotherNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate());
+ private QName oldType = QName.createQName(RM_URI, GUID.generate());
+ private QName newType = QName.createQName(RM_URI, GUID.generate());
+
+ /** service mocks */
+ private @Mock NodeService mockedNodeService;
+ private @Mock VersionService mockedVersionService;
+ private @Mock LockService mockedLockService;
+ private @Mock AlfrescoTransactionSupport mockedAlfrescoTransactionSupport;
+ private @Mock AuthenticationUtil mockedAuthenticationUtil;
+
+ /** test instance of extended versionable aspect behaviour bean */
+ private @InjectMocks ExtendedVersionableAspect extendedVersionableAspect;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void testSetup()
+ {
+ MockitoAnnotations.initMocks(this);
+
+ // just do the work
+ doAnswer(new Answer