Work that's part of RM-2431 and RM-2432.

Added to the classified-content-model - various properties as described in RM-2431 along with a constraint on Reclassification Action.
Addition to the ClassificationSchemeService of methods and types associated with Reclassification. (Upgrade, Downgrade, Declassify). See RM-2432.
Behaviour bean that will automatically set lastReclassificationAction and lastReclassifyBy in response to any change to currentClassificationLevel.
Also some util methods in RMCollections.
Fixed some spelling mistakes in classification-related properties.



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@108878 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Neil McErlean
2015-07-23 16:13:43 +00:00
parent 9d5fb95ced
commit ebc34f9209
11 changed files with 354 additions and 6 deletions

View File

@@ -113,6 +113,8 @@
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationReasonById=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getUnclassifiedClassificationLevel=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getExemptionCategories=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getReclassification=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getReclassificationValues=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.*=ACL_DENY
</value>
</property>
@@ -231,4 +233,12 @@
</value>
</property>
</bean>
<!-- Beans relating to the classification content model -->
<bean id="clf.classified"
class="org.alfresco.module.org_alfresco_module_rm.model.clf.aspect.ClassifiedAspect"
parent="rm.baseBehaviour">
<property name="classificationSchemeService" ref="ClassificationSchemeService" />
</bean>
</beans>

View File

@@ -32,6 +32,8 @@
type="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationReasonConstraint" />
<constraint name="clf:exemptionCategoryRestriction"
type="org.alfresco.module.org_alfresco_module_rm.classification.ExemptionCategoryConstraint" />
<constraint name="clf:reclassificationList"
type="org.alfresco.module.org_alfresco_module_rm.classification.ReclassificationValueConstraint" />
</constraints>
<!-- Types -->
@@ -105,19 +107,19 @@
<mandatory>false</mandatory>
<!-- TODO Constraint on this and two previous fields -->
</property>
<property name="clf:declasificationDate">
<property name="clf:declassificationDate">
<title>Declassification Date</title>
<description>The date when this may be declassified</description>
<type>d:date</type>
<mandatory>false</mandatory>
</property>
<property name="clf:declasificationEvent">
<property name="clf:declassificationEvent">
<title>Declassification Event</title>
<description>The event when this may be declassified</description>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="clf:declasificationExemptions">
<property name="clf:declassificationExemptions">
<title>Declassification Exemptions</title>
<description>Exemptions that may preclude this from being declassified</description>
<type>d:text</type>
@@ -126,6 +128,32 @@
<constraint ref="clf:exemptionCategoryRestriction" />
</constraints>
</property>
<property name="clf:lastReclassificationAction">
<title>Last Reclassification Action</title>
<description>Text identifying the type of the previous reclassification on this node</description>
<type>d:text</type>
<constraints>
<constraint ref="clf:reclassificationList" />
</constraints>
</property>
<property name="clf:lastReclassifyBy">
<title>Last user to reclassify this node</title>
<description>Identifier for the user who most recently reclassified this node</description>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="clf:lastReclassifyAt">
<title>Last reclassification date</title>
<description>Date for when this node was last reclassified</description>
<type>d:date</type>
<mandatory>false</mandatory>
</property>
<property name="clf:lastReclassifyReason">
<title>Last reclassification reason</title>
<description>Text giving the reason for the last reclassification</description>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
</aspect>

View File

@@ -28,4 +28,5 @@ import java.io.Serializable;
*/
public interface ClassificationSchemeEntity extends Serializable
{
// Intentionally empty
}

View File

@@ -19,6 +19,7 @@
package org.alfresco.module.org_alfresco_module_rm.classification;
import java.util.List;
import java.util.Set;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.LevelIdNotFound;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.ReasonIdNotFound;
@@ -78,4 +79,31 @@ public interface ClassificationSchemeService
* @return The exemption categories in the order that they are defined.
*/
List<ExemptionCategory> getExemptionCategories();
/**
* Identifies the reclassification type for the provided pair of {@link ClassificationLevel levels}.
*
* @param from the first classification level.
* @param to the second classification level.
* @return the reclassification represented by this change, or {@code null} if it is not a change.
*/
Reclassification getReclassification(ClassificationLevel from, ClassificationLevel to);
Set<String> getReclassificationValues();
/** Types of reclassification. */
enum Reclassification
{
UPGRADE, DOWNGRADE, DECLASSIFY;
/** Returns the name of this enum value in a format suitable for storage in the Alfresco repo. */
public String toModelString()
{
final String name = toString();
final StringBuilder result = new StringBuilder(name.length());
result.append(name.charAt(0))
.append(name.substring(1).toLowerCase());
return result.toString();
}
}
}

View File

