This checkin provides the non-RM-specific parts of metadata delegation, which are required for the refactor of classified renditions needed for RM-2549.

We essentially have two new services, MetadataDelegationService and MetadataDelegationAdminService, along with some spring configuration and content model configuration (this will come in the subsequent RM-specific checkin.)

metadata-delegation-context.xml defines the spring beans that form the Metadata Delegation services.

The DelegationAdminService is used to attach/detach nodes in pairs such that one node can ‘inherit’ some aspect metadata from another node - the ‘delegate’.

The Delegation class is the definition of a type of link - it’s defined by the aspects that it handles and the type of peer association that it uses to link pairs of nodes.

Delegations must be defined as spring beans in the system to be available for use and they are exposed to Java code via the DelegationRegistry class.

Note that chains of delegations (node A -> node B -> node C) are not supported. Any attempt to attach nodes in a way that would lead to a delegation chain is detected and prevented.

The DelegationService is not about the creation and destruction of links, but is about accessing the metadata on a delegate node.



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/BRANCHES/classified_renditions@111292 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Neil McErlean
2015-09-02 12:03:38 +00:00
parent 06a7ef9b73
commit 9e4ccdd053
13 changed files with 1500 additions and 1 deletions

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<QName, Serializable>()
{{
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<QName, Serializable> 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<Delegation, NodeRef> expectedDelegations = new HashMap<>();
expectedDelegations.put(delegate, delegateNode);
assertEquals(expectedDelegations, delegationService.getDelegations(nodeWithDelegate));
assertEquals(emptyMap(), delegationService.getDelegations(nodeWithoutDelegate));
}
}

View File

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