RM-1671: Automatic "Versioned By" Relationship

* as versions are record they are automatically connected to previous record version via the "versions" relationship in the file plan.
 * unit and integration tests
 * meta-data sotred on record about origional version, for example version label and description



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@89170 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Roy Wetherall
2014-10-27 03:01:34 +00:00
parent b47290b010
commit c6075654c8
9 changed files with 251 additions and 25 deletions

View File

@@ -30,6 +30,13 @@
</namespaces>
<constraints>
<!-- recordable version policy list -->
<!-- -->
<!-- contains the following recordable version policy options: -->
<!-- * none - business as usual -->
<!-- * major only - only major revisions will be recorded -->
<!-- * all - all revisions will be recorded -->
<constraint name="rmv:recordableVersionPolicyList" type="LIST">
<title>Recordable Version Policy List</title>
<parameter name="allowedValues">
@@ -45,13 +52,18 @@
<aspects>
<!-- versionsable aspsect -->
<!-- -->
<!-- defines the recordable version policy -->
<aspect name="rmv:versionable">
<properties>
<!-- destination file plan, defaults if not set -->
<property name="rmv:filePlan">
<title>File Plan</title>
<type>d:noderef</type>
<index enabled="false"/>
</property>
<!-- recordable version policy -->
<property name="rmv:recordableVersionPolicy">
<title>Recordable Version Policy</title>
<type>d:text</type>
@@ -64,13 +76,20 @@
</properties>
</aspect>
<!-- recorded version aspect -->
<!-- -->
<!-- applied to version and contains additional information required for a recorded -->
<!-- version -->
<aspect name="rmv:recordedVersion">
<properties>
<!-- reference to record that records frozen state for this version -->
<property name="rmv:recordNodeRef">
<title>Record Node Reference</title>
<type>d:noderef</type>
<index enabled="false"/>
</property><property name="rmv:frozenOwner">
</property>
<!-- frozen owner details -->
<property name="rmv:frozenOwner">
<title>Frozen Owner</title>
<type>d:text</type>
<index enabled="false"/>
@@ -78,6 +97,27 @@
</properties>
</aspect>
<!-- version record aspect -->
<!-- -->
<!-- applied to records and contains additional version record information -->
<aspect name="rmv:versionRecord">
<properties>
<!-- reference to the node that this record is a version of -->
<property name="rmv:versionedNodeRef">
<type>d:noderef</type>
<index enabled="false"/>
</property>
<!-- version label -->
<property name="rmv:versionLabel">
<type>d:text</type>
</property>
<!-- version description -->
<property name="rmv:versionDescription">
<type>d:text</type>
</property>
</properties>
</aspect>
</aspects>
</model>

View File

@@ -20,6 +20,7 @@
<property name="ownableService" ref="OwnableService" />
<property name="extendedSecurityService" ref="ExtendedSecurityService" />
<property name="authenticationUtil" ref="rm.authenticationUtil" />
<property name="relationshipService" ref="RelationshipService" />
</bean>
<bean class="org.alfresco.util.BeanExtender">
<property name="beanName" value="versionService" />

View File

@@ -989,6 +989,9 @@ public class RecordServiceImpl extends BaseBehaviourBean
props.put(PROP_IDENTIFIER, recordId);
props.put(PROP_ORIGIONAL_NAME, name);
nodeService.addAspect(document, RecordsManagementModel.ASPECT_RECORD, props);
// remove versionable aspect(s)
nodeService.removeAspect(document, RecordableVersionModel.ASPECT_VERSIONABLE);
}
catch (FileNotFoundException e)
{

View File

@@ -41,4 +41,10 @@ public interface RecordableVersionModel
QName ASPECT_RECORDED_VERSION = QName.createQName(RMV_URI, "recordedVersion");
QName PROP_RECORD_NODE_REF = QName.createQName(RMV_URI, "recordNodeRef");
QName PROP_FROZEN_OWNER = QName.createQName(RMV_URI, "frozenOwner");
/** version record aspect */
QName ASPECT_VERSION_RECORD = QName.createQName(RMV_URI, "versionRecord");
QName PROP_VERSIONED_NODEREF = QName.createQName(RMV_URI, "versionedNodeRef");
QName PROP_VERSION_LABEL = QName.createQName(RMV_URI, "versionLabel");
QName PROP_VERSION_DESCRIPTION = QName.createQName(RMV_URI, "versionDescription");
}

View File