@@ -18,16 +18,21 @@
*/
package org.alfresco.module.org_alfresco_module_rm.classification;
import static java.util.Collections.unmodifiableSet;
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.LevelIdNotFound;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.ReasonIdNotFound;
import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel;
import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.util.ParameterCheck;
/**
* @author Neil Mc Erlean
@@ -122,4 +127,39 @@ public class ClassificationSchemeServiceImpl extends ServiceBaseImpl implements
return (exemptionCategoryManager == null ? Collections.<ExemptionCategory>emptyList() :
Collections.unmodifiableList(exemptionCategoryManager.getExemptionCategories()));
}
@Override
public Reclassification getReclassification(ClassificationLevel from, ClassificationLevel to)
{
ParameterCheck.mandatory("from", from);
ParameterCheck.mandatory("to", to);
final List<ClassificationLevel> levels = getClassificationLevels();
final int fromIndex = levels.indexOf(from);
final int toIndex = levels.indexOf(to);
final int lastIndex = levels.size() - 1;
if (from.equals(to))
{ return null; }
else if (fromIndex < lastIndex && toIndex == lastIndex)
{
return Reclassification.DECLASSIFY;
}
else
{
return fromIndex < toIndex ? Reclassification.DOWNGRADE : Reclassification.UPGRADE;
}
}
@Override
public Set<String> getReclassificationValues()
{
Set<String> result = new HashSet<>();
for (Reclassification r : Reclassification.values())
{
result.add(r.toModelString());
}
return unmodifiableSet(result);
}
}

View File

@@ -44,8 +44,8 @@ import org.alfresco.service.namespace.QName;
*
* @author tpage
*/
public class ContentClassificationServiceImpl extends ServiceBaseImpl implements ContentClassificationService,
ClassifiedContentModel
public class ContentClassificationServiceImpl extends ServiceBaseImpl
implements ContentClassificationService, ClassifiedContentModel
{
private ClassificationLevelManager levelManager;
private ClassificationReasonManager reasonManager;

View File

@@ -0,0 +1,47 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.classification;
import static java.util.Collections.unmodifiableList;
import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Check that a {@link ClassifiedContentModel#PROP_LAST_RECLASSIFICATION_ACTION reclassifiction action }value is valid.
*
* @author Neil Mc Erlean
* @since 3.0.a
*/
public class ReclassificationValueConstraint extends ClassificationSchemeEntityConstraint
{
@Override
protected List<String> getAllowedValues()
{
final Set<String> resultSet = classificationSchemeService.getReclassificationValues();
List<String> result = new ArrayList<>(resultSet.size());
result.addAll(resultSet);
return unmodifiableList(result);
}
}

View File

@@ -54,6 +54,15 @@ public interface ClassifiedContentModel
QName PROP_DECLASSIFICATION_EVENT = QName.createQName(CLF_URI, "declassificationEvent");
QName PROP_DECLASSIFICATION_EXEMPTIONS = QName.createQName(CLF_URI, "declassificationExemptions");
QName PROP_LAST_RECLASSIFY_BY = QName.createQName(CLF_URI, "lastReclassifyBy");
QName PROP_LAST_RECLASSIFY_AT = QName.createQName(CLF_URI, "lastReclassifyAt");
QName PROP_LAST_RECLASSIFY_REASON = QName.createQName(CLF_URI, "lastReclassifyReason");
QName PROP_LAST_RECLASSIFICATION_ACTION = QName.createQName(CLF_URI, "lastReclassificationAction");
/** Reclassification allowed values. */
String RECLASSIFICATION_UPGRADE = "UPGRADE";
String RECLASSIFICATION_DOWNGRADE = "DOWNGRADE";
String RECLASSIFICATION_DECLASSIFY = "DECLASSIFY";
/** Security Clearance aspect. */
QName ASPECT_SECURITY_CLEARANCE = QName.createQName(CLF_URI, "securityClearance");
QName PROP_CLEARANCE_LEVEL = QName.createQName(CLF_URI, "clearanceLevel");

View File

@@ -0,0 +1,107 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.model.clf.aspect;
import static org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.Reclassification;
import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.ASPECT_CLASSIFIED;
import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION;
import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.PROP_LAST_RECLASSIFICATION_ACTION;
import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.PROP_LAST_RECLASSIFY_AT;
import static org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.Difference;
import static org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.diffKey;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationLevel;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService;
import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel;
import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean;
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;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
/**
* clf:classification behaviour bean
*
* @since 3.0.a
*/
@BehaviourBean
(
defaultType = "clf:classified"
)
public class ClassifiedAspect extends BaseBehaviourBean
implements NodeServicePolicies.OnUpdatePropertiesPolicy
{
private ClassificationSchemeService classificationSchemeService;
public void setClassificationSchemeService(ClassificationSchemeService service)
{
this.classificationSchemeService = service;
}
/**
* Ensures that on reclassification of content (in other words a change in the value of the
* {@link ClassifiedContentModel#PROP_CURRENT_CLASSIFICATION clf:currentClassification} property)
* that various metadata are correctly updated as a side-effect.
*/
@Override
@Behaviour
(
kind = BehaviourKind.CLASS,
notificationFrequency = NotificationFrequency.EVERY_EVENT
)
public void onUpdateProperties(final NodeRef nodeRef,
final Map<QName, Serializable> before,
final Map<QName, Serializable> after)
{
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork()
{
final Difference classificationChange = diffKey(before, after, PROP_CURRENT_CLASSIFICATION);
if (classificationChange == Difference.CHANGED && nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED))
{
final String oldValue = (String)before.get(PROP_CURRENT_CLASSIFICATION);
final String newValue = (String)after.get(PROP_CURRENT_CLASSIFICATION);
final ClassificationLevel oldLevel = classificationSchemeService.getClassificationLevelById(oldValue);
final ClassificationLevel newLevel = classificationSchemeService.getClassificationLevelById(newValue);
Reclassification reclassification = classificationSchemeService.getReclassification(oldLevel, newLevel);
if (reclassification != null)
{
nodeService.setProperty(nodeRef, PROP_LAST_RECLASSIFICATION_ACTION, reclassification.toModelString());
nodeService.setProperty(nodeRef, PROP_LAST_RECLASSIFY_AT, new Date());
}
}
return null;
}
}, AuthenticationUtil.getSystemUserName());
}
}

