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 0e3e7fe767..d1b78c753a 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 @@ -52,7 +52,7 @@ rm.dispositionlifecycletrigger.cronexpression=0 0/5 * * * ? # Records contributors group # # if false then record contributor check is ignored and all users can contribute records from -# a collaboration site, if true then a user must be a member of the records contributor group +# a collaboration site, if true then a user must be a member of the records contributor group # in order for them to contribute a record from a collaboration site. Default value 'false'. rm.record.contributors.group.enabled=false # record contributors group, default value 'RECORD_CONTRIBUTORS' @@ -65,3 +65,5 @@ rm.record.contributors.group.name=RECORD_CONTRIBUTORS rm.classification.levelsFile=/alfresco/module/org_alfresco_module_rm/classification/rm-classification-levels.json # The location of the classification reasons configuration file (relative to the classpath). rm.classification.reasonsFile=/alfresco/module/org_alfresco_module_rm/classification/rm-classification-reasons.json +# The location of the exemption categories configuration file (relative to the classpath). +rm.classification.exemptionCategoriesFile=/alfresco/module/org_alfresco_module_rm/classification/rm-exemption-categories.json diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/classification/rm-exemption-categories.json b/rm-server/config/alfresco/module/org_alfresco_module_rm/classification/rm-exemption-categories.json new file mode 100644 index 0000000000..1be3f54b5c --- /dev/null +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/classification/rm-exemption-categories.json @@ -0,0 +1,38 @@ +[ + { + "id" : "1", + "displayLabel" : "rm.exemption-category.1" + }, + { + "id" : "2", + "displayLabel" : "rm.exemption-category.2" + }, + { + "id" : "3", + "displayLabel" : "rm.exemption-category.3" + }, + { + "id" : "4", + "displayLabel" : "rm.exemption-category.4" + }, + { + "id" : "5", + "displayLabel" : "rm.exemption-category.5" + }, + { + "id" : "6", + "displayLabel" : "rm.exemption-category.6" + }, + { + "id" : "7", + "displayLabel" : "rm.exemption-category.7" + }, + { + "id" : "8", + "displayLabel" : "rm.exemption-category.8" + }, + { + "id" : "9", + "displayLabel" : "rm.exemption-category.9" + } +] 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 cff254ead0..6c3db9e689 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 @@ -33,6 +33,7 @@ + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/classified-content.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/classified-content.properties index 8650728f5b..80e7fdbb1e 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/classified-content.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/classified-content.properties @@ -14,4 +14,15 @@ rm.classification-reason.14c=Intelligence activities (including special activiti rm.classification-reason.14d=Foreign relations or foreign activities for the U.S., including confidential sources. rm.classification-reason.14e=Scientific, technological or economic matters relating to the nation security which includes defense against transnational terrorism. rm.classification-reason.14f=U.S. Government programs for safeguarding nuclear materials of facilities. -rm.classification-reason.14g=Vulnerabilities or capabilities of systems, installations, infrastructures, projects, plans or protection services relating to the national security, which includes defense against transnational terrorism. \ No newline at end of file +rm.classification-reason.14g=Vulnerabilities or capabilities of systems, installations, infrastructures, projects, plans or protection services relating to the national security, which includes defense against transnational terrorism. + +## Default exemption categories +rm.exemption-category.1=Information that is currently and properly classified. +rm.exemption-category.2=Information that pertains solely to the internal rules and practices of the agency that, if released, would allow circumvention of an agency rule, policy, or statute, thereby impeding the agency in the conduct of its mission. +rm.exemption-category.3=Information specifically exempted by a statute establishing particular criteria for withholding. The language of the statute must clearly state that the information will not be disclosed. +rm.exemption-category.4=Information such as trade secrets and commercial or financial information obtained from a company on a privileged or confidential basis that, if released, would result in competitive harm to the company, impair the Government's ability to obtain like information in the future, or impair the Government's interest in compliance with program effectiveness. +rm.exemption-category.5=Inter- or intra-agency memorandums or letters containing information considered privileged in civil litigation. The most common privilege is the deliberative process privilege, which concerns documents that are part of the decision-making process and contain subjective evaluations, opinions, and recommendations. Other common privileges are the attorney-client and attorney work product privileges. +rm.exemption-category.6=Information, the release of which would reasonably be expected to constitute a clearly unwarranted invasion of the personal privacy of individuals. +rm.exemption-category.7=Records or information compiled for law enforcement purposes that: (a) Could reasonably be expected to interfere with law enforcement proceedings. (b) Would deprive a person of a right to a fair trial or impartial adjudication. (c) Could reasonably be expected to constitute an unwarranted invasion of the personal privacy of others. (d) Disclose the identity of a confidential source. (e) Disclose investigative techniques and procedures. (f) Could reasonably be expected to endanger the life or physical safety of any individual. +rm.exemption-category.8=Certain records of agencies responsible for supervision of financial institutions. +rm.exemption-category.9=Geological and geophysical information (including maps) concerning wells. diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationException.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationException.java index 0ebe62ac61..879b7ff4d2 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationException.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationException.java @@ -18,12 +18,12 @@ */ package org.alfresco.module.org_alfresco_module_rm.classification; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.repository.NodeRef; - import java.util.Collections; import java.util.List; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.NodeRef; + /** * Generic class for any runtime exception to do with classified records. * @@ -108,6 +108,18 @@ public class ClassificationException extends AlfrescoRuntimeException } } + /** The supplied exemption category id was not found in the configured list. */ + public static class ExemptionCategoryIdNotFound extends ClassificationException + { + /** serial version uid */ + private static final long serialVersionUID = -6754627999115496384L; + + public ExemptionCategoryIdNotFound(String id) + { + super("Could not find classification reason with id " + id); + } + } + public static class InvalidNode extends ClassificationException { /** serial version uid */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelValidation.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelValidation.java index 3f38240f88..ba66c8a9fc 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelValidation.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelValidation.java @@ -21,13 +21,13 @@ package org.alfresco.module.org_alfresco_module_rm.classification; import static java.util.Arrays.asList; import static org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.getDuplicateElements; +import java.util.ArrayList; +import java.util.List; + import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.IllegalAbbreviationChars; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.IllegalConfiguration; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.MissingConfiguration; -import java.util.ArrayList; -import java.util.List; - /** * This class is responsible for validating {@link ClassificationLevel}s. * @@ -36,7 +36,7 @@ import java.util.List; */ public class ClassificationLevelValidation { - /** The maximum number of chatacters allowed in a {@link ClassificationLevel#getId() level ID}. */ + /** The maximum number of characters allowed in a {@link ClassificationLevel#getId() level ID}. */ private static final int ABBREVIATION_LENGTH_LIMIT = 10; /** diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java index c92fcf2051..d771cdd042 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java @@ -46,8 +46,8 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem /** Logging utility for the class. */ private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationServiceBootstrap.class); - private final AuthenticationUtil authenticationUtil; - private final TransactionService transactionService; + private final AuthenticationUtil authenticationUtil; + private final TransactionService transactionService; private AttributeService attributeService; /** The classification levels currently configured in this server. */ private ClassificationLevelManager classificationLevelManager = new ClassificationLevelManager(); @@ -55,6 +55,8 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem private ClassificationReasonManager classificationReasonManager = new ClassificationReasonManager(); /** The clearance levels currently configured in this server. */ private ClearanceLevelManager clearanceLevelManager = new ClearanceLevelManager(); + /** The exemption categories currently configured in this server. */ + private ExemptionCategoryManager exemptionCategoryManager = new ExemptionCategoryManager(); private ClassificationServiceDAO classificationServiceDAO; private final ClassificationLevelValidation levelValidation = new ClassificationLevelValidation(); @@ -73,10 +75,13 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem public void setClassificationServiceDAO(ClassificationServiceDAO classificationServiceDAO) { this.classificationServiceDAO = classificationServiceDAO; } public void setAttributeService(AttributeService attributeService) { this.attributeService = attributeService; } /** Used in unit tests. */ + protected void setExemptionCategoryManager(ExemptionCategoryManager exemptionCategoryManager) { this.exemptionCategoryManager = exemptionCategoryManager; } + /** Used in unit tests. */ protected void setClearanceLevelManager(ClearanceLevelManager clearanceLevelManager) { this.clearanceLevelManager = clearanceLevelManager; } public ClassificationLevelManager getClassificationLevelManager() { return classificationLevelManager; } public ClassificationReasonManager getClassificationReasonManager() { return classificationReasonManager; } + public ExemptionCategoryManager getExemptionCategoryManager() { return exemptionCategoryManager; } public ClearanceLevelManager getClearanceLevelManager() { return clearanceLevelManager; } @Override public void onBootstrap(ApplicationEvent event) @@ -91,6 +96,7 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem { initConfiguredClassificationLevels(); initConfiguredClassificationReasons(); + initConfiguredExemptionCategories(); initConfiguredClearanceLevels(classificationLevelManager.getClassificationLevels()); return null; } @@ -173,6 +179,8 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem LOGGER.debug("Persisted classification reasons: {}", loggableStatusOf(persistedReasons)); LOGGER.debug("Classpath classification reasons: {}", loggableStatusOf(classpathReasons)); + // TODO Add reason validation. + if (isEmpty(persistedReasons)) { if (isEmpty(classpathReasons)) @@ -217,6 +225,64 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean implem return classificationServiceDAO.getConfiguredReasons(); } + /** + * Initialise the system's exemption categories by loading the values from a configuration file and storing them in + * the attribute service and the {@link ExemptionCategoryManager}. + */ + protected void initConfiguredExemptionCategories() + { + final List persistedCategories = getPersistedCategories(); + final List classpathCategories = getConfigurationCategories(); + + LOGGER.debug("Persisted exemption categories: {}", loggableStatusOf(persistedCategories)); + LOGGER.debug("Classpath exemption categories: {}", loggableStatusOf(classpathCategories)); + + // TODO Add exemption category validation. + + if (isEmpty(persistedCategories)) + { + if (isEmpty(classpathCategories)) + { + throw new MissingConfiguration("Exemption category configuration is missing."); + } + attributeService.setAttribute((Serializable) classpathCategories, EXEMPTION_CATEGORIES_KEY); + this.exemptionCategoryManager.setExemptionCategories(classpathCategories); + } + else + { + if (isEmpty(classpathCategories) || !classpathCategories.equals(persistedCategories)) + { + LOGGER.warn("Exemption categories configured in classpath do not match those stored in Alfresco. " + + "Alfresco will use the unchanged values stored in the database."); + // RM-2313 says that we should log a warning and proceed normally. + } + this.exemptionCategoryManager.setExemptionCategories(persistedCategories); + } + } + + /** + * Gets the list of exemption categories as persisted in the system. + * @return the list of exemption categories if they have been persisted, else {@code null}. + */ + private List getPersistedCategories() + { + return authenticationUtil.runAsSystem(new RunAsWork>() + { + @Override + @SuppressWarnings("unchecked") + public List doWork() throws Exception + { + return (List) attributeService.getAttribute(EXEMPTION_CATEGORIES_KEY); + } + }); + } + + /** Gets the list of exemption categories - as defined and ordered in the system configuration. */ + private List getConfigurationCategories() + { + return classificationServiceDAO.getConfiguredExemptionCategories(); + } + /** * Initialise and create a {@link ClearanceLevelManager}. * diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceDAO.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceDAO.java index 75469834b9..4c7be0570d 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceDAO.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceDAO.java @@ -42,6 +42,7 @@ class ClassificationServiceDAO { private String levelConfigLocation; private String reasonConfigLocation; + private String exemptionCategoryConfigLocation; /** Set the location of the level configuration file relative to the classpath. */ public void setLevelConfigLocation(String levelConfigLocation) { this.levelConfigLocation = levelConfigLocation; } @@ -49,6 +50,9 @@ class ClassificationServiceDAO /** Set the location of the reasons configuration file relative to the classpath. */ public void setReasonConfigLocation(String reasonConfigLocation) { this.reasonConfigLocation = reasonConfigLocation; } + /** Set the location of the exemption categories configuration file relative to the classpath. */ + public void setExemptionCategoryConfigLocation(String exemptionCategoryConfigLocation) { this.exemptionCategoryConfigLocation = exemptionCategoryConfigLocation; } + /** * Gets the list (in descending order) of classification levels - as defined in the classpath. * @@ -116,4 +120,38 @@ class ClassificationServiceDAO } return result; } + + /** + * Gets the list of exemption categories as defined in the classpath. + * + * @return the configured exemption categories in the given order, or an empty list if there are none. + */ + public List getConfiguredExemptionCategories() + { + List result; + try (final InputStream in = this.getClass().getResourceAsStream(exemptionCategoryConfigLocation)) + { + if (in == null) { result = Collections.emptyList(); } + else + { + final String jsonString = IOUtils.toString(in); + final JSONArray jsonArray = new JSONArray(new JSONTokener(jsonString)); + + result = new ArrayList<>(jsonArray.length()); + + for (int i = 0; i < jsonArray.length(); i++) + { + final JSONObject nextObj = jsonArray.getJSONObject(i); + final String id = nextObj.getString("id"); + final String displayLabelKey = nextObj.getString("displayLabel"); + result.add(new ExemptionCategory(id, displayLabelKey)); + } + } + } + catch (IOException | JSONException e) + { + throw new MalformedConfiguration("Could not read exemption category configuration", e); + } + return result; + } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategory.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategory.java new file mode 100644 index 0000000000..11b4010e5b --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategory.java @@ -0,0 +1,87 @@ +/* + * 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 org.apache.commons.lang.StringUtils.isNotBlank; + +import java.io.Serializable; + +import org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * This class is a POJO data type for an exemption category. It gives a reason why a piece of content should not be + * declassified. + * + * @author tpage + * @since 3.0 + */ +public final class ExemptionCategory implements Serializable +{ + /** serial version uid */ + private static final long serialVersionUID = -8990809567320071986L; + + private final String id; + private final String displayLabelKey; + + public ExemptionCategory(final String id, final String displayLabelKey) + { + RMParameterCheck.checkNotBlank("id", id); + RMParameterCheck.checkNotBlank("displayLabelKey", displayLabelKey); + this.id = id; + this.displayLabelKey = displayLabelKey; + } + + /** Returns the unique identifier for this exemption category. */ + public String getId() { return this.id; } + + /** Returns the key for the display label. */ + public String getDisplayLabelKey() { return displayLabelKey; } + + /** + * Returns the localised (current locale) display label for this exemption category. If no translation is found then + * return the key instead. + */ + public String getDisplayLabel() + { + String message = I18NUtil.getMessage(displayLabelKey); + return (isNotBlank(message) ? message : displayLabelKey); + } + + @Override public String toString() + { + StringBuilder msg = new StringBuilder(); + msg.append(ExemptionCategory.class.getSimpleName()) + .append(":").append(id); + + return msg.toString(); + } + + @Override public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExemptionCategory that = (ExemptionCategory) o; + + return this.id.equals(that.id); + } + + @Override public int hashCode() { return id.hashCode(); } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategoryManager.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategoryManager.java new file mode 100644 index 0000000000..b0938da276 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategoryManager.java @@ -0,0 +1,70 @@ +/* + * 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 java.util.Collection; + +import com.google.common.collect.ImmutableList; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.ExemptionCategoryIdNotFound; + +/** + * Container for the configured {@link ExemptionCategory} objects. + * + * @author tpage + */ +public class ExemptionCategoryManager +{ + /** An immutable list of exemption categories. */ + private ImmutableList exemptionCategories; + + /** + * Store an immutable copy of the given categories. + * + * @param exemptionCategories The exemption categories. + */ + public void setExemptionCategories(Collection exemptionCategories) + { + this.exemptionCategories = ImmutableList.copyOf(exemptionCategories); + } + + /** @return An immutable list of exemption categories. */ + public ImmutableList getExemptionCategories() + { + return exemptionCategories; + } + + /** + * Get a ExemptionCategory using its id. + * + * @param id The id of an exemption category. + * @return The exemption category. + * @throws ExemptionCategoryIdNotFound If the exemption category cannot be found. + */ + public ExemptionCategory findCategoryById(String id) throws ExemptionCategoryIdNotFound + { + for (ExemptionCategory exemptionCategory : exemptionCategories) + { + if (exemptionCategory.getId().equals(id)) + { + return exemptionCategory; + } + } + throw new ExemptionCategoryIdNotFound(id); + } +} 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 a8c6996e3b..f3b3f0265a 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 @@ -38,6 +38,7 @@ public interface ClassifiedContentModel Serializable[] LEVELS_KEY = new String[] { "org.alfresco", "module.org_alfresco_module_rm", "classification.levels" }; Serializable[] REASONS_KEY = new String[] { "org.alfresco", "module.org_alfresco_module_rm", "classification.reasons" }; + Serializable[] EXEMPTION_CATEGORIES_KEY = new String[] { "org.alfresco", "module.org_alfresco_module_rm", "classification.exemptionCategories" }; /** Classified aspect */ QName ASPECT_CLASSIFIED = QName.createQName(CLF_URI, "classified"); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationReasonsTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationReasonsTest.java index b46e327a04..042836848b 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationReasonsTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationReasonsTest.java @@ -22,7 +22,7 @@ package org.alfresco.module.org_alfresco_module_rm.test.integration.classificati import java.util.List; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationReason; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeServiceImpl; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceBootstrap; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; /** @@ -39,7 +39,7 @@ public class ClassificationReasonsTest extends BaseRMTestCase *

