diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml
index c1ec943eb9..775cb44d97 100644
--- a/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml
@@ -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
@@ -231,4 +233,12 @@
+
+
+
+
+
+
diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml
index 61c042e759..90d487894f 100644
--- a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml
@@ -32,6 +32,8 @@
type="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationReasonConstraint" />
+
@@ -105,19 +107,19 @@
false
-
+
Declassification Date
The date when this may be declassified
d:date
false
-
+
Declassification Event
The event when this may be declassified
d:text
false
-
+
Declassification Exemptions
Exemptions that may preclude this from being declassified
d:text
@@ -126,6 +128,32 @@
+
+ Last Reclassification Action
+ Text identifying the type of the previous reclassification on this node
+ d:text
+
+
+
+
+
+ Last user to reclassify this node
+ Identifier for the user who most recently reclassified this node
+ d:text
+ false
+
+
+ Last reclassification date
+ Date for when this node was last reclassified
+ d:date
+ false
+
+
+ Last reclassification reason
+ Text giving the reason for the last reclassification
+ d:text
+ false
+
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeEntity.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeEntity.java
index 8680f3ab62..52c0462be7 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeEntity.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeEntity.java
@@ -28,4 +28,5 @@ import java.io.Serializable;
*/
public interface ClassificationSchemeEntity extends Serializable
{
+ // Intentionally empty
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java
index b655ed4ba8..627aa4bd22 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java
@@ -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 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 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();
+ }
+ }
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java
index b40d6c21bd..6f4201418d 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java
@@ -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.emptyList() :
Collections.unmodifiableList(exemptionCategoryManager.getExemptionCategories()));
}
+
+ @Override
+ public Reclassification getReclassification(ClassificationLevel from, ClassificationLevel to)
+ {
+ ParameterCheck.mandatory("from", from);
+ ParameterCheck.mandatory("to", to);
+
+ final List 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 getReclassificationValues()
+ {
+ Set result = new HashSet<>();
+ for (Reclassification r : Reclassification.values())
+ {
+ result.add(r.toModelString());
+ }
+ return unmodifiableSet(result);
+ }
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java
index 612f6aaad6..19cf809500 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java
@@ -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;
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ReclassificationValueConstraint.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ReclassificationValueConstraint.java
new file mode 100644
index 0000000000..169583c8d1
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ReclassificationValueConstraint.java
@@ -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 .
+ */
+
+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 getAllowedValues()
+ {
+ final Set resultSet = classificationSchemeService.getReclassificationValues();
+ List result = new ArrayList<>(resultSet.size());
+ result.addAll(resultSet);
+
+ return unmodifiableList(result);
+ }
+}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java
index 60a45ff976..169242ffc1 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java
@@ -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");
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspect.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspect.java
new file mode 100644
index 0000000000..702e847064
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspect.java
@@ -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 .
+ */
+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 before,
+ final Map after)
+ {
+ AuthenticationUtil.runAs(new RunAsWork()
+ {
+ 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());
+ }
+}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtils.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtils.java
index 37ea4eff40..3bc4e39937 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtils.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtils.java
@@ -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 the type of the key.
+ * @param the type of the value.
+ * @return the {@link Difference}.
+ *
+ * @throws IllegalArgumentException if {@code key} is {@code null}.
+ */
+ public static Difference diffKey(Map from, Map 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; }
+ }
+ }
}
diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtilsUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtilsUnitTest.java
index 83ca2c61e4..c84065c5fa 100644
--- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtilsUnitTest.java
+++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/util/RMCollectionUtilsUnitTest.java
@@ -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 mapA = new HashMap<>();
+ final Map 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));
+ }
}