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 868d68578d..5a9b103c02 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 @@ -58,7 +58,7 @@ - + Cl + + + + + clf:classified + + + @@ -187,6 +195,7 @@ + @@ -234,15 +243,15 @@ class="org.alfresco.module.org_alfresco_module_rm.model.clf.aspect.ClassifiedAspect" parent="rm.baseBehaviour"> + - + - diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/metadata-referral-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/metadata-referral-context.xml new file mode 100644 index 0000000000..11cadeabbb --- /dev/null +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/metadata-referral-context.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService.attachReferrer=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService.detachReferrer=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService.getAttachedReferralsFrom=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService.getAttachedReferralFrom=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService.*=ACL_DENY + + + + + + + + + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.isReferringMetadata=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.getReferentNode=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.getReferredProperties=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.getReferredProperty=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.hasReferredAspect=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.getAttachedReferrals=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService.*=ACL_DENY + + + + + + + + + + + + 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 88b17a8445..8cefb0d377 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 @@ -38,11 +38,6 @@ type="org.alfresco.module.org_alfresco_module_rm.classification.ReclassificationValueConstraint" /> - - - - - @@ -178,5 +173,21 @@ + + + + + + true + true + + + cm:content + true + true + + + + \ No newline at end of file diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml index f75a9ff12d..e38c0e6e54 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml @@ -267,9 +267,12 @@ + + + - + 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 b58018e9b6..9123e6a800 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 @@ -23,11 +23,6 @@ import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.c import static org.alfresco.util.ParameterCheck.mandatory; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.io.Serializable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - import org.alfresco.model.ContentModel; import org.alfresco.model.QuickShareModel; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.InvalidNode; @@ -35,6 +30,7 @@ import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationE 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.freeze.FreezeService; +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; @@ -43,6 +39,11 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + /** * A service to handle the classification of content. * @@ -57,12 +58,17 @@ public class ContentClassificationServiceImpl extends ServiceBaseImpl private SecurityClearanceService securityClearanceService; private ClassificationServiceBootstrap classificationServiceBootstrap; private FreezeService freezeService; + private ReferredMetadataService referredMetadataService; public void setLevelManager(ClassificationLevelManager levelManager) { this.levelManager = levelManager; } public void setReasonManager(ClassificationReasonManager reasonManager) { this.reasonManager = reasonManager; } public void setSecurityClearanceService(SecurityClearanceService securityClearanceService) { this.securityClearanceService = securityClearanceService; } public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap) { this.classificationServiceBootstrap = classificationServiceBootstrap; } public void setFreezeService(FreezeService service) { this.freezeService = service; } + public void setReferredMetadataService(ReferredMetadataService service) + { + this.referredMetadataService = service; + } public void init() { @@ -80,16 +86,24 @@ public class ContentClassificationServiceImpl extends ServiceBaseImpl { public ClassificationLevel doWork() throws Exception { - // by default everything is unclassified - ClassificationLevel result = ClassificationLevelManager.UNCLASSIFIED; + final String classificationId; if (nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)) { - String classificationId = (String)nodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); - result = levelManager.findLevelById(classificationId); + classificationId = (String)nodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); + } + else if (referredMetadataService.isReferringMetadata(nodeRef, ASPECT_CLASSIFIED)) + { + classificationId = (String) referredMetadataService.getReferredProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); + // Note that this property value could be null/missing. + } + else + { + classificationId = null; } - return result; + // by default everything is unclassified + return classificationId == null ? ClassificationLevelManager.UNCLASSIFIED : levelManager.findLevelById(classificationId); } }); }; @@ -231,12 +245,19 @@ public class ContentClassificationServiceImpl extends ServiceBaseImpl mandatory("nodeRef", nodeRef); boolean isClassified = false; - String currentClassification = (String) nodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); - if (nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED) && - !(UNCLASSIFIED_ID).equals(currentClassification)) + final String currentClassification; + if (nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)) { - isClassified = true; + currentClassification = (String) nodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); + isClassified = currentClassification != null && ! UNCLASSIFIED_ID.equals(currentClassification); + } + else if (referredMetadataService.isReferringMetadata(nodeRef, ASPECT_CLASSIFIED)) + { + currentClassification = (String) referredMetadataService.getReferredProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); + // This could be a null/missing property. + + isClassified = currentClassification != null && ! UNCLASSIFIED_ID.equals(currentClassification); } return isClassified; 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 b509092f55..a6d89f6124 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 @@ -62,4 +62,8 @@ public interface ClassifiedContentModel /** Security Clearance aspect. */ QName ASPECT_SECURITY_CLEARANCE = QName.createQName(CLF_URI, "securityClearance"); QName PROP_CLEARANCE_LEVEL = QName.createQName(CLF_URI, "clearanceLevel"); + + /** Classified Rendition aspect. */ + QName ASPECT_CLASSIFIED_RENDITION = QName.createQName(CLF_URI, "classifiedRendition"); + QName ASSOC_CLASSIFIED_RENDITION = QName.createQName(CLF_URI, "classifiedRendition"); } \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditions.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditions.java index 48dff5c815..aec6c61e4b 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditions.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditions.java @@ -20,14 +20,15 @@ package org.alfresco.module.org_alfresco_module_rm.model.clf; import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService; import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; -import org.alfresco.module.org_alfresco_module_rm.util.CoreServicesExtras; 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.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -45,7 +46,7 @@ public class ClassifiedRenditions extends BaseBehaviourBean ClassifiedContentModel { private ContentClassificationService contentClassificationService; - private CoreServicesExtras servicesExtras; + private ReferralAdminService referralAdminService; private RenditionService renditionService; public void setContentClassificationService(ContentClassificationService service) @@ -53,9 +54,9 @@ public class ClassifiedRenditions extends BaseBehaviourBean this.contentClassificationService = service; } - public void setCoreServicesExtras(CoreServicesExtras extras) + public void setReferralAdminService(ReferralAdminService service) { - this.servicesExtras = extras; + this.referralAdminService = service; } public void setRenditionService(RenditionService service) @@ -74,16 +75,19 @@ public class ClassifiedRenditions extends BaseBehaviourBean ) public void onAddAspect(final NodeRef renditionNodeRef, final QName aspectTypeQName) { + // When a rendition is created, set up a metadata link of its classification to the source node. authenticationUtil.runAs(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork() { public Void doWork() { - final NodeRef sourceNode = renditionService.getSourceNode(renditionNodeRef).getParentRef(); - if (contentClassificationService.isClassified(sourceNode)) + final ChildAssociationRef chAssRef = renditionService.getSourceNode(renditionNodeRef); + final NodeRef sourceNode = chAssRef.getParentRef(); + if (contentClassificationService.isClassified(sourceNode) && + referralAdminService.getAttachedReferralFrom(renditionNodeRef, ASPECT_CLASSIFIED) != null) { - // All renditions should be given the same classification as their source node - servicesExtras.copyAspect(sourceNode, renditionNodeRef, ASPECT_CLASSIFIED); + referralAdminService.attachReferrer(renditionNodeRef, sourceNode, ASPECT_CLASSIFIED); } + return null; } }, authenticationUtil.getSystemUserName()); 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 index d226824d26..622e345eb3 100644 --- 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 @@ -20,17 +20,13 @@ package org.alfresco.module.org_alfresco_module_rm.model.clf.aspect; import static org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.diffKey; -import java.io.Serializable; -import java.util.Date; -import java.util.Map; - import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.MissingDowngradeInstructions; 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.ClassificationSchemeService.Reclassification; import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService; import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; -import org.alfresco.module.org_alfresco_module_rm.util.CoreServicesExtras; import org.alfresco.module.org_alfresco_module_rm.util.RMCollectionUtils.Difference; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; @@ -44,6 +40,11 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Map; + /** * clf:classification behaviour bean * @@ -55,34 +56,31 @@ import org.alfresco.service.namespace.QName; ) public class ClassifiedAspect extends BaseBehaviourBean implements NodeServicePolicies.OnUpdatePropertiesPolicy, NodeServicePolicies.OnAddAspectPolicy, + NodeServicePolicies.OnRemoveAspectPolicy, ClassifiedContentModel { private ClassificationSchemeService classificationSchemeService; + private ReferralAdminService referralAdminService; private RenditionService renditionService; - private CoreServicesExtras servicesExtras; public void setClassificationSchemeService(ClassificationSchemeService service) { this.classificationSchemeService = service; } + public void setReferralAdminService(ReferralAdminService service) + { + this.referralAdminService = service; + } + public void setRenditionService(RenditionService service) { this.renditionService = service; } - public void setCoreServicesExtras(CoreServicesExtras extras) - { - this.servicesExtras = extras; - } - /** * Behaviour associated with updating the classified aspect properties. *

- * 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. - *

* Validates the consistency of the properties. */ @Override @@ -91,7 +89,7 @@ public class ClassifiedAspect extends BaseBehaviourBean implements NodeServicePo kind = BehaviourKind.CLASS, notificationFrequency = NotificationFrequency.EVERY_EVENT ) - public void onUpdateProperties(final NodeRef nodeRef, + public void onUpdateProperties(final NodeRef classifiedNode, final Map before, final Map after) { @@ -101,7 +99,7 @@ public class ClassifiedAspect extends BaseBehaviourBean implements NodeServicePo { final Difference classificationChange = diffKey(before, after, PROP_CURRENT_CLASSIFICATION); - if (classificationChange == Difference.CHANGED && nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)) + if (classificationChange == Difference.CHANGED && nodeService.hasAspect(classifiedNode, ASPECT_CLASSIFIED)) { final String oldValue = (String)before.get(PROP_CURRENT_CLASSIFICATION); final String newValue = (String)after.get(PROP_CURRENT_CLASSIFICATION); @@ -113,14 +111,12 @@ public class ClassifiedAspect extends BaseBehaviourBean implements NodeServicePo if (reclassification != null) { - nodeService.setProperty(nodeRef, PROP_LAST_RECLASSIFICATION_ACTION, reclassification.toModelString()); - nodeService.setProperty(nodeRef, PROP_LAST_RECLASSIFY_AT, new Date()); + nodeService.setProperty(classifiedNode, PROP_LAST_RECLASSIFICATION_ACTION, reclassification.toModelString()); + nodeService.setProperty(classifiedNode, PROP_LAST_RECLASSIFY_AT, new Date()); } } - checkConsistencyOfProperties(nodeRef); - - copyClassifiedPropertiesToRenditions(nodeRef); + checkConsistencyOfProperties(classifiedNode); return null; } @@ -128,7 +124,7 @@ public class ClassifiedAspect extends BaseBehaviourBean implements NodeServicePo } /** - * Behaviour associated with updating the classified aspect properties. + * Behaviour associated with adding the classified aspect. *

* Validates the consistency of the properties. */ @@ -136,31 +132,62 @@ public class ClassifiedAspect extends BaseBehaviourBean implements NodeServicePo @Behaviour ( kind = BehaviourKind.CLASS, - notificationFrequency = NotificationFrequency.EVERY_EVENT + notificationFrequency = NotificationFrequency.FIRST_EVENT ) - public void onAddAspect(final NodeRef nodeRef, final QName aspectTypeQName) + public void onAddAspect(final NodeRef classifiedNode, final QName aspectTypeQName) { AuthenticationUtil.runAs(new RunAsWork() { public Void doWork() { - checkConsistencyOfProperties(nodeRef); + checkConsistencyOfProperties(classifiedNode); - copyClassifiedPropertiesToRenditions(nodeRef); + // If this node has any renditions, we must ensure that they inherit the classification + // from their source node. + final List renditions = renditionService.getRenditions(classifiedNode); + for (ChildAssociationRef chAssRef : renditions) + { + final NodeRef renditionNode = chAssRef.getChildRef(); + if (referralAdminService.getAttachedReferralFrom(renditionNode, ASPECT_CLASSIFIED) == null) + { + referralAdminService.attachReferrer(renditionNode, classifiedNode, ASPECT_CLASSIFIED); + } + } return null; } }, AuthenticationUtil.getSystemUserName()); } - private void copyClassifiedPropertiesToRenditions(NodeRef nodeRef) + /** + * Behaviour associated with removing the classified aspect. + *

+ * Validates the consistency of the properties. + */ + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS, + notificationFrequency = NotificationFrequency.FIRST_EVENT + ) + public void onRemoveAspect(final NodeRef classifiedNode, final QName aspectTypeQName) { - // All renditions should be given the same classification as their source node - for (final ChildAssociationRef chAssRef : renditionService.getRenditions(nodeRef)) + AuthenticationUtil.runAs(new RunAsWork() { - final NodeRef renditionNode = chAssRef.getChildRef(); - servicesExtras.copyAspect(nodeRef, renditionNode, ASPECT_CLASSIFIED); - } + public Void doWork() + { + // If this node has any renditions, we should remove the metadata link + final List renditions = renditionService.getRenditions(classifiedNode); + for (ChildAssociationRef chAssRef : renditions) + { + // In RM, renditions are only attached to one metadata referent - the source node. + // Therefore it is safe to (and we must) remove the aspect from the rendition node. + nodeService.removeAspect(chAssRef.getChildRef(), ASPECT_CLASSIFIED_RENDITION); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); } /** diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/MetadataReferral.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/MetadataReferral.java new file mode 100644 index 0000000000..2b739e6a23 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/MetadataReferral.java @@ -0,0 +1,137 @@ +/* + * 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.referredmetadata; + +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.InvalidMetadataReferral; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +/** + * A {@link MetadataReferral} is a definition of a {@link #aspects set of metadata} which are, in effect, shared + * between multiple nodes. + * Using a {@link MetadataReferral}, you can link two NodeRefs such that {@code hasAspect} and + * {@code getPropert[y|ies]} calls on one node can can be delegated to the other. In this way a defined set of + * metadata on one node can be made available for read access via another node. + *

+ * The connection between the nodes is made with a specified {@link #assocType peer association}. + *

+ * Note that a {@link MetadataReferral} is not an instance of a link between two nodes, but the definition of such a link. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class MetadataReferral +{ + private DictionaryService dictionaryService; + private ReferralRegistry referralRegistry; + private Set aspects; + private QName assocType; + + public MetadataReferral() + { + // Intentionally empty. + } + + public void setDictionaryService(DictionaryService service) + { + this.dictionaryService = service; + } + + public void setReferralRegistry(ReferralRegistry registry) + { + this.referralRegistry = registry; + } + + public void setAssocType(QName assocType) + { + this.assocType = assocType; + } + + public void setAspects(Set aspects) + { + this.aspects = aspects; + } + + public void validateAndRegister() + { + if (this.assocType == null) + { + throw new InvalidMetadataReferral("Illegal null assocType"); + } + if (aspects == null || aspects.isEmpty()) + { + throw new InvalidMetadataReferral("Illegal null or empty aspects set"); + } + if (dictionaryService.getAssociation(assocType) == null) + { + throw new InvalidMetadataReferral("Association not found: " + assocType); + } + for (QName aspect : aspects) + { + if (dictionaryService.getAspect(aspect) == null) + { + throw new InvalidMetadataReferral("Aspect not found: " + aspect); + } + } + + this.referralRegistry.register(this); + } + + /** Gets the type of the peer association linking the node to its delegate. */ + public QName getAssocType() + { + return assocType; + } + + /** Gets the set of aspects which are being referred. */ + public Set getAspects() + { + return Collections.unmodifiableSet(aspects); + } + + @Override public int hashCode() + { + return Objects.hash(aspects, assocType); + } + + @Override public boolean equals(Object other) + { + boolean result = false; + if (other instanceof MetadataReferral) + { + MetadataReferral that = (MetadataReferral)other; + result = this.aspects.equals(that.aspects) && + this.assocType.equals(that.assocType); + } + return result; + } + + @Override public String toString() + { + StringBuilder result = new StringBuilder(); + result.append(this.getClass().getSimpleName()).append(':') + .append("--").append(assocType).append("->") + .append(aspects); + return result.toString(); + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminService.java new file mode 100644 index 0000000000..52a94d7a09 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminService.java @@ -0,0 +1,93 @@ +/* + * 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.referredmetadata; + +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.ChainedMetadataReferralUnsupported; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +import java.util.Set; + +/** + * A service to manage the referral of aspect metadata. + * Using this service a node can be {@link #attachReferrer linked} to a referrer node for a specific set of aspects. + * (Note that this referrer node must already exist within the database.) + * Then any read request for relevant metadata such as hasAspect or getProperties can be delegated to the + * linked node. + *

+ * For a link to be made, there must be a {@link ReferralRegistry#getMetadataReferrals()} defined MetadataReferral} + * already in the system. + * This means that a peer-association type will have to have been declared and that a spring bean will have to have + * defined which aspects are to be handled by this {@link MetadataReferral}. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public interface ReferralAdminService +{ + /** + * Creates a link between two nodes such that the first {@code referrer} can 'inherit' or reuse some aspect + * metadata from another node - the {@code referrer}. + *

+ * Note that attaching a referrer for the specified aspect will also link the two nodes for + * all aspects defined in the {@link MetadataReferral}. + *

+ * Note that links can currently only extend between two pairs of nodes and cannot be chained. + * + * @param referrer the node which is to inherit additional metadata. + * @param referent the node which will provide the additional metadata. + * @param aspectName the name of the aspect whose metadata is to be attached. + * @return a {@link MetadataReferral} object which defines the link type. + * @throws ChainedMetadataReferralUnsupported if an attempt is made to attach nodes such that a chain would be made. + */ + MetadataReferral attachReferrer(NodeRef referrer, NodeRef referent, QName aspectName); + + /** + * Removes an existing metadata link between two nodes. + *

+ * Note that detaching a referrer for the specified aspect will also unlink the two nodes for + * all aspects defined in the {@link MetadataReferral}. + * + * @param referrer the node which has been linked to a metadata source. + * @param aspectName the name of the aspect whose metadata is to be detached. + * @return the removed {@link MetadataReferral}. + */ + MetadataReferral detachReferrer(NodeRef referrer, QName aspectName); // FIXME Chase all references + + /** + * Gets the set of {@link MetadataReferral}s which are currently applied from the specified {@code referrer}. + * From these, the types of peer associations which are linked to the specified + * {@code referrer} as well as the aspect types that are handled can be retrieved. + * + * @param referrer the NodeRef whose {@link MetadataReferral}s are sought. + * @return the set of {@link MetadataReferral}s from the specified referrer. + */ + Set getAttachedReferralsFrom(NodeRef referrer); + + /** + * Gets the {@link MetadataReferral} from the specified {@code referrer} for the specified {@code aspectName}, + * if there is one. + * + * @param referrer the node whose {@link MetadataReferral} is sought. + * @param aspectName the aspect name for which a {@link MetadataReferral} is sought. + * @return the {@link MetadataReferral} which is attached to the specified node if there is one, else {@code null}. + */ + MetadataReferral getAttachedReferralFrom(NodeRef referrer, QName aspectName); +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminServiceImpl.java new file mode 100644 index 0000000000..4aa8c4afbb --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminServiceImpl.java @@ -0,0 +1,195 @@ +/* + * 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.referredmetadata; + +import static org.alfresco.util.collections.CollectionUtils.transform; + +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.ChainedMetadataReferralUnsupported; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.collections.Function; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class ReferralAdminServiceImpl implements ReferralAdminService +{ + // Author's implementation note + // ---------------------------- + // + // I can imagine that these services would be potentially useful in core Alfresco. + // However they are not yet full services and couldn't be moved as is into core. + // They solve a very specific RM problem in a fairly generic way that should allow + // someone to use them as the basis for fuller services within core. + // + // The problem they solve is that of 'classified renditions' whereby the classification + // metadata on a node should appear to be on its renditions as well. This particular problem + // is simplified by the fact that renditions are not 'normal' nodes, as they are usually + // not accessed directly. This implementation also relies on the fact that RM already + // has interceptors for checking content classification and we can programmatically add + // the calls to metadata referral within the ContentClassificationService. + // + // To solve the problem of Metadata Referral in a general way would require the provision + // of 'MetadataReferral' interceptors that could sit in front of the NodeService. Only in this + // way could the services be used declaratively, thus minimising their impact on calling code. + // To add these to core would require careful assessment of their impact, not least in + // performance terms. This work is beyond RM's scope at this stage. + // The addition of such interceptors to the NodeService would also ensure that any metadata + // returned to e.g. Share for a particular node could automatically include 'linked' metadata + // which would be important. + // + // There are further enhancements that should be considered if these were developed into + // fuller services including the automatic registration of behaviours (onAddAspect, onRemoveAspect) + // for the aspect types which are linked. Currently these behaviours need to be hand-coded. + // See ClassifiedAspect.java for an example. + + private ReferralRegistry registry; + private NodeService nodeService; + + public void setNodeService(NodeService service) + { + this.nodeService = service; + } + + public void setReferralRegistry(ReferralRegistry registry) + { + this.registry = registry; + } + + @Override public MetadataReferral attachReferrer(NodeRef referrer, NodeRef referent, QName aspectName) + { + final MetadataReferral metadataReferral = registry.getReferralForAspect(aspectName); + if (metadataReferral == null) + { + throw new IllegalArgumentException("No defined " + MetadataReferral.class.getSimpleName() + + " for aspect " + aspectName); + } + final QName assocType = metadataReferral.getAssocType(); + + // Prevent the creation of chains of metadata linking from node A to B to C. + + // If any nodes are already linked to referrer for the specified assoc, then we can't chain. + final List existingReferrerAssocs = nodeService.getSourceAssocs(referrer, assocType); + if ( !existingReferrerAssocs.isEmpty()) + { + final List existingReferrers = transform(existingReferrerAssocs, + new Function() + { + @Override public NodeRef apply(AssociationRef assocRef) + { + return assocRef.getSourceRef(); + } + }); + throw new ChainedMetadataReferralUnsupported("Cannot attach referrer", existingReferrers); + } + + // Likewise if this referent node is already itself linked elsewhere, we cannot chain. + final List existingReferentAssocs = nodeService.getTargetAssocs(referent, assocType); + if ( !existingReferentAssocs.isEmpty()) + { + // If it's not empty, it should only have one value in it, but just in case... + final List existingReferents = transform(existingReferentAssocs, + new Function() + { + @Override public NodeRef apply(AssociationRef assocRef) + { + return assocRef.getTargetRef(); + } + }); + throw new ChainedMetadataReferralUnsupported("Cannot attach referent", existingReferents); + } + + // OK. We're good to go. We're not making a chain here. + nodeService.createAssociation(referrer, referent, assocType); + + return metadataReferral; + } + + /** Gets the {@link MetadataReferral} which uses the specified {@code assocType}. */ + private MetadataReferral getReferralForAssociation(QName assocType) + { + final MetadataReferral metadataReferral = registry.getReferralForAssociation(assocType); + + if (metadataReferral == null) + { + throw new IllegalArgumentException("No " + MetadataReferral.class.getSimpleName() + + " configured for assocType " + assocType); + } + return metadataReferral; + } + + @Override public MetadataReferral detachReferrer(NodeRef referrer, QName aspectName) + { + final MetadataReferral referral = registry.getReferralForAspect(aspectName); + final QName assocType = referral.getAssocType(); + + // Is the association there? + final List assocs = nodeService.getTargetAssocs(referrer, assocType); + + if (assocs == null || assocs.isEmpty()) + { + return null; + } + else + { + // There should only be one such association... but we'll remove them all just in case + for (AssociationRef assocRef : assocs) + { + nodeService.removeAssociation(referrer, assocRef.getTargetRef(), assocType); + } + + return referral; + } + } + + @Override public Set getAttachedReferralsFrom(NodeRef referrer) + { + final Set allMetadataReferrals = registry.getMetadataReferrals(); + + final Set result = new HashSet<>(); + for (MetadataReferral d : allMetadataReferrals) + { + final QName assocType = d.getAssocType(); + if ( !nodeService.getTargetAssocs(referrer, assocType).isEmpty()) + { + result.add(d); + } + } + + return result; + } + + @Override public MetadataReferral getAttachedReferralFrom(NodeRef referrer, QName aspectName) + { + final Set allMetadataReferrals = getAttachedReferralsFrom(referrer); + + for (MetadataReferral d : allMetadataReferrals) + { + if (d.getAspects().contains(aspectName)) return d; + } + return null; + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralRegistry.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralRegistry.java new file mode 100644 index 0000000000..653e2e7790 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralRegistry.java @@ -0,0 +1,109 @@ +/* + * 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.referredmetadata; + +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.MetadataReferralAlreadyExists; +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.InvalidMetadataReferral; +import org.alfresco.service.namespace.QName; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * This is a registry of {@link MetadataReferral}s which have been defined in the system. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class ReferralRegistry +{ + private final Set metadataReferrals = new HashSet<>(); + + public void register(MetadataReferral metadataReferral) + { + // Various validation steps to do here to ensure we get consistent, sensible referrals registered. + if (metadataReferrals.contains(metadataReferral)) + { + throw new MetadataReferralAlreadyExists("Cannot register duplicate referral", metadataReferral); + } + for (MetadataReferral existingMetadataReferral : metadataReferrals) + { + if (existingMetadataReferral.getAssocType().equals(metadataReferral.getAssocType())) + { + throw new InvalidMetadataReferral("Cannot register two referrals with the same assocType. " + + "Existing: " + existingMetadataReferral + + " New: " + metadataReferral); + } + // Yes this is a for loop inside a for loop but we're assuming these sets will not be large. + for (QName existingAspect : existingMetadataReferral.getAspects()) + { + if (metadataReferral.getAspects().contains(existingAspect)) + { + throw new InvalidMetadataReferral("Cannot register two referrals with the same aspect. " + + "Existing: " + existingMetadataReferral + + " New: " + metadataReferral); + } + } + } + + this.metadataReferrals.add(metadataReferral); + } + + public Set getMetadataReferrals() + { + return Collections.unmodifiableSet(metadataReferrals); + } + + /** + * Gets the {@link MetadataReferral} which is defined to use the specified {@code assocType}. + * + * @param assocType the peer association type whose {@link MetadataReferral} is sought. + * @return the {@link MetadataReferral} defined to use the specified {@code assocType} if there is one, else {@code null}. + */ + public MetadataReferral getReferralForAssociation(QName assocType) + { + for (MetadataReferral mr : metadataReferrals) + { + if (mr.getAssocType().equals(assocType)) + { + return mr; + } + } + return null; + } + + /** + * Gets the {@link MetadataReferral} which is defined to handle the specified aspect. + * + * @param aspectName the name of the aspect whose {@link MetadataReferral} is sought. + * @return the {@link MetadataReferral} handling the specified aspect if there is one, else {@code null}. + */ + public MetadataReferral getReferralForAspect(QName aspectName) + { + for (MetadataReferral mr : metadataReferrals) + { + if (mr.getAspects().contains(aspectName)) + { + return mr; + } + } + return null; + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataException.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataException.java new file mode 100644 index 0000000000..83cbb83ed1 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataException.java @@ -0,0 +1,109 @@ +/* + * 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.referredmetadata; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.NodeRef; + +import java.util.List; + +/** + * Generic class for any runtime exceptions related to metadata referrals. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class ReferredMetadataException extends AlfrescoRuntimeException +{ + public ReferredMetadataException(String msgId) { super(msgId); } + public ReferredMetadataException(String msgId, Throwable cause) { super(msgId, cause); } + + /** This exception may be thrown when a {@link MetadataReferral} was incorrectly initialised. */ + public static class InvalidMetadataReferral extends ReferredMetadataException + { + public InvalidMetadataReferral(String msgId) + { + super(msgId); + } + } + + /** This exception may be thrown when a {@link MetadataReferral} already exists. */ + public static class MetadataReferralAlreadyExists extends ReferredMetadataException + { + private final MetadataReferral metadataReferral; + + public MetadataReferralAlreadyExists(String msgId, MetadataReferral metadataReferral) + { + super(msgId); + this.metadataReferral = metadataReferral; + } + } + + /** A {@link MetadataReferral} has not been found. */ + public static class MetadataReferralNotFound extends ReferredMetadataException + { + public MetadataReferralNotFound(String msgId) + { + super(msgId); + } + } + + /** A referent Node has not been found. */ + public static class ReferentNodeNotFound extends ReferredMetadataException + { + public ReferentNodeNotFound(String msgId) + { + super(msgId); + } + } + + /** Exception to report that chains of metadata referral are not currently supported. */ + public static class ChainedMetadataReferralUnsupported extends ReferredMetadataException + { + private final List existingReferrers; + + public ChainedMetadataReferralUnsupported(String msgId, List existingReferrers) + { + super(msgId); + this.existingReferrers = existingReferrers; + } + + public List getExistingReferrers() + { + return this.existingReferrers; + } + + @Override public String toString() + { + StringBuilder msg = new StringBuilder(); + msg.append(this.getClass().getSimpleName()).append(" Already referring from: ") + .append(existingReferrers.toString()); + return msg.toString(); + } + } + + /** Exception to report that metadata referral is not supported for metadata defined on content types. */ + public static class TypeMetadataReferralUnsupported extends ReferredMetadataException + { + public TypeMetadataReferralUnsupported(String msgId) + { + super(msgId); + } + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataService.java new file mode 100644 index 0000000000..77d234dbfe --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataService.java @@ -0,0 +1,102 @@ +/* + * 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.referredmetadata; + +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.MetadataReferralNotFound; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.TypeMetadataReferralUnsupported; + +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +import java.io.Serializable; +import java.util.Map; + +/** + * This service provides read-only access to linked metadata. It is primarily concerned with data transfer. + * For an overview, see the package javadoc. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public interface ReferredMetadataService +{ + /** + * Checks if the specified referrer has an attached {@link MetadataReferral} for the specified aspect. + * + * @param potentialReferrer the referrer which may or may not be linked to a referent node. + * @param aspectName the name of the aspect. + * @return whether the node is linked to a referent node for the specified aspect. + * @throws InvalidNodeRefException if the supplied referrer does not exist. + * @throws MetadataReferralNotFound if no {@link MetadataReferral} is defined for the specified aspect. + */ + boolean isReferringMetadata(NodeRef potentialReferrer, QName aspectName); + + /** + * Gets the referent node for the specified aspect, if there is one. + * + * @param referrer the node whose referent is sought. + * @param aspectName the aspect name. + * @return the referent of the provided referrer if there is one, else {@code null}. + * @throws InvalidNodeRefException if the supplied referrer does not exist. + * @throws MetadataReferralNotFound if no {@link MetadataReferral} is defined for the specified aspect. + */ + NodeRef getReferentNode(NodeRef referrer, QName aspectName); + + /** + * Gets all the property values from the referent node for the specified aspect. + * + * @param referrer the referring node. + * @param aspectName the aspect name which holds the properties we want. + * @return the property values as obtained from the referent node. + */ + Map getReferredProperties(NodeRef referrer, QName aspectName); + + /** + * Gets the specified property value from the referent node. + * + * @param referrer the referring node. + * @param propertyName the property name whose value is sought. + * @return the property value as obtained from the referent node. + * @throws IllegalArgumentException if the specified property is not defined. + * @throws TypeMetadataReferralUnsupported if the specified property is not defined on an aspect. + */ + Serializable getReferredProperty(NodeRef referrer, QName propertyName); + + /** + * Determines if the specified aspect is present on a node's referent. + * + * @param referrer the referring node. + * @param aspectName the aspect which is to be checked on the referent node. + * @return Returns true if the aspect has been applied to the referent node, + * otherwise false + */ + boolean hasReferredAspect(NodeRef referrer, QName aspectName); + + /** + * Gets all {@link MetadataReferral referrals} currently attached to the specified node. + * + * @param potentialReferrer the node whose attached {@link MetadataReferral referrals} are sought. + * @return Returns a map of all attached {@link MetadataReferral referrals} for the specified nodeRef. + * The map has the form {@code (key, value) = (MetadataReferral, referent Node for that Referral)} + * The map may be empty but will not be {@code null}. + */ + Map getAttachedReferrals(NodeRef potentialReferrer); +} + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataServiceImpl.java new file mode 100644 index 0000000000..fe9eaca755 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataServiceImpl.java @@ -0,0 +1,195 @@ +/* + * 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.referredmetadata; + +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.MetadataReferralNotFound; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.ReferentNodeNotFound; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.TypeMetadataReferralUnsupported; +import static org.alfresco.util.collections.CollectionUtils.filterKeys; + +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.collections.Function; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class ReferredMetadataServiceImpl implements ReferredMetadataService +{ + private ReferralAdminService referralAdminService; + private ReferralRegistry referralRegistry; + private DictionaryService dictionaryService; + private NodeService nodeService; + + public void setReferralAdminService(ReferralAdminService service) + { + this.referralAdminService = service; + } + + public void setReferralRegistry(ReferralRegistry registry) + { + this.referralRegistry = registry; + } + + public void setDictionaryService(DictionaryService service) + { + this.dictionaryService = service; + } + + public void setNodeService(NodeService service) + { + this.nodeService = service; + } + + @Override public boolean isReferringMetadata(NodeRef potentialReferrer, QName aspectName) + { + if ( !nodeService.exists(potentialReferrer)) + { + throw new InvalidNodeRefException(potentialReferrer); + } + + final MetadataReferral metadataReferral = referralRegistry.getReferralForAspect(aspectName); + + if (metadataReferral == null) + { + throw new MetadataReferralNotFound("No defined referral found for aspect: " + aspectName); + } + else + { + final List targetAssocs = nodeService.getTargetAssocs(potentialReferrer, metadataReferral.getAssocType()); + return !targetAssocs.isEmpty(); + } + } + + @Override public NodeRef getReferentNode(NodeRef referrer, QName aspectName) + { + if ( !nodeService.exists(referrer)) + { + throw new InvalidNodeRefException(referrer); + } + + final MetadataReferral d = referralRegistry.getReferralForAspect(aspectName); + + if (d == null) + { + throw new MetadataReferralNotFound("No defined referral found for aspect: " + aspectName); + } + else + { + final QName assocType = d.getAssocType(); + final List assocs = nodeService.getTargetAssocs(referrer, assocType); + + return assocs.isEmpty() ? null : assocs.get(0).getTargetRef(); + } + } + + @Override public Map getReferredProperties(NodeRef referrer, final QName aspectName) + { + final NodeRef referentNode = getReferentNode(referrer, aspectName); + + if (referentNode == null) + { + throw new ReferentNodeNotFound("No referent node found for " + referrer + " " + aspectName); + } + else + { + final Map allProps = nodeService.getProperties(referentNode); + final Map aspectProps = filterKeys(allProps, + new Function() + { + @Override public Boolean apply(QName propName) + { + final QName containerClassname = dictionaryService.getProperty(propName) + .getContainerClass() + .getName(); + return containerClassname.equals(aspectName); + } + }); + return aspectProps; + } + } + + @Override public Serializable getReferredProperty(NodeRef referrer, QName propertyName) + { + final PropertyDefinition propDefn = dictionaryService.getProperty(propertyName); + + if (propDefn == null) + { + throw new IllegalArgumentException("Property " + propertyName + " not found."); + } + + final ClassDefinition aspectDefn = propDefn.getContainerClass(); + if (!aspectDefn.isAspect()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Property '").append(propertyName).append("' is not defined on an aspect: ") + .append(aspectDefn.getName()); + + throw new TypeMetadataReferralUnsupported(msg.toString()); + } + + final Map allPropValues = getReferredProperties(referrer, aspectDefn.getName()); + return allPropValues.get(propertyName); + } + + @Override public boolean hasReferredAspect(NodeRef referrer, QName aspectName) + { + final NodeRef referentNode = getReferentNode(referrer, aspectName); + + if (referentNode == null) + { + throw new ReferentNodeNotFound("No referent node found for " + referrer + " " + aspectName); + } + else + { + return nodeService.hasAspect(referentNode, aspectName); + } + } + + @Override public Map getAttachedReferrals(NodeRef potentialReferrer) + { + Set metadataReferrals = referralAdminService.getAttachedReferralsFrom(potentialReferrer); + + Map result = new HashMap<>(); + for (MetadataReferral mr : metadataReferrals) + { + // We need only use the first aspect to get the MetadataReferral object + if (!mr.getAspects().isEmpty()) + { + result.put(mr, getReferentNode(potentialReferrer, mr.getAspects().iterator().next())); + } + } + + return result; + } +} + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/package-info.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/package-info.java new file mode 100644 index 0000000000..7a1a00ae73 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/package-info.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 . + */ +/** + * This package contains the types that deliver the Metadata Referral feature. + * Metadata referral allows node metadata to be shared between multiple Alfresco nodes. + *

+ * In this way nodes can 'inherit' some of their metadata from another node which may + * have benefits when more than one node is required to share some of the same metadata. + *

+ * Only aspect metadata can be shared and it is only shared as read-only data to the other nodes. + * The node which contains the metadata values is the 'referent' node and any nodes which have been + * linked to the referent and share the metadata are known as referrers. + *

+ * Multiple nodes may share the same referent node and one node may be linked to multiple referrers. + *

+ * The linking of nodes to their metadata referents is done with Alfresco peer associations. + * Association types must be declared in an Alfresco content model in the normal way. + * Spring configuration is used to assign each association type a set of aspects which will + * be available from the referent via the association. + *

+ * See {@link org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService} + * for details on how to create and destroy metadata links between nodes. + *

+ * See {@link org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService} + * for details on how the data access is performed. + *

+ * See {@link org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralRegistry} + * for details on what {@link org.alfresco.module.org_alfresco_module_rm.referredmetadata.MetadataReferral}s + * are defined in the system. + */ +package org.alfresco.module.org_alfresco_module_rm.referredmetadata; \ No newline at end of file diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java index c7cf727359..86a61ec760 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java @@ -45,6 +45,7 @@ import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationE import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.LevelIdNotFound; import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService; +import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService; import org.alfresco.module.org_alfresco_module_rm.test.util.MockAuthenticationUtilHelper; import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -80,6 +81,7 @@ public class ContentClassificationServiceImplUnitTest implements ClassifiedConte @Mock FreezeService mockFreezeService; @Mock SecurityClearanceService mockSecurityClearanceService; @Mock AuthenticationUtil mockAuthenticationUtil; + @Mock ReferredMetadataService mockReferredMetadataService; @Mock ClassificationAspectProperties mockPropertiesDTO; @Captor ArgumentCaptor> propertiesCaptor; diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditionsUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditionsUnitTest.java deleted file mode 100644 index 69ce5d7086..0000000000 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/ClassifiedRenditionsUnitTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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; - -import static java.util.Arrays.asList; - -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - -import org.alfresco.model.RenditionModel; -import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; -import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; -import org.alfresco.module.org_alfresco_module_rm.model.clf.aspect.ClassifiedAspect; -import org.alfresco.module.org_alfresco_module_rm.test.util.MockAuthenticationUtilHelper; -import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; -import org.alfresco.module.org_alfresco_module_rm.util.CoreServicesExtras; -import org.alfresco.service.cmr.rendition.RenditionService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -/** - * Unit tests for {@link ClassifiedRenditions}. - * - * @since 2.4.a - */ -public class ClassifiedRenditionsUnitTest implements ClassifiedContentModel -{ - private static final NodeRef SOURCE_NODE = new NodeRef("node://ref/"); - private static final NodeRef RENDITION_1 = new NodeRef("node://rendition1/"); - private static final NodeRef RENDITION_2 = new NodeRef("node://rendition2/"); - - @InjectMocks ClassifiedAspect classifiedAspect; - - @Mock AuthenticationUtil mockAuthenticationUtil; - @Mock ContentClassificationService mockContentClassificationService; - @Mock CoreServicesExtras mockCoreServicesExtras; - @Mock NodeService mockNodeService; - @Mock RenditionService mockRenditionService; - - @Before - public void setUp() - { - initMocks(this); - - MockAuthenticationUtilHelper.setup(mockAuthenticationUtil); - } - - @Test public void newRenditionOfClassifiedNodeShouldItselfBeClassified() - { - when(mockRenditionService.getRenditions(SOURCE_NODE)) - .thenReturn(asList(rendition(SOURCE_NODE, RENDITION_1), rendition(SOURCE_NODE, RENDITION_2))); - when(mockRenditionService.getSourceNode(RENDITION_1)).thenReturn(rendition(SOURCE_NODE, RENDITION_1)); - when(mockRenditionService.getSourceNode(RENDITION_2)).thenReturn(rendition(SOURCE_NODE, RENDITION_2)); - when(mockContentClassificationService.isClassified(SOURCE_NODE)).thenReturn(true); - - final ClassifiedRenditions behaviour = new ClassifiedRenditions(); - behaviour.setAuthenticationUtil(mockAuthenticationUtil); - behaviour.setContentClassificationService(mockContentClassificationService); - behaviour.setCoreServicesExtras(mockCoreServicesExtras); - behaviour.setNodeService(mockNodeService); - behaviour.setRenditionService(mockRenditionService); - - behaviour.onAddAspect(RENDITION_2, RenditionModel.ASPECT_RENDITION); - - verify(mockCoreServicesExtras).copyAspect(SOURCE_NODE, RENDITION_2, ClassifiedContentModel.ASPECT_CLASSIFIED); - } - - /** Creates a test Rendition ChildAssociationRef. */ - private ChildAssociationRef rendition(NodeRef source, NodeRef rendition) - { - return new ChildAssociationRef(RenditionModel.ASSOC_RENDITION, source, RenditionModel.ASSOC_RENDITION, rendition); - } -} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspectUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspectUnitTest.java index 7259b0bf6b..0d2729b503 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspectUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/clf/aspect/ClassifiedAspectUnitTest.java @@ -19,37 +19,32 @@ package org.alfresco.module.org_alfresco_module_rm.model.clf.aspect; import static java.util.Arrays.asList; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - import org.alfresco.model.RenditionModel; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.MissingDowngradeInstructions; -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.referredmetadata.ReferralAdminService; import org.alfresco.module.org_alfresco_module_rm.util.CoreServicesExtras; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import java.util.Date; + /** * Unit tests for the {@link ClassifiedAspect}. * * @author Tom Page + * @author Neil Mc Erlean * @since 2.4.a */ public class ClassifiedAspectUnitTest implements ClassifiedContentModel @@ -57,14 +52,13 @@ public class ClassifiedAspectUnitTest implements ClassifiedContentModel private static final NodeRef NODE_REF = new NodeRef("node://Ref/"); private static final NodeRef RENDITION_1 = new NodeRef("node://rendition1/"); private static final NodeRef RENDITION_2 = new NodeRef("node://rendition2/"); - private static final ClassificationLevel TOP_SECRET = new ClassificationLevel("Top Secret", "Top Secret"); - private static final ClassificationLevel SECRET = new ClassificationLevel("Secret", "Secret"); @InjectMocks ClassifiedAspect classifiedAspect; @Mock ClassificationSchemeService mockClassificationSchemeService; @Mock CoreServicesExtras mockCoreServicesExtras; @Mock NodeService mockNodeService; @Mock RenditionService mockRenditionService; + @Mock ReferralAdminService mockReferralAdminService; @Before public void setUp() @@ -135,50 +129,20 @@ public class ClassifiedAspectUnitTest implements ClassifiedContentModel classifiedAspect.checkConsistencyOfProperties(NODE_REF); } - /** Check that when a node is classified, its renditions are also classified. */ - @Test public void classificationOfNodeShouldClassifyRenditions() + @Test public void newlyClassifiedNodeShouldLinkItsMetadataToAllRenditions() { - for (NodeRef n : asList(NODE_REF, RENDITION_1, RENDITION_2)) - { - when(mockNodeService.hasAspect(n, ASPECT_CLASSIFIED)).thenReturn(true); - } - when(mockClassificationSchemeService.getClassificationLevelById(eq("Top Secret"))).thenReturn(TOP_SECRET); - when(mockClassificationSchemeService.getClassificationLevelById(eq("Secret"))).thenReturn(SECRET); - when(mockClassificationSchemeService.getReclassification(any(), any())).thenReturn(ClassificationSchemeService.Reclassification.DOWNGRADE); - when(mockRenditionService.getRenditions(eq(NODE_REF))) + when(mockRenditionService.getRenditions(NODE_REF)) .thenReturn(asList(rendition(NODE_REF, RENDITION_1), rendition(NODE_REF, RENDITION_2))); + for (final NodeRef rendition : asList(RENDITION_1, RENDITION_2)) + { + when(mockRenditionService.getSourceNode(rendition)).thenReturn(rendition(NODE_REF, rendition)); + } classifiedAspect.onAddAspect(NODE_REF, ASPECT_CLASSIFIED); for (NodeRef rendition : asList(RENDITION_1, RENDITION_2)) { - verify(mockCoreServicesExtras).copyAspect(NODE_REF, rendition, ClassifiedContentModel.ASPECT_CLASSIFIED); - } - } - - @Test public void reclassificationOfNodeShouldReclassifyRenditions() - { - for (NodeRef n : asList(NODE_REF, RENDITION_1, RENDITION_2)) - { - when(mockNodeService.hasAspect(n, ASPECT_CLASSIFIED)).thenReturn(true); - } - when(mockClassificationSchemeService.getClassificationLevelById("Top Secret")).thenReturn(TOP_SECRET); - when(mockClassificationSchemeService.getClassificationLevelById("Secret")).thenReturn(SECRET); - when(mockClassificationSchemeService.getReclassification(any(), any())).thenReturn(ClassificationSchemeService.Reclassification.DOWNGRADE); - when(mockRenditionService.getRenditions(eq(NODE_REF))) - .thenReturn(asList(rendition(NODE_REF, RENDITION_1), rendition(NODE_REF, RENDITION_2))); - - Map oldProps = new HashMap<>(); - oldProps.put(PROP_CLASSIFIED_BY, "userone"); - oldProps.put(PROP_CURRENT_CLASSIFICATION, "Top Secret"); - Map newProps = new HashMap<>(oldProps); - newProps.put(PROP_CURRENT_CLASSIFICATION, "Secret"); - - classifiedAspect.onUpdateProperties(NODE_REF, oldProps, newProps); - - for (NodeRef rendition : asList(RENDITION_1, RENDITION_2)) - { - verify(mockCoreServicesExtras).copyAspect(NODE_REF, rendition, ClassifiedContentModel.ASPECT_CLASSIFIED); + verify(mockReferralAdminService).attachReferrer(rendition, NODE_REF, ASPECT_CLASSIFIED); } } diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/MetadataReferralUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/MetadataReferralUnitTest.java new file mode 100644 index 0000000000..ea942e7272 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/MetadataReferralUnitTest.java @@ -0,0 +1,120 @@ +/* + * 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.referredmetadata; + +import static java.util.Collections.emptySet; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.InvalidMetadataReferral; +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.expectedException; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asListFrom; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link MetadataReferral}. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class MetadataReferralUnitTest +{ + @Mock DictionaryService mockDictionaryService; + @Mock NodeService mockNodeService; + + private final ReferralAdminServiceImpl referralAdminService = new ReferralAdminServiceImpl(); + + private final QName aspect1 = QName.createQName("test", "aspect1"); + private final QName aspect2 = QName.createQName("test", "aspect2"); + private final QName assoc1 = QName.createQName("test", "assoc1"); + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + + referralAdminService.setNodeService(mockNodeService); + } + + @Test public void nullOrEmptyReferralsAreForbidden() + { + asListFrom(() -> new MetadataReferral(), + () -> { + MetadataReferral mr = new MetadataReferral(); + mr.setAssocType(assoc1); + mr.setAspects(null); + mr.setDictionaryService(mockDictionaryService); + return mr; + }, + () -> { + MetadataReferral mr = new MetadataReferral(); + mr.setAssocType(assoc1); + mr.setAspects(emptySet()); + mr.setDictionaryService(mockDictionaryService); + return mr; + }, + () -> { + MetadataReferral mr = new MetadataReferral(); + mr.setAssocType(null); + mr.setAspects(asSet(aspect1, aspect2)); + mr.setDictionaryService(mockDictionaryService); + return mr; + }) + .forEach(mr -> expectedException(InvalidMetadataReferral.class, () -> { + mr.validateAndRegister(); + return null; + }) + ); + } + + @Test(expected=InvalidMetadataReferral.class) + public void referralMustHaveAssocThatExists() + { + when(mockDictionaryService.getAssociation(assoc1)).thenReturn(null); + when(mockDictionaryService.getAspect(aspect1)).thenReturn(mock(AspectDefinition.class)); + + MetadataReferral mr = new MetadataReferral(); + mr.setAssocType(assoc1); + mr.setAspects(asSet(aspect1)); + mr.setDictionaryService(mockDictionaryService); + mr.validateAndRegister(); + } + + @Test(expected=InvalidMetadataReferral.class) + public void referralMustHaveAspectsAllOfWhichExist() + { + when(mockDictionaryService.getAssociation(assoc1)).thenReturn(mock(AssociationDefinition.class)); + when(mockDictionaryService.getAspect(aspect1)).thenReturn(mock(AspectDefinition.class)); + when(mockDictionaryService.getAspect(aspect2)).thenReturn(null); + + MetadataReferral mr = new MetadataReferral(); + mr.setAssocType(assoc1); + mr.setAspects(asSet(aspect1, aspect2)); + mr.setDictionaryService(mockDictionaryService); + mr.validateAndRegister(); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminServiceImplUnitTest.java new file mode 100644 index 0000000000..f5388c9f06 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferralAdminServiceImplUnitTest.java @@ -0,0 +1,142 @@ +/* + * 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.referredmetadata; + +import static java.util.Arrays.asList; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.ChainedMetadataReferralUnsupported; +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.expectedException; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ReferralAdminServiceImpl}. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class ReferralAdminServiceImplUnitTest +{ + @InjectMocks private final ReferralAdminServiceImpl referralAdminService = new ReferralAdminServiceImpl(); + + @Mock DictionaryService mockDictionaryService; + @Mock NodeService mockNodeService; + @Mock ReferralRegistry mockRegistry; + @Mock ReferredMetadataServiceImpl mockReferredMetadataService; + + private final NodeRef node1 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node1"); + private final NodeRef node2 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node2"); + private final NodeRef node3 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node3"); + + private final QName assoc1 = QName.createQName("test", "assoc1"); + private final QName aspect1 = QName.createQName("test", "aspect1"); + private final QName aspect2 = QName.createQName("test", "aspect2"); + + private final QName assoc2 = QName.createQName("test", "assoc2"); + private final QName aspect3 = QName.createQName("test", "aspect3"); + + private final MetadataReferral referral1 = new MetadataReferral() + {{ + this.setAssocType(assoc1); + this.setAspects(asSet(aspect1, aspect2)); + }}; + private final MetadataReferral referral2 = new MetadataReferral() + {{ + this.setAssocType(assoc2); + this.setAspects(asSet(aspect3)); + }}; + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + + when(mockRegistry.getMetadataReferrals()).thenReturn(asSet(referral1, referral2)); + } + + @Test(expected=IllegalArgumentException.class) + public void attachingReferrerWithNoAspectConfiguredShouldFail() + { + referralAdminService.attachReferrer(node2, node1, aspect1); + } + + @Test public void attachDetach() + { + when(mockRegistry.getReferralForAspect(aspect1)).thenReturn(referral1); + + // attach + MetadataReferral d = attachReferrer(node1, node2, aspect1); + + // validate + assertEquals(assoc1, d.getAssocType()); + assertEquals(asSet(aspect1, aspect2), d.getAspects()); + assertTrue(mockReferredMetadataService.isReferringMetadata(node1, aspect1)); + assertFalse(mockReferredMetadataService.isReferringMetadata(node1, aspect3)); + + // detach + assertEquals(d, referralAdminService.detachReferrer(node1, aspect1)); + } + + private MetadataReferral attachReferrer(NodeRef referrer, NodeRef referent, QName aspectName) + { + MetadataReferral mr = referralAdminService.attachReferrer(referrer, referent, aspectName); + final QName assocType = mr.getAssocType(); + when(mockNodeService.getSourceAssocs(referent, assocType)).thenReturn(asList(new AssociationRef(referrer, assocType, referent))); + when(mockNodeService.getTargetAssocs(referrer, assocType)).thenReturn(asList(new AssociationRef(referrer, assocType, referent))); + for (QName aspect : mr.getAspects()) + { + when(mockReferredMetadataService.isReferringMetadata(referrer, aspect)).thenReturn(true); + } + return mr; + } + + @Test public void chainsOfDelegationShouldBePrevented() + { + when(mockRegistry.getReferralForAspect(aspect1)).thenReturn(referral1); + + // The node already has a delegation in place: node1 -> node2. We're trying to add to the + // end of the chain: node2 -> node3 + when(mockNodeService.getSourceAssocs(node2, assoc1)).thenReturn(asList(new AssociationRef(node1, assoc1, node2))); + when(mockNodeService.getTargetAssocs(node1, assoc1)).thenReturn(asList(new AssociationRef(node1, assoc1, node2))); + + expectedException(ChainedMetadataReferralUnsupported.class, () -> { + referralAdminService.attachReferrer(node2, node3, aspect1); + return null; + }); + + // Now try to add to the start of the chain: node3 -> node1 + expectedException(ChainedMetadataReferralUnsupported.class, () -> { + referralAdminService.attachReferrer(node3, node1, aspect1); + return null; + }); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataServiceImplUnitTest.java new file mode 100644 index 0000000000..0a818176ce --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/referredmetadata/ReferredMetadataServiceImplUnitTest.java @@ -0,0 +1,196 @@ +/* + * 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.referredmetadata; + +import static java.util.Collections.emptyMap; +import static java.util.Arrays.asList; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.ReferentNodeNotFound; +import static org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataException.MetadataReferralNotFound; +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.expectedException; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * Unit tests for {@link ReferredMetadataServiceImpl}. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class ReferredMetadataServiceImplUnitTest +{ + @InjectMocks private final ReferredMetadataServiceImpl referredMetadataService = new ReferredMetadataServiceImpl(); + + @Mock DictionaryService mockDictionaryService; + @Mock NodeService mockNodeService; + @Mock ReferralAdminServiceImpl mockReferralAdminService; + @Mock ReferralRegistry mockReferralRegistry; + + /** This node has a referent node. */ + private final NodeRef referringNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "referringNode"); + /** This is the referent for {@link #referringNode}. */ + private final NodeRef referentNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "referentNode"); + /** This node has no referent node. */ + private final NodeRef nodeWithoutReferent = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "nodeWithoutReferent"); + + /** The type of the peer association that links the referringNode to its source. */ + private final QName referralAssocType = QName.createQName("test", "referralAssocType"); + /** The instance of the association between {@link #referringNode} and {@link #referentNode}. */ + private final AssociationRef attachedReferralAssocRef = new AssociationRef(referringNode, referralAssocType, referentNode); + + /** Name of an aspect that has been referred. */ + private final QName referredAspect1 = QName.createQName("test", "referredAspect1"); + /** Name of an aspect that has been referred. */ + private final QName referredAspect2 = QName.createQName("test", "referredAspect2"); + /** Name of a content class (a type in this case) that has not been referred. + * N.B. Types can't be referred currently. */ + private final QName unreferredType = QName.createQName("test", "unreferredType"); + + private final QName referredProp = QName.createQName("test", "referredProp"); + private final Serializable referredPropValue = "hello"; + private final QName unreferredProp = QName.createQName("test", "unreferredProp"); + + private final MetadataReferral referral = new MetadataReferral() + {{ + this.setAssocType(referralAssocType); + this.setAspects(asSet(referredAspect1, referredAspect2)); + }}; + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + + final PropertyDefinition aspectProp = mock(PropertyDefinition.class); + final ClassDefinition aspectDefn = mock(ClassDefinition.class); + when(aspectDefn.getName()).thenReturn(referredAspect1); + when(aspectProp.getContainerClass()).thenReturn(aspectDefn); + when(aspectDefn.isAspect()).thenReturn(true); + + final PropertyDefinition typeProp = mock(PropertyDefinition.class); + final ClassDefinition typeDefn = mock(ClassDefinition.class); + when(typeDefn.getName()).thenReturn(unreferredType); + when(typeProp.getContainerClass()).thenReturn(typeDefn); + when(typeDefn.isAspect()).thenReturn(false); + + when(mockDictionaryService.getProperty(referredProp)).thenReturn(aspectProp); + + when(mockReferralAdminService.getAttachedReferralsFrom(referringNode)).thenReturn(asSet(referral)); + for (QName referredAspect : asSet(referredAspect1, referredAspect2)) + { + when(mockReferralRegistry.getReferralForAspect(referredAspect)).thenReturn(referral); + when(mockNodeService.hasAspect(referentNode, referredAspect)).thenReturn(true); + } + when(mockNodeService.getSourceAssocs(referentNode, referralAssocType)).thenReturn(asList(attachedReferralAssocRef)); + when(mockNodeService.getTargetAssocs(referringNode, referralAssocType)).thenReturn(asList(attachedReferralAssocRef)); + when(mockNodeService.exists(any(NodeRef.class))).thenReturn(true); + when(mockNodeService.getProperties(referentNode)) + .thenReturn(new HashMap() + {{ + this.put(referredProp, referredPropValue); + }}); + } + + @Test public void isReferringMetadata() + { + assertTrue(referredMetadataService.isReferringMetadata(referringNode, referredAspect1)); + expectedException(MetadataReferralNotFound.class, + () -> referredMetadataService.isReferringMetadata(nodeWithoutReferent, unreferredType)); + assertFalse(referredMetadataService.isReferringMetadata(nodeWithoutReferent, referredAspect1)); + } + + @Test public void getReferentNode() + { + assertEquals(referentNode, referredMetadataService.getReferentNode(referringNode, referredAspect1)); + expectedException(MetadataReferralNotFound.class, + () -> { + referredMetadataService.getReferentNode(referringNode, unreferredType); + return null; + }); + assertNull(referredMetadataService.getReferentNode(nodeWithoutReferent, referredAspect1)); + } + + @Test public void getReferredProperties() + { + final Map expectedProps = new HashMap<>(); + expectedProps.put(referredProp, referredPropValue); + + assertEquals(expectedProps, referredMetadataService.getReferredProperties(referringNode, referredAspect1)); + + expectedException(MetadataReferralNotFound.class, + () -> referredMetadataService.getReferredProperties(referringNode, unreferredType)); + + expectedException(ReferentNodeNotFound.class, + () -> referredMetadataService.getReferredProperties(nodeWithoutReferent, referredAspect1)); + } + + @Test public void getReferredProperty() + { + assertEquals(referredPropValue, referredMetadataService.getReferredProperty(referringNode, referredProp)); + + expectedException(IllegalArgumentException.class, + () -> referredMetadataService.getReferredProperty(referringNode, unreferredProp)); + + expectedException(MetadataReferralNotFound.class, + () -> referredMetadataService.getReferredProperties(nodeWithoutReferent, referredProp)); + } + + @Test public void hasReferredAspect() + { + assertTrue(referredMetadataService.hasReferredAspect(referringNode, referredAspect1)); + + expectedException(MetadataReferralNotFound.class, + () -> referredMetadataService.hasReferredAspect(referringNode, unreferredType)); + + expectedException(ReferentNodeNotFound.class, + () -> referredMetadataService.hasReferredAspect(nodeWithoutReferent, referredAspect1)); + } + + @Test public void getAttachedReferrals() + { + final Map expectedReferrals = new HashMap<>(); + expectedReferrals.put(referral, referentNode); + + assertEquals(expectedReferrals, referredMetadataService.getAttachedReferrals(referringNode)); + assertEquals(emptyMap(), referredMetadataService.getAttachedReferrals(nodeWithoutReferent)); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/FPUtils.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/FPUtils.java new file mode 100644 index 0000000000..335461a587 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/FPUtils.java @@ -0,0 +1,85 @@ +/* + * 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.test.util; + +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * Utility class to help with Java 8 FP stuff. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class FPUtils +{ + /** + * This method is intended to work exactly like {@code java.util.Arrays.asList()} but it takes + * a vararg of {@code Supplier}s instead of actual objects. + * + * @param suppliers a vararg of {@link Supplier}s giving a sequence of values for the list. + * @param the type of elements in the list. + * @return the list with each element being the first retrieved from a {@code Supplier}. + */ + public static List asListFrom(Supplier... suppliers) + { + if (suppliers == null || suppliers.length == 0) + { + return Collections.emptyList(); + } + else + { + return Stream.of(suppliers) + .map(s -> s.get()) + .collect(toList()); + } + } + + /** + * This method is intended to work exactly like {@link #asSet(Object[])}} but it takes + * a vararg of {@code Supplier}s instead of actual objects. + * + * @param suppliers a vararg of {@link Supplier}s giving a sequence of values for the set. + * @param the type of elements in the set. + * @return the set with each element being the first retrieved from a {@code Supplier} (duplicates removed). + */ + public static Set asSetFrom(Supplier... suppliers) + { + List l = asListFrom(suppliers); + return new HashSet<>(l); + } + + /** + * This utility method converts a vararg of objects into a Set. + * + * @param objects the objects to be added to the set + * @return a Set of objects (any equal objects will of course not be duplicated) + */ + public static Set asSet(T... objects) + { + return new HashSet<>(asList(objects)); + } +} \ No newline at end of file diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/FPUtilsUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/FPUtilsUnitTest.java new file mode 100644 index 0000000000..8db28f62b3 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/FPUtilsUnitTest.java @@ -0,0 +1,77 @@ +/* + * 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.test.util; + +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asListFrom; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSetFrom; +import static org.junit.Assert.assertEquals; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +/** + * Unit tests for {@link FPUtils}. + * + * @author Neil Mc Erlean + * @since 2.4.a + */ +public class FPUtilsUnitTest +{ + @Test public void asListShouldProduceList() + { + List l = asListFrom(() -> "hello", + () -> "world", + () -> { + String s1 = "abc"; + String s2 = "xyz"; + return s1 + s2; + }); + assertEquals(asList("hello", "world", "abcxyz"), l); + } + + @Test public void asListShouldWorkForEmptyVarArgs() + { + assertEquals(emptyList(), FPUtils.asListFrom()); + } + + @Test public void asSetShouldProduceSet() + { + assertEquals(new HashSet<>(asList("hello", "world")), + asSet("hello", "hello", "world")); + } + + @Test public void asSetFromShouldWork() + { + Set s = asSetFrom(() -> "hello", + () -> "hello", + () -> "world", + () -> { + String s1 = "wo"; + String s2 = "rld"; + return s1 + s2; + }); + assertEquals(new HashSet<>(asList("hello", "world")), s); + } +} \ No newline at end of file