Merge of classified_renditions branch which introduces MetadataReferralService and fixes RM-2549.

==============================================================================

Merged  to :
   111287: Common utility classes developed as part of refactor for RM-2549.
       asSet method that works like java.util.Arrays.asList.
       Also variants of java.util.Arrays.asList that take Supplier<T> rather than T.
   111292: This checkin provides the non-RM-specific parts of metadata delegation, which are required for the refactor of classified renditions needed for RM-2549.
   111633: Massive renaming. Delegate/Delegation becomes Referrer, Referent and things *do* make a little more sense.
   111643: This is the RM-specific parts of the refactor for classified renditions - see RM-2549.
   111696: Addressing code review comments.
   111703: Addressing code review comments
   111707: Addressing review comments. Clearer use of lambdas due to default methods in java.util.Collection.
   111768: Addition of tidyup code for when clf:classified aspect is removed. Also added notes on what's to do if this ever becomes a core service.
   111772: Slight refactor. ReferredMetadataService uses the registry to look up Referrals rather than the AdminService. Seems neater.
   111779: Addressing review comment - don't have assoc types in the service API - have aspect names instead.
       I agree with this comment. I think assoc types are an implementation detail of this service.
   111855: Applying code review comment. I added an 'mr' prefix to the spring beans, which we hope will make our lives easier if this Metadata Referral stuff ever makes it into core.




git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@111864 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Neil McErlean
2015-09-10 13:34:40 +00:00
24 changed files with 1887 additions and 207 deletions

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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<Void>()
{
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());

View File

@@ -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.
* <p>
* 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.
* <p>
* 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<QName, Serializable> before,
final Map<QName, Serializable> 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.
* <p>
* 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<Void>()
{
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<ChildAssociationRef> 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.
* <p>
* 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<Void>()
{
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<ChildAssociationRef> 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());
}
/**

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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.
* <p/>
* The connection between the nodes is made with a specified {@link #assocType peer association}.
*<p/>
* 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<QName> 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<QName> 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<QName> 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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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.
* <p/>
* 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}.
* <p/>
* Note that attaching a referrer for the specified aspect will also link the two nodes for
* all aspects defined in the {@link MetadataReferral}.
* <p/>
* 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.
* <p/>
* 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<MetadataReferral> 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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<AssociationRef> existingReferrerAssocs = nodeService.getSourceAssocs(referrer, assocType);
if ( !existingReferrerAssocs.isEmpty())
{
final List<NodeRef> existingReferrers = transform(existingReferrerAssocs,
new Function<AssociationRef, NodeRef>()
{
@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<AssociationRef> 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<NodeRef> existingReferents = transform(existingReferentAssocs,
new Function<AssociationRef, NodeRef>()
{
@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<AssociationRef> 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<MetadataReferral> getAttachedReferralsFrom(NodeRef referrer)
{
final Set<MetadataReferral> allMetadataReferrals = registry.getMetadataReferrals();
final Set<MetadataReferral> 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<MetadataReferral> allMetadataReferrals = getAttachedReferralsFrom(referrer);
for (MetadataReferral d : allMetadataReferrals)
{
if (d.getAspects().contains(aspectName)) return d;
}
return null;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<MetadataReferral> 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<MetadataReferral> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<NodeRef> existingReferrers;
public ChainedMetadataReferralUnsupported(String msgId, List<NodeRef> existingReferrers)
{
super(msgId);
this.existingReferrers = existingReferrers;
}
public List<NodeRef> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<QName, Serializable> 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<MetadataReferral, NodeRef> getAttachedReferrals(NodeRef potentialReferrer);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<AssociationRef> 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<AssociationRef> assocs = nodeService.getTargetAssocs(referrer, assocType);
return assocs.isEmpty() ? null : assocs.get(0).getTargetRef();
}
}
@Override public Map<QName, Serializable> 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<QName, Serializable> allProps = nodeService.getProperties(referentNode);
final Map<QName, Serializable> aspectProps = filterKeys(allProps,
new Function<QName, Boolean>()
{
@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<QName, Serializable> 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<MetadataReferral, NodeRef> getAttachedReferrals(NodeRef potentialReferrer)
{
Set<MetadataReferral> metadataReferrals = referralAdminService.getAttachedReferralsFrom(potentialReferrer);
Map<MetadataReferral, NodeRef> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* This package contains the types that deliver the Metadata Referral feature.
* Metadata referral allows node metadata to be shared between multiple Alfresco nodes.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* Multiple nodes may share the same referent node and one node may be linked to multiple referrers.
* <p/>
* 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.
* <p/>
* See {@link org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferralAdminService}
* for details on how to create and destroy metadata links between nodes.
* <p/>
* See {@link org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetadataService}
* for details on how the data access is performed.
* <p/>
* 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;