diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/metadata-delegation-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/metadata-delegation-context.xml new file mode 100644 index 0000000000..106cf0cb90 --- /dev/null +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/metadata-delegation-context.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService.attachDelegate=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService.detachDelegate=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService.getDefinedDelegations=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService.getDelegationFor=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService.getDelegationsFrom=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService.*=ACL_DENY + + + + + + + + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.hasDelegateForAspect=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.getDelegateFor=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.getDelegateProperties=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.getDelegateProperty=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.hasAspectOnDelegate=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.getDelegations=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService.*=ACL_DENY + + + + + + + + + + + + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml index f75a9ff12d..921a34aaf0 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml @@ -267,9 +267,12 @@ + + + - + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/Delegation.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/Delegation.java new file mode 100644 index 0000000000..05a2e3caef --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/Delegation.java @@ -0,0 +1,135 @@ +/* + * 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.metadatadelegation; + +import org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.InvalidDelegation; +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 Delegation is a definition of a {@link #aspects set of aspects} whose metadata are to be delegated. + * Using a Delegation, you can attach a delegate node to any node and {@code hasAspect} and + * {@code getPropert[y|ies]} calls can be delegated to the delegate node. + *

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

+ * Note that a Delegation is not an instance of a link between two nodes, but the definition of such a link. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class Delegation +{ + private DictionaryService dictionaryService; + private DelegationRegistry delegationRegistry; + private Set aspects; + private QName assocType; + + public Delegation() + { + // Intentionally empty + } + + public void setDictionaryService(DictionaryService service) + { + this.dictionaryService = service; + } + + public void setDelegationRegistry(DelegationRegistry registry) + { + this.delegationRegistry = 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 InvalidDelegation("Illegal null assocType"); + } + if (aspects == null || aspects.isEmpty()) + { + throw new InvalidDelegation("Illegal null or empty aspects set"); + } + if (dictionaryService.getAssociation(assocType) == null) + { + throw new InvalidDelegation("Association not found: " + assocType); + } + for (QName aspect : aspects) + { + if (dictionaryService.getAspect(aspect) == null) + { + throw new InvalidDelegation("Aspect not found: " + aspect); + } + } + + this.delegationRegistry.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 delegated. */ + 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 Delegation) + { + Delegation that = (Delegation)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/metadatadelegation/DelegationAdminService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminService.java new file mode 100644 index 0000000000..0a49ce4d2c --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminService.java @@ -0,0 +1,92 @@ +/* + * 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.metadatadelegation; + +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.ChainedDelegationUnsupported; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +import java.util.Set; + +/** + * A service to manage the delegation of aspect metadata. + * Using this service a node can be {@link #attachDelegate linked} to a delegate node for a configured set of aspects. + * (Note that this delegate 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 #getDefinedDelegations() defined Delegation} 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 {@code Delegation}. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public interface DelegationAdminService +{ + /** + * Creates a link between two nodes such that the first {@code nodeRef} can 'inherit' or reuse some aspect + * metadata from another node - the {@code delegateNodeRef}. + *

+ * Note that links can currently only extend between two pairs of nodes and cannot be chained. + * + * @param nodeRef the node which is to inherit additional metadata. + * @param delegateNodeRef the node which will provide the additional metadata. + * @param assocType the type of the peer association which will link the two nodes. + * @return a {@link Delegation} object which defines the link type. + * @throws ChainedDelegationUnsupported if an attempt is made to attach nodes such that a chain would be made. + */ + Delegation attachDelegate(NodeRef nodeRef, NodeRef delegateNodeRef, QName assocType); + + /** + * Removes an existing metadata delegation link between two nodes. + * + * @param nodeRef the node which has been linked to a delegate. + * @param assocType the type of the peer assocation forming the link. + * @return the removed {@link Delegation}. + */ + Delegation detachDelegate(NodeRef nodeRef, QName assocType); + + /** + * Gets the set of defined {@link Delegation}s. + * + * @return the set of defined Delegations. + */ + Set getDefinedDelegations(); + + /** + * Gets the {@link Delegation} which contains the specified {@code aspectName} if there is one. + * Note that only one {@link Delegation} may declare that it handles any particular aspect. + * + * @param aspectName the name of the aspect whose {@link Delegation} is sought. + * @return the {@link Delegation} which handles the specified aspect, if there is one. + */ + Delegation getDelegationFor(QName aspectName); + + /** + * Gets the set of {@link Delegation}s which are in effect from the specified {@code nodeRef}. + * From these, you can retrieve the types of peer associations which are linked to the specified + * {@code nodeRef} as well as the aspect types that are handled. + * + * @param nodeRef the NodeRef whose delegations are sought. + * @return the set of {@link Delegation}s from the specified nodeRef. + */ + Set getDelegationsFrom(NodeRef nodeRef); +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminServiceImpl.java new file mode 100644 index 0000000000..0a2463ba05 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminServiceImpl.java @@ -0,0 +1,167 @@ +/* + * 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.metadatadelegation; + +import static org.alfresco.util.collections.CollectionUtils.transform; + +import org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.ChainedDelegationUnsupported; +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 3.0.a + */ +public class DelegationAdminServiceImpl implements DelegationAdminService +{ + private DelegationRegistry registry; + private NodeService nodeService; + + public void setNodeService(NodeService service) + { + this.nodeService = service; + } + + public void setDelegationRegistry(DelegationRegistry registry) + { + this.registry = registry; + } + + @Override public Delegation attachDelegate(NodeRef nodeRef, NodeRef delegateNodeRef, QName assocType) + { + final Delegation delegation = getDelegationForAssociation(assocType); + + // Prevent the creation of chains of delegation from node A to B to C. + + // If any nodes are already delegating to nodeRef for the specified assoc, then we can't chain. + final List existingDelegatorAssocs = nodeService.getSourceAssocs(nodeRef, assocType); + if ( !existingDelegatorAssocs.isEmpty()) + { + final List existingDelegators = transform(existingDelegatorAssocs, + new Function() + { + @Override public NodeRef apply(AssociationRef assocRef) + { + return assocRef.getSourceRef(); + } + }); + throw new ChainedDelegationUnsupported("Cannot attach delegate", existingDelegators); + } + + // Likewise if this delegate node is already itself delegating elsewhere, we cannot chain. + final List existingDelegateAssocs = nodeService.getTargetAssocs(delegateNodeRef, assocType); + if ( !existingDelegateAssocs.isEmpty()) + { + // If it's not empty, it should only have one value in it, but just in case... + final List existingDelegates = transform(existingDelegateAssocs, + new Function() + { + @Override public NodeRef apply(AssociationRef assocRef) + { + return assocRef.getTargetRef(); + } + }); + throw new ChainedDelegationUnsupported("Cannot attach delegate", existingDelegates); + } + + // OK. We're good to go. We're not making a chain here. + nodeService.createAssociation(nodeRef, delegateNodeRef, assocType); + + return delegation; + } + + private Delegation getDelegationForAssociation(QName assocType) + { + final Delegation delegation = registry.getDelegateForAssociation(assocType); + + if (delegation == null) + { + throw new IllegalArgumentException("No " + Delegation.class.getSimpleName() + + " configured for assocType " + assocType); + } + return delegation; + } + + @Override public Delegation detachDelegate(NodeRef nodeRef, QName assocType) + { + // Is the association there? + final List assocs = nodeService.getTargetAssocs(nodeRef, assocType); + + if (assocs == null || assocs.isEmpty()) + { + return null; + } + else + { + Delegation result = getDelegationForAssociation(assocType); + + // There should only be one such association... but we'll remove them all just in case + for (AssociationRef assocRef : assocs) + { + nodeService.removeAssociation(nodeRef, assocRef.getTargetRef(), assocType); + } + + return result; + } + } + + @Override public Delegation getDelegationFor(QName aspectName) + { + Delegation delegation = null; + + for (Delegation d : getDefinedDelegations()) + { + if (d.getAspects().contains(aspectName)) + { + delegation = d; + break; + } + } + return delegation; + } + + @Override public Set getDelegationsFrom(NodeRef nodeRef) + { + final Set allDelegations = getDefinedDelegations(); + + final Set result = new HashSet<>(); + for (Delegation d : allDelegations) + { + final QName assocType = d.getAssocType(); + if ( !nodeService.getTargetAssocs(nodeRef, assocType).isEmpty()) + { + result.add(d); + } + } + + return result; + } + + @Override public Set getDefinedDelegations() + { + return registry.getDelegations(); + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationException.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationException.java new file mode 100644 index 0000000000..ea978abf7b --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationException.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.module.org_alfresco_module_rm.metadatadelegation; + +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 delegates. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class DelegationException extends AlfrescoRuntimeException +{ + public DelegationException(String msgId) { super(msgId); } + public DelegationException(String msgId, Throwable cause) { super(msgId, cause); } + + public static class InvalidDelegation extends DelegationException + { + public InvalidDelegation(String msgId) + { + super(msgId); + } + } + + /** A Metadata Delegation already exists. */ + public static class DelegationAlreadyExists extends DelegationException + { + private final Delegation delegation; + + public DelegationAlreadyExists(String msgId, Delegation delegation) + { + super(msgId); + this.delegation = delegation; + } + } + + /** + * A {@link Delegation} has not been found. + * Remember that a Delegation is the definition of a type of link. + */ + public static class DelegationNotFound extends DelegationException + { + public DelegationNotFound(String msgId) + { + super(msgId); + } + } + + /** + * A Delegate has not been found. + * Remember that a Delegate is an instance of a link between two nodes. + */ + public static class DelegateNotFound extends DelegationException + { + public DelegateNotFound(String msgId) + { + super(msgId); + } + } + + /** + * Exception to report that we currently do not support chained delegation. + */ + public static class ChainedDelegationUnsupported extends DelegationException + { + private final List nodesAlreadyDelegating; + + public ChainedDelegationUnsupported(String msgId, List nodesAlreadyDelegating) + { + super(msgId); + this.nodesAlreadyDelegating = nodesAlreadyDelegating; + } + + public List getNodesAlreadyDelegating() + { + return this.nodesAlreadyDelegating; + } + + @Override public String toString() + { + StringBuilder msg = new StringBuilder(); + msg.append(this.getClass().getSimpleName()).append(" Already delegating from: ") + .append(nodesAlreadyDelegating.toString()); + return msg.toString(); + } + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationRegistry.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationRegistry.java new file mode 100644 index 0000000000..54133ee40b --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationRegistry.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.module.org_alfresco_module_rm.metadatadelegation; + +import org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.DelegationAlreadyExists; +import org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.InvalidDelegation; +import org.alfresco.service.namespace.QName; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * This is a registry of {@link Delegation delegations} which have been defined in the system. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class DelegationRegistry +{ + private final Set delegations = new HashSet<>(); + + public void register(Delegation delegation) + { + // Various validation steps to do here to ensure we get consistent, sensible Delegations registered. + if (delegations.contains(delegation)) + { + throw new DelegationAlreadyExists("Cannot register duplicate delegation", delegation); + } + for (Delegation existingDelegation : delegations) + { + if (existingDelegation.getAssocType().equals(delegation.getAssocType())) + { + throw new InvalidDelegation("Cannot register two delegations with the same assocType. " + + "Existing: " + existingDelegation + + " New: " + delegation); + } + // Yes this is a for loop inside a for loop but we're assuming these sets will not be large. + for (QName existingAspect : existingDelegation.getAspects()) + { + if (delegation.getAspects().contains(existingAspect)) + { + throw new InvalidDelegation("Cannot register two delegations with the same aspect. " + + "Existing: " + existingDelegation + + " New: " + delegation); + } + } + } + + this.delegations.add(delegation); + } + + public Set getDelegations() + { + return Collections.unmodifiableSet(delegations); + } + + public Delegation getDelegateForAssociation(QName assocType) + { + for (Delegation d : delegations) + { + if (d.getAssocType().equals(assocType)) + { + return d; + } + } + return null; + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationService.java new file mode 100644 index 0000000000..b4048761c0 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationService.java @@ -0,0 +1,96 @@ +/* + * 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.metadatadelegation; + +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.DelegationNotFound; + +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 delegated metadata. + * TODO complete. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public interface DelegationService +{ + /** + * Checks if the specified nodeRef has an attached {@link Delegation} for the specified aspect. + * + * @param nodeRef the nodeRef which may or may not have a delegate node for the specified aspect. + * @param aspectName the name of the aspect for which the node may or may not have delegation. + * @return whether the node is delegating metadata reads for the specified aspect. + * @throws InvalidNodeRefException if the supplied nodeRef does not exist. + * @throws DelegationNotFound if no delegation for the specified aspect has been attached. + */ + boolean hasDelegateForAspect(NodeRef nodeRef, QName aspectName); + + /** + * Gets the delegate node for the specified aspect, if there is one. + * + * @param nodeRef the node with the delegate. + * @param aspectName the aspect name. + * @return the nodeRef of the delegate if there is one, else {@code null}. + * @throws DelegationNotFound if no delegation for the specified aspect has been attached. + */ + NodeRef getDelegateFor(NodeRef nodeRef, QName aspectName); + + /** + * Gets all the property values from the delegate node for the specified aspect. + * + * @param nodeRef the node with the delegate. + * @param aspectName the aspect name which holds the properties we want. + * @return the property values as obtained from the delegate node. + */ + Map getDelegateProperties(NodeRef nodeRef, QName aspectName); + + /** + * Gets the specified property value from the delegate node. + * + * @param nodeRef the node with the delegate. + * @param propertyName the property name which we want. + * @return the property value as obtained from the delegate node. + */ + Serializable getDelegateProperty(NodeRef nodeRef, QName propertyName); + + /** + * Determines if a given aspect is present on a node's delegates. + * + * @param nodeRef the node for which a delegate is sought. + * @param aspectName the aspect which is to be checked. + * @return Returns true if the aspect has been applied to one of the given node's delegates, + * otherwise false + */ + boolean hasAspectOnDelegate(NodeRef nodeRef, QName aspectName); + + /** + * Gets all {@link Delegation delegations} currently attached to the specified node. + * + * @param nodeRef the node whose delegations are sought. + * @return Returns a map of all {@link Delegation delegations} by NodeRef for the specified nodeRef. + */ + Map getDelegations(NodeRef nodeRef); +} + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationServiceImpl.java new file mode 100644 index 0000000000..3b7190c7d5 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationServiceImpl.java @@ -0,0 +1,182 @@ +/* + * 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.metadatadelegation; + +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.DelegateNotFound; +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.DelegationNotFound; +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 3.0.a + */ +public class DelegationServiceImpl implements DelegationService +{ + private DelegationAdminService delegationAdminService; + private DictionaryService dictionaryService; + private NodeService nodeService; + + public void setDelegationAdminService(DelegationAdminService service) + { + this.delegationAdminService = service; + } + + public void setDictionaryService(DictionaryService service) + { + this.dictionaryService = service; + } + + public void setNodeService(NodeService service) + { + this.nodeService = service; + } + + @Override public boolean hasDelegateForAspect(NodeRef nodeRef, QName aspectName) + { + final Delegation delegation = delegationAdminService.getDelegationFor(aspectName); + + if ( !nodeService.exists(nodeRef)) + { + throw new InvalidNodeRefException(nodeRef); + } + else if (delegation == null) + { + throw new DelegationNotFound("No delegation found for aspect: " + aspectName); + } + else + { + final List targetAssocs = nodeService.getTargetAssocs(nodeRef, delegation.getAssocType()); + return !targetAssocs.isEmpty(); + } + } + + @Override public NodeRef getDelegateFor(NodeRef nodeRef, QName aspectName) + { + final Delegation d = delegationAdminService.getDelegationFor(aspectName); + + if (d == null) + { + throw new DelegationNotFound("No delegation found for aspect: " + aspectName); + } + else + { + final QName assocType = d.getAssocType(); + final List assocs = nodeService.getTargetAssocs(nodeRef, assocType); + + return assocs.isEmpty() ? null : assocs.get(0).getTargetRef(); + } + } + + @Override public Map getDelegateProperties(NodeRef nodeRef, final QName aspectName) + { + final NodeRef delegateNode = getDelegateFor(nodeRef, aspectName); + + if (delegateNode == null) + { + throw new DelegateNotFound("No delegate node found for " + nodeRef + " " + aspectName); + } + else + { + Map allProps = nodeService.getProperties(delegateNode); + 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 getDelegateProperty(NodeRef nodeRef, 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 IllegalArgumentException(msg.toString()); + } + + Map allPropValues = getDelegateProperties(nodeRef, aspectDefn.getName()); + return allPropValues.get(propertyName); + } + + @Override public boolean hasAspectOnDelegate(NodeRef nodeRef, QName aspectName) + { + final NodeRef delegateNode = getDelegateFor(nodeRef, aspectName); + + if (delegateNode == null) + { + throw new DelegateNotFound("No delegate node found for " + nodeRef + " " + aspectName); + } + else + { + return nodeService.hasAspect(delegateNode, aspectName); + } + } + + @Override public Map getDelegations(NodeRef nodeRef) + { + Set delegations = delegationAdminService.getDelegationsFrom(nodeRef); + + Map result = new HashMap<>(); + for (Delegation d : delegations) + { + // We need only use the first aspect to get the Delegation object + if (!d.getAspects().isEmpty()) + { + result.put(d, getDelegateFor(nodeRef, d.getAspects().iterator().next())); + } + } + + return result; + } +} + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/package-info.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/package-info.java new file mode 100644 index 0000000000..b35ca04e74 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/package-info.java @@ -0,0 +1,46 @@ +/* + * 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 Delegation feature. + * Metadata delegation allows read-only aspect metadata for any given Alfresco node to + * be sourced from another node, the delegate. + *

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

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

+ * The linking of nodes to their metadata delegates 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 delegate via the association. + *

+ * See {@link org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationAdminService} + * for details on how to create and destroy delegation links between nodes. + *

+ * The read-only access to delegated metadat is made available via the + * See {@link org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationService} + * for details on how the data access is performed. + *

+ * See {@link org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationRegistry} + * for details on what {@link org.alfresco.module.org_alfresco_module_rm.metadatadelegation.Delegation}s + * are defined in the system. + */ +package org.alfresco.module.org_alfresco_module_rm.metadatadelegation; \ No newline at end of file diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminServiceImplUnitTest.java new file mode 100644 index 0000000000..1cd6a81caf --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationAdminServiceImplUnitTest.java @@ -0,0 +1,141 @@ +/* + * 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.metadatadelegation; + +import static java.util.Arrays.asList; +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.ChainedDelegationUnsupported; +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.expectedException; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link DelegationAdminServiceImpl}. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class DelegationAdminServiceImplUnitTest +{ + @InjectMocks private final DelegationAdminServiceImpl delegationAdminService = new DelegationAdminServiceImpl(); + + @Mock DictionaryService mockDictionaryService; + @Mock NodeService mockNodeService; + @Mock DelegationRegistry mockRegistry; + @Mock DelegationServiceImpl mockDelegationService; + + private final NodeRef node1 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node1"); + private final NodeRef node2 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node2"); + private final NodeRef node3 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node3"); + + private final QName assoc1 = QName.createQName("test", "assoc1"); + private final QName assoc2 = QName.createQName("test", "assoc2"); + + private final QName aspect1 = QName.createQName("test", "aspect1"); + private final QName aspect2 = QName.createQName("test", "aspect2"); + private final QName aspect3 = QName.createQName("test", "aspect3"); + + private final Delegation delegate1 = new Delegation() + {{ + this.setAssocType(assoc1); + this.setAspects(asSet(aspect1, aspect2)); + }}; + private final Delegation delegate2 = new Delegation() + {{ + this.setAssocType(assoc2); + this.setAspects(asSet(aspect3)); + }}; + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + + when(mockRegistry.getDelegations()).thenReturn(asSet(delegate1, delegate2)); + } + + @Test(expected=IllegalArgumentException.class) + public void attachingDelegateWithNoAssociationConfiguredShouldFail() + { + delegationAdminService.attachDelegate(node1, node2, assoc1); + } + + @Test public void attachDetach() + { + when(mockRegistry.getDelegateForAssociation(assoc1)).thenReturn(delegate1); + + // attach + Delegation d = attachDelegate(node1, node2, assoc1); + + // validate + assertEquals(assoc1, d.getAssocType()); + assertEquals(asSet(aspect1, aspect2), d.getAspects()); + assertTrue(mockDelegationService.hasDelegateForAspect(node1, aspect1)); + assertFalse(mockDelegationService.hasDelegateForAspect(node1, aspect3)); + + // detach + assertEquals(d, delegationAdminService.detachDelegate(node1, assoc1)); + } + + private Delegation attachDelegate(NodeRef from, NodeRef to, QName assocType) + { + Delegation d = delegationAdminService.attachDelegate(from, to, assocType); + when(mockNodeService.getSourceAssocs(to, assocType)).thenReturn(asList(new AssociationRef(from, assocType, to))); + when(mockNodeService.getTargetAssocs(from, assocType)).thenReturn(asList(new AssociationRef(from, assocType, to))); + for (QName aspect : d.getAspects()) + { + when(mockDelegationService.hasDelegateForAspect(from, aspect)).thenReturn(true); + } + return d; + } + + @Test public void chainsOfDelegationShouldBePrevented() + { + when(mockRegistry.getDelegateForAssociation(assoc1)).thenReturn(delegate1); + + // The node already has a delegation in place: node1 -> node2. We're trying to add to the + // end of the chain: node2 -> node3 + when(mockNodeService.getSourceAssocs(node2, assoc1)).thenReturn(asList(new AssociationRef(node1, assoc1, node2))); + when(mockNodeService.getTargetAssocs(node1, assoc1)).thenReturn(asList(new AssociationRef(node1, assoc1, node2))); + + expectedException(ChainedDelegationUnsupported.class, () -> { + delegationAdminService.attachDelegate(node2, node3, assoc1); + return null; + }); + + // Now try to add to the start of the chain: node3 -> node1 + expectedException(ChainedDelegationUnsupported.class, () -> { + delegationAdminService.attachDelegate(node3, node1, assoc1); + return null; + }); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationServiceImplUnitTest.java new file mode 100644 index 0000000000..b28fae5de4 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationServiceImplUnitTest.java @@ -0,0 +1,194 @@ +/* + * 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.metadatadelegation; + +import static java.util.Collections.emptyMap; +import static java.util.Arrays.asList; +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.DelegateNotFound; +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.DelegationNotFound; +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.expectedException; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * Unit tests for {@link DelegationServiceImpl}. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class DelegationServiceImplUnitTest +{ + @InjectMocks private final DelegationServiceImpl delegationService = new DelegationServiceImpl(); + + @Mock DictionaryService mockDictionaryService; + @Mock NodeService mockNodeService; + @Mock DelegationAdminServiceImpl mockDelegationAdminService; + + /** This node has a delegate node. */ + private final NodeRef nodeWithDelegate = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "nodeWithDelegate"); + /** This is the delgate for {@link #nodeWithDelegate}. */ + private final NodeRef delegateNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "delegateNode"); + /** This node has no delegate node. */ + private final NodeRef nodeWithoutDelegate = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "nodeWithoutDelegate"); + + /** The type of the peer association that links the delegate to its source. */ + private final QName delegateAssocType = QName.createQName("test", "delegateAssocType"); + /** The instance of the association between {@link #nodeWithDelegate} and {@link #delegateNode}. */ + private final AssociationRef delegateAssocRef = new AssociationRef(nodeWithDelegate, delegateAssocType, delegateNode); + + /** Name of an aspect that has been delegated. */ + private final QName delegatedAspect1 = QName.createQName("test", "delegatedAspect1"); + /** Name of an aspect that has been delegated. */ + private final QName delegatedAspect2 = QName.createQName("test", "delegatedAspect2"); + /** Name of a content class (a type in this case) that has not been delegated. + * N.B. Types can't be delegated currently. */ + private final QName undelegatedType = QName.createQName("test", "undelegatedType"); + + private final QName delegatedProp = QName.createQName("test", "delegatedProp"); + private final Serializable delegatedPropValue = "hello"; + private final QName undelegatedProp = QName.createQName("test", "undelegatedProp"); + + private final Delegation delegate = new Delegation() + {{ + this.setAssocType(delegateAssocType); + this.setAspects(asSet(delegatedAspect1, delegatedAspect2)); + }}; + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + + final PropertyDefinition aspectProp = mock(PropertyDefinition.class); + final ClassDefinition aspectDefn = mock(ClassDefinition.class); + when(aspectDefn.getName()).thenReturn(delegatedAspect1); + when(aspectProp.getContainerClass()).thenReturn(aspectDefn); + when(aspectDefn.isAspect()).thenReturn(true); + + final PropertyDefinition typeProp = mock(PropertyDefinition.class); + final ClassDefinition typeDefn = mock(ClassDefinition.class); + when(typeDefn.getName()).thenReturn(undelegatedType); + when(typeProp.getContainerClass()).thenReturn(typeDefn); + when(typeDefn.isAspect()).thenReturn(false); + + when(mockDictionaryService.getProperty(delegatedProp)).thenReturn(aspectProp); + + when(mockDelegationAdminService.getDelegationsFrom(nodeWithDelegate)).thenReturn(asSet(delegate)); + for (QName delegatedAspect : asSet(delegatedAspect1, delegatedAspect2)) + { + when(mockDelegationAdminService.getDelegationFor(delegatedAspect)).thenReturn(delegate); + when(mockNodeService.hasAspect(delegateNode, delegatedAspect)).thenReturn(true); + } + when(mockNodeService.getSourceAssocs(delegateNode, delegateAssocType)).thenReturn(asList(delegateAssocRef)); + when(mockNodeService.getTargetAssocs(nodeWithDelegate, delegateAssocType)).thenReturn(asList(delegateAssocRef)); + when(mockNodeService.exists(any(NodeRef.class))).thenReturn(true); + when(mockNodeService.getProperties(delegateNode)) + .thenReturn(new HashMap() + {{ + this.put(delegatedProp, delegatedPropValue); + }}); + } + + @Test public void hasDelegateForAspect() + { + assertTrue(delegationService.hasDelegateForAspect(nodeWithDelegate, delegatedAspect1)); + expectedException(DelegationNotFound.class, () -> delegationService.hasDelegateForAspect(nodeWithoutDelegate, undelegatedType)); + assertFalse(delegationService.hasDelegateForAspect(nodeWithoutDelegate, delegatedAspect1)); + } + + @Test public void getDelegateFor() + { + assertEquals(delegateNode, delegationService.getDelegateFor(nodeWithDelegate, delegatedAspect1)); + expectedException(DelegationNotFound.class, () -> + { + delegationService.getDelegateFor(nodeWithDelegate, undelegatedType); + return null; + }); + assertNull(delegationService.getDelegateFor(nodeWithoutDelegate, delegatedAspect1)); + } + + @Test public void getDelegateProperties() + { + final Map expectedProps = new HashMap<>(); + expectedProps.put(delegatedProp, delegatedPropValue); + + assertEquals(expectedProps, delegationService.getDelegateProperties(nodeWithDelegate, delegatedAspect1)); + + expectedException(DelegationNotFound.class, + () -> delegationService.getDelegateProperties(nodeWithDelegate, undelegatedType)); + + expectedException(DelegateNotFound.class, + () -> delegationService.getDelegateProperties(nodeWithoutDelegate, delegatedAspect1)); + } + + @Test public void getDelegateProperty() + { + assertEquals(delegatedPropValue, delegationService.getDelegateProperty(nodeWithDelegate, delegatedProp)); + + expectedException(IllegalArgumentException.class, + () -> delegationService.getDelegateProperty(nodeWithDelegate, undelegatedProp)); + + expectedException(DelegationNotFound.class, + () -> delegationService.getDelegateProperties(nodeWithoutDelegate, delegatedProp)); + } + + @Test public void hasAspectOnDelegate() + { + assertTrue(delegationService.hasAspectOnDelegate(nodeWithDelegate, delegatedAspect1)); + + expectedException(DelegationNotFound.class, + () -> delegationService.hasAspectOnDelegate(nodeWithDelegate, undelegatedType)); + + expectedException(DelegateNotFound.class, + () -> delegationService.hasAspectOnDelegate(nodeWithoutDelegate, delegatedAspect1)); + } + + @Test public void getDelegations() + { + final Map expectedDelegations = new HashMap<>(); + expectedDelegations.put(delegate, delegateNode); + + assertEquals(expectedDelegations, delegationService.getDelegations(nodeWithDelegate)); + assertEquals(emptyMap(), delegationService.getDelegations(nodeWithoutDelegate)); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationUnitTest.java new file mode 100644 index 0000000000..d63e6409d4 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/metadatadelegation/DelegationUnitTest.java @@ -0,0 +1,128 @@ +/* + * 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.metadatadelegation; + +import static java.util.Collections.emptySet; +import static org.alfresco.module.org_alfresco_module_rm.metadatadelegation.DelegationException.InvalidDelegation; +import static org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils.expectedException; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asListFrom; +import static org.alfresco.module.org_alfresco_module_rm.test.util.FPUtils.asSet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +/** + * Unit tests for {@link Delegation}. + * + * @author Neil Mc Erlean + * @since 3.0.a + */ +public class DelegationUnitTest +{ + @Mock DictionaryService mockDictionaryService; + @Mock NodeService mockNodeService; + + private final DelegationAdminServiceImpl metadataDelegationService = new DelegationAdminServiceImpl(); + + private final NodeRef node1 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node1"); + private final NodeRef node2 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "node2"); + private final QName aspect1 = QName.createQName("test", "aspect1"); + private final QName aspect2 = QName.createQName("test", "aspect2"); + private final QName assoc1 = QName.createQName("test", "assoc1"); + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + + metadataDelegationService.setNodeService(mockNodeService); + } + + @Test public void nullOrEmptyDelegatesAreForbidden() + { + List invalidDelegations = asListFrom(() -> new Delegation(), + () -> { + Delegation d = new Delegation(); + d.setAssocType(assoc1); + d.setAspects(null); + d.setDictionaryService(mockDictionaryService); + return d; + }, + () -> { + Delegation d = new Delegation(); + d.setAssocType(assoc1); + d.setAspects(emptySet()); + d.setDictionaryService(mockDictionaryService); + return d; + }, + () -> { + Delegation d = new Delegation(); + d.setAssocType(null); + d.setAspects(asSet(aspect1, aspect2)); + d.setDictionaryService(mockDictionaryService); + return d; + }); + + invalidDelegations.stream() + .forEach(d -> expectedException(InvalidDelegation.class, () -> { + d.validateAndRegister(); + return null; + } + )); + } + + @Test(expected=InvalidDelegation.class) + public void delegateMustHaveAssocThatExists() + { + when(mockDictionaryService.getAssociation(assoc1)).thenReturn(null); + when(mockDictionaryService.getAspect(aspect1)).thenReturn(mock(AspectDefinition.class)); + + Delegation d = new Delegation(); + d.setAssocType(assoc1); + d.setAspects(asSet(aspect1)); + d.setDictionaryService(mockDictionaryService); + d.validateAndRegister(); + } + + @Test(expected=InvalidDelegation.class) + public void delegateMustHaveAspectsAllOfWhichExist() + { + when(mockDictionaryService.getAssociation(assoc1)).thenReturn(mock(AssociationDefinition.class)); + when(mockDictionaryService.getAspect(aspect1)).thenReturn(mock(AspectDefinition.class)); + when(mockDictionaryService.getAspect(aspect2)).thenReturn(null); + + Delegation d = new Delegation(); + d.setAssocType(assoc1); + d.setAspects(asSet(aspect1, aspect2)); + d.setDictionaryService(mockDictionaryService); + d.validateAndRegister(); + } +}