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)); + } }