@@ -19,6 +19,7 @@
package org.alfresco.module.org_alfresco_module_rm.version;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -29,6 +30,7 @@ import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.relationship.RelationshipService;
import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService;
import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil;
import org.alfresco.repo.policy.PolicyScope;
@@ -46,8 +48,10 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.version.ReservedVersionNameException;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.PropertyMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -66,6 +70,9 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
/** key used to indicate a recordable version */
public static final String KEY_RECORDABLE_VERSION = "recordable-version";
public static final String KEY_FILE_PLAN = "file-plan";
/** version record property */
public static final String PROP_VERSION_RECORD = "RecordVersion";
/** file plan service */
protected FilePlanService filePlanService;
@@ -84,6 +91,9 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
/** authentication util helper */
protected AuthenticationUtil authenticationUtil;
/** relationship service */
protected RelationshipService relationshipService;
/**
* @param filePlanService file plan service
@@ -132,6 +142,14 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
{
this.authenticationUtil = authenticationUtil;
}
/**
* @param relationshipService relationship service
*/
public void setRelationshipService(RelationshipService relationshipService)
{
this.relationshipService = relationshipService;
}
/**
* @see org.alfresco.repo.version.Version2ServiceImpl#createVersion(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, int)
@@ -265,13 +283,13 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
/**
* Creates a new recorded version
*
* @param sourceTypeRef
* @param versionHistoryRef
* @param standardVersionProperties
* @param versionProperties
* @param versionNumber
* @param nodeDetails
* @return
* @param sourceTypeRef source type name
* @param versionHistoryRef version history reference
* @param standardVersionProperties standard version properties
* @param versionProperties version properties
* @param versionNumber version number
* @param nodeDetails policy scope
* @return {@link NodeRef} record version
*/
protected NodeRef createNewRecordedVersion(QName sourceTypeRef,
NodeRef versionHistoryRef,
@@ -302,13 +320,51 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
final NodeRef nodeRef = (NodeRef)standardVersionProperties.get(Version2Model.PROP_QNAME_FROZEN_NODE_REF);
// create record
NodeRef record = createRecord(nodeRef, filePlan);
final NodeRef record = createRecord(nodeRef, filePlan);
// apply version record aspect to record
PropertyMap versionRecordProps = new PropertyMap(3);
versionRecordProps.put(PROP_VERSIONED_NODEREF, nodeRef);
versionRecordProps.put(RecordableVersionModel.PROP_VERSION_LABEL,
standardVersionProperties.get(
QName.createQName(Version2Model.NAMESPACE_URI,
Version2Model.PROP_VERSION_LABEL)));
versionRecordProps.put(RecordableVersionModel.PROP_VERSION_DESCRIPTION,
standardVersionProperties.get(
QName.createQName(Version2Model.NAMESPACE_URI,
Version2Model.PROP_VERSION_DESCRIPTION)));
nodeService.addAspect(record, ASPECT_VERSION_RECORD, versionRecordProps);
// wire record up to previous record
VersionHistory versionHistory = getVersionHistory(nodeRef);
if (versionHistory != null)
{
Collection<Version> previousVersions = versionHistory.getAllVersions();
for (Version previousVersion : previousVersions)
{
// look for the associated record
final NodeRef previousRecord = (NodeRef)previousVersion.getVersionProperties().get(PROP_VERSION_RECORD);
if (previousRecord != null)
{
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
// indicate that the new record versions the previous record
relationshipService.addRelationship("versions", record, previousRecord);
return null;
}
});
break;
}
}
}
// create version nodeRef
ChildAssociationRef childAssocRef = dbNodeService.createNode(
versionHistoryRef,
Version2Model.CHILD_QNAME_VERSIONS,
// 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
QName.createQName(Version2Model.NAMESPACE_URI, Version2Model.CHILD_VERSIONS + "-" + versionNumber),
sourceTypeRef,
null);
@@ -393,7 +449,7 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
// create a copy of the original state and add it to the unfiled record container
FileInfo recordInfo = fileFolderService.copy(nodeRef, unfiledRecordFolder, null);
record = recordInfo.getNodeRef();
record = recordInfo.getNodeRef();
// remove added copy assocs
List<AssociationRef> recordAssocs = dbNodeService.getTargetAssocs(record, ContentModel.ASSOC_ORIGINAL);
@@ -465,7 +521,7 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl
NodeRef record = (NodeRef)dbNodeService.getProperty(versionRef, PROP_RECORD_NODE_REF);
if (record != null)
{
version.getVersionProperties().put("RecordVersion", record);
version.getVersionProperties().put(PROP_VERSION_RECORD, record);
}
return version;

View File