View File

@@ -18,13 +18,17 @@
*/
package org.alfresco.module.org_alfresco_module_rm.util;
import static org.springframework.util.ObjectUtils.nullSafeEquals;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Various common helper methods for Collections.
* Various common helper methods for Collections. This class is probably only appropriate for use with relatively
* small collections as it has not been optimised for dealing with large collections.
*
* @author Neil Mc Erlean
* @since 3.0
@@ -58,4 +62,45 @@ public final class RMCollectionUtils
}
return duplicateElems;
}
/**
* This enum represents a change in an entry between 2 collections.
*/
public enum Difference
{
ADDED, REMOVED, CHANGED, UNCHANGED
}
/**
* Determines the change in a Map entry between two Maps.
* Note that both maps must have the same types of key-value pair.
*
* @param from the first collection.
* @param to the second collection.
* @param key the key identifying the entry.
* @param <K> the type of the key.
* @param <V> the type of the value.
* @return the {@link Difference}.
*
* @throws IllegalArgumentException if {@code key} is {@code null}.
*/
public static <K, V> Difference diffKey(Map<K, V> from, Map<K, V> to, K key)
{
if (key == null) { throw new IllegalArgumentException("Key cannot be null."); }
if (from.containsKey(key))
{
if (to.containsKey(key))
{
if (nullSafeEquals(from.get(key), to.get(key))) { return Difference.UNCHANGED; }
else { return Difference.CHANGED; }
}
else { return Difference.REMOVED; }
}
else
{
if (to.containsKey(key)) { return Difference.ADDED; }
else { return Difference.UNCHANGED; }
}
}
}

View File

@@ -20,12 +20,17 @@
package org.alfresco.module.org_alfresco_module_rm.util;
import static java.util.Arrays.asList;
import static org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.diffKey;
import static org.junit.Assert.assertEquals;
import org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.Difference;
import org.junit.Test;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Unit tests for {@link RMCollectionUtils}.
@@ -42,4 +47,32 @@ public class RMCollectionUtilsUnitTest
assertEquals(Collections.emptyList(), RMCollectionUtils.getDuplicateElements(asList("A", "B", "C")));
}
@Test public void compareMaps()
{
// Set up two maps to compare
final Map<Integer, Integer> mapA = new HashMap<>();
final Map<Integer, Integer> mapB = new HashMap<>();
// Fill one map with numbers and their squares...
for (int i : asList(1, 2, 3, 4, 5))
{
mapA.put(i, i*i);
}
// ... the other one has the same entries...
mapB.putAll(mapA);
// ... but with an addition, a deletion and a value change.
mapB.put(6, 36);
mapB.remove(1);
mapB.put(3, 100);
// Now ensure that various changes are correctly identified
assertEquals(Difference.REMOVED, diffKey(mapA, mapB, 1));
assertEquals(Difference.ADDED, diffKey(mapA, mapB, 6));
assertEquals(Difference.UNCHANGED, diffKey(mapA, mapB, 2));
assertEquals(Difference.UNCHANGED, diffKey(mapA, mapB, -1));
assertEquals(Difference.CHANGED, diffKey(mapA, mapB, 3));
}
}