()
{
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