@@ -126,7 +126,13 @@ public class AdHocRecordableVersions extends RecordableVersionsBaseTest
{
// create version
versionService.createVersion(dmDocument, versionProperties);
}
}
public void then()
{
// check that the record has been recorded
checkRecordedVersion(dmDocument, DESCRIPTION, "0.1");
}
});
}
@@ -157,7 +163,7 @@ public class AdHocRecordableVersions extends RecordableVersionsBaseTest
Version version = versionService.createVersion(dmDocument, versionProperties);
// add custom meta-data to record
NodeRef record = (NodeRef)version.getVersionProperties().get("RecordVersion");
NodeRef record = (NodeRef)version.getVersionProperties().get(RecordableVersionServiceImpl.PROP_VERSION_RECORD);
assertNotNull(record);
recordService.addRecordType(record, TestModel.ASPECT_RECORD_METADATA);
nodeService.setProperty(record, TestModel.PROPERTY_RECORD_METADATA, "Peter Wetherall");

View File

@@ -18,10 +18,16 @@
*/
package org.alfresco.module.org_alfresco_module_rm.test.integration.version;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.relationship.Relationship;
import org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionModel;
import org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionPolicy;
import org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionServiceImpl;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.util.PropertyMap;
/**
@@ -32,24 +38,30 @@ import org.alfresco.util.PropertyMap;
*/
public class AutoRecordableVersions extends RecordableVersionsBaseTest
{
/** example content */
public final static String MY_NEW_CONTENT = "this is some new content that I have changed to trigger auto version";
/**
* Given that all revisions will be recorded,
* When I update the content of a document,
* Then a recorded version will be created
*/
public void testAutoVersionRecordAllRevisions()
{
doBehaviourDrivenTest(new BehaviourDrivenTest(dmCollaborator)
{
public void given() throws Exception
{
// make the node versionable
PropertyMap versionableProperties = new PropertyMap(1);
versionableProperties.put(ContentModel.PROP_INITIAL_VERSION, false);
nodeService.addAspect(dmDocument, ContentModel.ASPECT_VERSIONABLE, versionableProperties);
// set the recordable version policy
PropertyMap recordableVersionProperties = new PropertyMap(1);
recordableVersionProperties.put(PROP_RECORDABLE_VERSION_POLICY, RecordableVersionPolicy.ALL);
recordableVersionProperties.put(PROP_FILE_PLAN, filePlan);
nodeService.addAspect(dmDocument, RecordableVersionModel.ASPECT_VERSIONABLE, recordableVersionProperties);
nodeService.addAspect(dmDocument, RecordableVersionModel.ASPECT_VERSIONABLE, recordableVersionProperties);
// make the node versionable
PropertyMap versionableProperties = new PropertyMap(1);
versionableProperties.put(ContentModel.PROP_INITIAL_VERSION, false);
nodeService.addAspect(dmDocument, ContentModel.ASPECT_VERSIONABLE, versionableProperties);
}
public void when()
@@ -62,9 +74,98 @@ public class AutoRecordableVersions extends RecordableVersionsBaseTest
public void then()
{
// check that the record has been recorded
checkRecordedVersion(dmDocument, null, "0.2");
checkRecordedVersion(dmDocument, null, "0.1");
}
});
}
/**
* Given that all revisions will be automatically recorded,
* When I update a document 3 times,
* Then all 3 created records will be related together using the "VersionedBy" relationship
*/
public void testVersionRecordsRelated()
{
doBehaviourDrivenTest(new BehaviourDrivenTest(dmCollaborator, false)
{
/** given **/
public void given() throws Exception
{
doTestInTransaction(new VoidTest()
{
@Override
public void runImpl() throws Exception
{
// set the recordable version policy
PropertyMap recordableVersionProperties = new PropertyMap(1);
recordableVersionProperties.put(PROP_RECORDABLE_VERSION_POLICY, RecordableVersionPolicy.ALL);
recordableVersionProperties.put(PROP_FILE_PLAN, filePlan);
nodeService.addAspect(dmDocument, RecordableVersionModel.ASPECT_VERSIONABLE, recordableVersionProperties);
// make the node versionable
PropertyMap versionableProperties = new PropertyMap(1);
versionableProperties.put(ContentModel.PROP_INITIAL_VERSION, false);
nodeService.addAspect(dmDocument, ContentModel.ASPECT_VERSIONABLE, versionableProperties);
}
});
}
/** when **/
public void when()
{
// update the content 3 times
updateContent();
updateContent();
updateContent();
}
/** then */
public void then()
{
doTestInTransaction(new VoidTest()
{
@Override
public void runImpl() throws Exception
{
// check that the record has been recorded
checkRecordedVersion(dmDocument, null, "0.3");
Version version = versionService.getCurrentVersion(dmDocument);
NodeRef record = (NodeRef)version.getVersionProperties().get(RecordableVersionServiceImpl.PROP_VERSION_RECORD);
boolean foundPrevious = false;
Set<Relationship> relationships = relationshipService.getRelationshipsFrom(record);
assertNotNull(relationships);
assertEquals(1, relationships.size());
for (Relationship relationship : relationships)
{
if (relationship.getUniqueName().equals("versions"))
{
NodeRef previousVersionRecord = relationship.getTarget();
assertNotNull(previousVersionRecord);
foundPrevious = true;
}
}
assertTrue(foundPrevious);
}
});
}
/**
* Helper method to update content of dmDocument
*/
private void updateContent()
{
doTestInTransaction(new VoidTest()
{
@Override
public void runImpl() throws Exception
{
ContentWriter writer = contentService.getWriter(dmDocument, ContentModel.PROP_CONTENT, true);
writer.putContent(MY_NEW_CONTENT);
}
});
}
});
}
}