* Note that this test requires a clean db, as otherwise the classification scheme service will use the persisted * classification reasons in preference to those given on the classpath (see the logic in - * {@link ClassificationSchemeServiceImpl#initConfiguredClassificationReasons()}). + * {@link ClassificationServiceBootstrap#initConfiguredClassificationReasons()}). */ public void testLoadBootstrappedClassificationReasons() throws Exception { diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java index f1e7dfa3c2..882848ccf6 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java @@ -73,6 +73,9 @@ public class ClassificationServiceBootstrapUnitTest new ClassificationReason("id2", "label2")); private static final List ALTERNATIVE_CLASSIFICATION_REASONS = asList(new ClassificationReason("id8", "label8"), new ClassificationReason("id9", "label9")); + private static final List EXEMPTION_CATEGORIES = asList(new ExemptionCategory("id1", "label1"), + new ExemptionCategory("id2", "label2")); + private static final List CHANGED_EXEMPTION_CATEGORIES = asList(new ExemptionCategory("id3", "label3")); /** * A convenience method for turning lists of level id Strings into lists @@ -103,6 +106,7 @@ public class ClassificationServiceBootstrapUnitTest @Mock ClassificationServiceDAO mockClassificationServiceDAO; @Mock AttributeService mockAttributeService; @Mock AuthenticationUtil mockAuthenticationUtil; + @Mock ExemptionCategoryManager mockExemptionCategoryManager; @Mock ClearanceLevelManager mockClearanceLevelManager; /** Using a mock appender in the class logger so that we can verify some of the logging requirements. */ @Mock Appender mockAppender; @@ -113,7 +117,8 @@ public class ClassificationServiceBootstrapUnitTest { MockitoAnnotations.initMocks(this); MockAuthenticationUtilHelper.setup(mockAuthenticationUtil); - // This seems to be necessary because the ClearanceLevelManager isn't a constructor argument. + // This seems to be necessary because the POJO managers aren't constructor arguments. + classificationServiceBootstrap.setExemptionCategoryManager(mockExemptionCategoryManager); classificationServiceBootstrap.setClearanceLevelManager(mockClearanceLevelManager); } @@ -224,6 +229,80 @@ public class ClassificationServiceBootstrapUnitTest classificationServiceBootstrap.initConfiguredClassificationReasons(); } + @Test + public void testInitConfiguredExemptionCategories_firstLoad() + { + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())) + .thenReturn((Serializable) null); + when(mockClassificationServiceDAO.getConfiguredExemptionCategories()).thenReturn(EXEMPTION_CATEGORIES); + + classificationServiceBootstrap.initConfiguredExemptionCategories(); + + verify(mockExemptionCategoryManager).setExemptionCategories(EXEMPTION_CATEGORIES); + } + + @Test + public void testInitConfiguredExemptionCategories_secondLoad() + { + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())) + .thenReturn((Serializable) EXEMPTION_CATEGORIES); + when(mockClassificationServiceDAO.getConfiguredExemptionCategories()).thenReturn(EXEMPTION_CATEGORIES); + + classificationServiceBootstrap.initConfiguredExemptionCategories(); + + verify(mockExemptionCategoryManager).setExemptionCategories(EXEMPTION_CATEGORIES); + } + + /** + * Check that if the exemption categories supplied on the classpath differ from those already persisted then a + * warning is logged and no change is made to the persisted categories. + *

+ * This test uses the underlying log4j implementation to insert a mock Appender, and tests this for the warning + * message. If the underlying logging framework is changed then this unit test will fail, and it may not be possible + * to/worth fixing. + */ + @Test + public void testInitConfiguredExemptionCategories_changedCategories() + { + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())) + .thenReturn((Serializable) EXEMPTION_CATEGORIES); + when(mockClassificationServiceDAO.getConfiguredExemptionCategories()).thenReturn(CHANGED_EXEMPTION_CATEGORIES); + + // Put the mock Appender into the log4j logger and allow warning messages to be received. + org.apache.log4j.Logger log4jLogger = org.apache.log4j.Logger.getLogger(ClassificationServiceBootstrap.class); + log4jLogger.addAppender(mockAppender); + Level normalLevel = log4jLogger.getLevel(); + log4jLogger.setLevel(Level.WARN); + + // Call the method under test. + classificationServiceBootstrap.initConfiguredExemptionCategories(); + + // Reset the logging level for other tests. + log4jLogger.setLevel(normalLevel); + + // Check the persisted values weren't changed. + verify(mockAttributeService, never()).setAttribute(any(Serializable.class), + anyString(), anyString(), anyString()); + + // Check that the warning message was logged. + verify(mockAppender).doAppend(loggingEventCaptor.capture()); + List loggingEvents = loggingEventCaptor.getAllValues(); + Stream messages = loggingEvents.stream() + .map(LoggingEvent::getRenderedMessage); + String expectedMessage = "Exemption categories configured in classpath do not match those stored in Alfresco. Alfresco will use the unchanged values stored in the database."; + assertTrue("Warning message not found in log.", messages.anyMatch(message -> expectedMessage.equals(message))); + } + + @Test(expected = MissingConfiguration.class) + public void testInitConfiguredExemptionCategories_noCategoriesFound() + { + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())) + .thenReturn((Serializable) null); + when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(null); + + classificationServiceBootstrap.initConfiguredExemptionCategories(); + } + /** * Check that the initialise method creates a clearance level corresponding to each classification level and that * the display label for the lowest clearance level is "No Clearance" (rather than "Unclassified"). diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategoryManagerUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategoryManagerUnitTest.java new file mode 100644 index 0000000000..b85eda4811 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ExemptionCategoryManagerUnitTest.java @@ -0,0 +1,60 @@ +/* + * 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 org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; + +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.ExemptionCategoryIdNotFound; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for the {@link ExemptionCategoryManager}. + * + * @author tpage + */ +public class ExemptionCategoryManagerUnitTest +{ + private static final ExemptionCategory CATEGORY_1 = new ExemptionCategory("id1", "displayLabelKey1"); + private static final ExemptionCategory CATEGORY_2 = new ExemptionCategory("id2", "displayLabelKey2"); + private static final ExemptionCategory CATEGORY_3 = new ExemptionCategory("id3", "displayLabelKey3"); + private static final List CATEGORIES = Arrays.asList(CATEGORY_1, CATEGORY_2, CATEGORY_3); + + private ExemptionCategoryManager exemptionCategoryManager; + + @Before public void setup() + { + exemptionCategoryManager = new ExemptionCategoryManager(); + exemptionCategoryManager.setExemptionCategories(CATEGORIES); + } + + @Test public void findClassificationById_found() + { + ExemptionCategory actual = exemptionCategoryManager.findCategoryById("id2"); + assertEquals(CATEGORY_2, actual); + } + + @Test(expected = ExemptionCategoryIdNotFound.class) public void findClassificationById_notFound() + { + exemptionCategoryManager.findCategoryById("id_unknown"); + } +}