View File

@@ -27,6 +27,7 @@ import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionModel;
import org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionServiceImpl;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -122,12 +123,21 @@ public abstract class RecordableVersionsBaseTest extends BaseRMTestCase implemen
checkAspects(frozen, beforeAspects);
// record version node reference is available on version
NodeRef record = (NodeRef)version.getVersionProperties().get("RecordVersion");
NodeRef record = (NodeRef)version.getVersionProperties().get(RecordableVersionServiceImpl.PROP_VERSION_RECORD);
assertNotNull(record);
// check that the version record information has been added
assertTrue(nodeService.hasAspect(record, ASPECT_VERSION_RECORD));
assertEquals(versionLabel, nodeService.getProperty(record, RecordableVersionModel.PROP_VERSION_LABEL));
assertEquals(description, nodeService.getProperty(record, RecordableVersionModel.PROP_VERSION_DESCRIPTION));
// record version is an unfiled record
assertTrue(recordService.isRecord(record));
assertFalse(recordService.isFiled(record));
// check that the created record does not have either of the versionable aspects
assertFalse(nodeService.hasAspect(record, ContentModel.ASPECT_VERSIONABLE));
assertFalse(nodeService.hasAspect(record, RecordableVersionModel.ASPECT_VERSIONABLE));
// check the version history
VersionHistory versionHistory = versionService.getVersionHistory(document);
@@ -152,7 +162,7 @@ public abstract class RecordableVersionsBaseTest extends BaseRMTestCase implemen
assertEquals(versionLabel, version.getVersionLabel());
// record version node reference is available on version
NodeRef record = (NodeRef)version.getVersionProperties().get("RecordVersion");
NodeRef record = (NodeRef)version.getVersionProperties().get(RecordableVersionServiceImpl.PROP_VERSION_RECORD);
assertNull(record);
// check the version history
@@ -179,7 +189,10 @@ public abstract class RecordableVersionsBaseTest extends BaseRMTestCase implemen
cloneFrozenProperties.remove(beforePropertyName);
}
else if (!PROP_FILE_PLAN.equals(beforePropertyName) &&
!PROP_RECORDABLE_VERSION_POLICY.equals(beforePropertyName))
!PROP_RECORDABLE_VERSION_POLICY.equals(beforePropertyName) &&
!ContentModel.PROP_AUTO_VERSION_PROPS.equals(beforePropertyName) &&
!ContentModel.PROP_AUTO_VERSION.equals(beforePropertyName) &&
!ContentModel.PROP_INITIAL_VERSION.equals(beforePropertyName))
{
fail("Property missing from frozen state .. " + beforePropertyName);
}
@@ -200,6 +213,7 @@ public abstract class RecordableVersionsBaseTest extends BaseRMTestCase implemen
Set<QName> frozenAspects = nodeService.getAspects(frozen);
cloneBeforeAspects.removeAll(frozenAspects);
cloneBeforeAspects.remove(RecordableVersionModel.ASPECT_VERSIONABLE);
cloneBeforeAspects.remove(ContentModel.ASPECT_VERSIONABLE);
if (!cloneBeforeAspects.isEmpty())
{
fail("Aspects not present in frozen state. " + cloneBeforeAspects.toString());

View File

@@ -70,7 +70,6 @@ public class RecordableVersionServiceImplUnitTest extends BaseUnitTest
private Map<String, Serializable> versionProperties;
/** mocked services */
@SuppressWarnings("unused")
private @Mock(name="versionMigrator") VersionMigrator mockedVersionMigrator;
private @Mock(name="dbNodeService") NodeService mockedDbNodeService;