diff --git a/pom.xml b/pom.xml index 9a30ca8a5c..2b1feef50e 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 2.11.0 2.10.2 1.2.5 - 0.0.2 + 0.0.6 4.0.2 diff --git a/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java b/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java new file mode 100644 index 0000000000..50d8601e00 --- /dev/null +++ b/src/main/java/org/alfresco/repo/event2/ChildAssociationEventConsolidator.java @@ -0,0 +1,170 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.event2; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.alfresco.repo.event.v1.model.ChildAssociationResource; +import org.alfresco.repo.event.v1.model.EventData; +import org.alfresco.repo.event.v1.model.RepoEvent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.namespace.QName; + +/** + * Encapsulates child association events that occurred in a single transaction. + * + * @author Chris Shields + * @author Sara Aspery + */ +public class ChildAssociationEventConsolidator implements ChildAssociationEventSupportedPolicies +{ + private final Deque eventTypes; + + private final ChildAssociationRef childAssociationRef; + private ChildAssociationResource resource; + private final QNameHelper helper; + + public ChildAssociationEventConsolidator(ChildAssociationRef childAssociationRef, QNameHelper helper) + { + this.eventTypes = new ArrayDeque<>(); + this.childAssociationRef = childAssociationRef; + this.helper = helper; + this.resource = buildChildAssociationResource(this.childAssociationRef); + } + + /** + * Builds and returns the {@link RepoEvent} instance. + * + * @param eventInfo the object holding the event information + * @return the {@link RepoEvent} instance + */ + public RepoEvent getRepoEvent(EventInfo eventInfo) + { + EventType eventType = getDerivedEvent(); + + EventData.Builder eventDataBuilder = EventData.builder() + .setEventGroupId(eventInfo.getTxnId()) + .setResource(resource); + + EventData eventData = eventDataBuilder.build(); + return RepoEvent.builder() + .setId(eventInfo.getId()) + .setSource(eventInfo.getSource()) + .setTime(eventInfo.getTimestamp()) + .setType(eventType.getType()) + .setData(eventData) + .build(); + } + + /** + * Add child association created event on create of a child association. + * + * @param childAssociationRef ChildAssociationRef + */ + @Override + public void onCreateChildAssociation(ChildAssociationRef childAssociationRef, boolean isNewNode) + { + eventTypes.add(EventType.CHILD_ASSOC_CREATED); + resource = buildChildAssociationResource(childAssociationRef); + } + + /** + * Add child association deleted event on delete of a child association. + * + * @param childAssociationRef ChildAssociationRef + */ + @Override + public void beforeDeleteChildAssociation(ChildAssociationRef childAssociationRef) + { + eventTypes.add(EventType.CHILD_ASSOC_DELETED); + resource = buildChildAssociationResource(childAssociationRef); + } + + private ChildAssociationResource buildChildAssociationResource(ChildAssociationRef childAssociationRef) + { + String parentId = childAssociationRef.getParentRef().getId(); + String childId = childAssociationRef.getChildRef().getId(); + String assocType = helper.getQNamePrefixString(childAssociationRef.getTypeQName()); + return new ChildAssociationResource(parentId, childId, assocType); + } + + /** + * @return a derived event for a transaction. + */ + private EventType getDerivedEvent() + { + if (isTemporaryChildAssociation()) + { + // This event will be filtered out, but we set the correct + // event type anyway for debugging purposes + return EventType.CHILD_ASSOC_DELETED; + } + else if (eventTypes.contains(EventType.CHILD_ASSOC_CREATED)) + { + return EventType.CHILD_ASSOC_CREATED; + } + else if (eventTypes.getLast() == EventType.CHILD_ASSOC_DELETED) + { + return EventType.CHILD_ASSOC_DELETED; + } + else + { + // Default to first event + return eventTypes.getFirst(); + } + } + + /** + * Whether or not the child association has been created and then deleted, i.e. a temporary child association. + * + * @return {@code true} if the child association has been created and then deleted, otherwise false + */ + public boolean isTemporaryChildAssociation() + { + return eventTypes.contains(EventType.CHILD_ASSOC_CREATED) && eventTypes.getLast() == EventType.CHILD_ASSOC_DELETED; + } + + /** + * Get child association type. + * + * @return QName the child association type + */ + public QName getChildAssocType() + { + return childAssociationRef.getTypeQName(); + } + + /** + * Get event types. + * + * @return Deque queue of event types + */ + public Deque getEventTypes() + { + return eventTypes; + } +} diff --git a/src/main/java/org/alfresco/repo/event2/ChildAssociationEventSupportedPolicies.java b/src/main/java/org/alfresco/repo/event2/ChildAssociationEventSupportedPolicies.java new file mode 100644 index 0000000000..c0db206715 --- /dev/null +++ b/src/main/java/org/alfresco/repo/event2/ChildAssociationEventSupportedPolicies.java @@ -0,0 +1,41 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.repo.event2; + +import org.alfresco.repo.node.NodeServicePolicies; + +/** + * Event generator supported policies. + * + * @author Chris Shields + * @author Sara Aspery + */ +public interface ChildAssociationEventSupportedPolicies extends NodeServicePolicies.OnCreateChildAssociationPolicy, + NodeServicePolicies.BeforeDeleteChildAssociationPolicy +{ +} + diff --git a/src/main/java/org/alfresco/repo/event2/EventConsolidator.java b/src/main/java/org/alfresco/repo/event2/EventConsolidator.java index 4a53857814..528d7b4789 100644 --- a/src/main/java/org/alfresco/repo/event2/EventConsolidator.java +++ b/src/main/java/org/alfresco/repo/event2/EventConsolidator.java @@ -28,6 +28,7 @@ package org.alfresco.repo.event2; import java.io.Serializable; import java.time.ZonedDateTime; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Deque; @@ -57,8 +58,8 @@ public class EventConsolidator implements EventSupportedPolicies { private final NodeResourceHelper helper; private final Deque eventTypes; - private final Set aspectsAdded; - private final Set aspectsRemoved; + private final List aspectsAdded; + private final List aspectsRemoved; private NodeResource.Builder resourceBuilder; private Map propertiesBefore; @@ -67,13 +68,14 @@ public class EventConsolidator implements EventSupportedPolicies private QName nodeType; private QName nodeTypeBefore; private List primaryHierarchyBefore; + private boolean resourceBeforeAllFieldsNull = true; public EventConsolidator(NodeResourceHelper nodeResourceHelper) { this.helper = nodeResourceHelper; this.eventTypes = new ArrayDeque<>(); - this.aspectsAdded = new HashSet<>(); - this.aspectsRemoved = new HashSet<>(); + this.aspectsAdded = new ArrayList<>(); + this.aspectsRemoved = new ArrayList<>(); } /** @@ -202,18 +204,42 @@ public class EventConsolidator implements EventSupportedPolicies public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { eventTypes.add(EventType.NODE_UPDATED); - aspectsAdded.add(aspectTypeQName); + addAspect(aspectTypeQName); createBuilderIfAbsent(nodeRef); } + void addAspect(QName aspectTypeQName) + { + if (aspectsRemoved.contains(aspectTypeQName)) + { + aspectsRemoved.remove(aspectTypeQName); + } + else + { + aspectsAdded.add(aspectTypeQName); + } + } + @Override public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) { eventTypes.add(EventType.NODE_UPDATED); - aspectsRemoved.add(aspectTypeQName); + removeAspect(aspectTypeQName); createBuilderIfAbsent(nodeRef); } + void removeAspect(QName aspectTypeQName) + { + if (aspectsAdded.contains(aspectTypeQName)) + { + aspectsAdded.remove(aspectTypeQName); + } + else + { + aspectsRemoved.add(aspectTypeQName); + } + } + private void setAfterProperties(Map after) { propertiesAfter = after; @@ -265,7 +291,7 @@ public class EventConsolidator implements EventSupportedPolicies { return null; } - + Builder builder = NodeResource.builder(); Map changedPropsBefore = getBeforeMapChanges(propertiesBefore, propertiesAfter); @@ -276,27 +302,33 @@ public class EventConsolidator implements EventSupportedPolicies if (!mappedProps.isEmpty()) { builder.setProperties(mappedProps); + resourceBeforeAllFieldsNull = false; } String name = (String) changedPropsBefore.get(ContentModel.PROP_NAME); if (name != null) { builder.setName(name); + resourceBeforeAllFieldsNull = false; } ContentInfo contentInfo = helper.getContentInfo(changedPropsBefore); if (contentInfo != null) { builder.setContent(contentInfo); + resourceBeforeAllFieldsNull = false; } + UserInfo modifier = helper.getUserInfo((String) changedPropsBefore.get(ContentModel.PROP_MODIFIER)); if (modifier != null) { builder.setModifiedByUser(modifier); + resourceBeforeAllFieldsNull = false; } ZonedDateTime modifiedAt = helper.getZonedDateTime((Date) changedPropsBefore.get(ContentModel.PROP_MODIFIED)); if (modifiedAt != null) { builder.setModifiedAt(modifiedAt); + resourceBeforeAllFieldsNull = false; } } @@ -304,22 +336,25 @@ public class EventConsolidator implements EventSupportedPolicies if (!aspectsBefore.isEmpty()) { builder.setAspectNames(aspectsBefore); + resourceBeforeAllFieldsNull = false; } if (primaryHierarchyBefore != null && !primaryHierarchyBefore.isEmpty()) { builder.setPrimaryHierarchy(primaryHierarchyBefore); + resourceBeforeAllFieldsNull = false; } if (nodeTypeBefore != null) { builder.setNodeType(helper.getQNamePrefixString(nodeTypeBefore)); + resourceBeforeAllFieldsNull = false; } - + return builder.build(); } - - private Set getMappedAspectsBefore(Set currentAspects) + + Set getMappedAspectsBefore(Set currentAspects) { if (currentAspects == null) { @@ -329,17 +364,21 @@ public class EventConsolidator implements EventSupportedPolicies { Set removed = helper.mapToNodeAspects(aspectsRemoved); Set added = helper.mapToNodeAspects(aspectsAdded); - - Set before = new HashSet<>(currentAspects); - if (!removed.isEmpty()) + + Set before = new HashSet<>(); + if (!removed.isEmpty() || !added.isEmpty()) { - // Add all the removed aspects from the current list - before.addAll(removed); - } - if (!added.isEmpty()) - { - // Remove all the added aspects from the current list - before.removeAll(added); + before = new HashSet<>(currentAspects); + if (!removed.isEmpty()) + { + // Add all the removed aspects from the current list + before.addAll(removed); + } + if (!added.isEmpty()) + { + // Remove all the added aspects from the current list + before.removeAll(added); + } } return before; } @@ -348,7 +387,12 @@ public class EventConsolidator implements EventSupportedPolicies private boolean hasChangedAspect() { - return !(aspectsRemoved.isEmpty() && aspectsAdded.isEmpty()); + if ((aspectsRemoved.isEmpty() && aspectsAdded.isEmpty()) || + org.apache.commons.collections.CollectionUtils.isEqualCollection(aspectsAdded, aspectsRemoved)) + { + return false; + } + return true; } private Map getBeforeMapChanges(Map before, Map after) @@ -413,4 +457,20 @@ public class EventConsolidator implements EventSupportedPolicies { return eventTypes; } -} \ No newline at end of file + + + public List getAspectsAdded() + { + return aspectsAdded; + } + + public List getAspectsRemoved() + { + return aspectsRemoved; + } + + public boolean isResourceBeforeAllFieldsNull() + { + return resourceBeforeAllFieldsNull; + } +} diff --git a/src/main/java/org/alfresco/repo/event2/EventGenerator.java b/src/main/java/org/alfresco/repo/event2/EventGenerator.java index 9fca423e28..6d9581ad35 100644 --- a/src/main/java/org/alfresco/repo/event2/EventGenerator.java +++ b/src/main/java/org/alfresco/repo/event2/EventGenerator.java @@ -28,16 +28,22 @@ package org.alfresco.repo.event2; import java.io.Serializable; import java.net.URI; import java.time.ZonedDateTime; +import java.util.Deque; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import org.alfresco.repo.event.v1.model.RepoEvent; +import org.alfresco.repo.event2.filter.ChildAssociationTypeFilter; import org.alfresco.repo.event2.filter.EventFilterRegistry; import org.alfresco.repo.event2.filter.EventUserFilter; import org.alfresco.repo.event2.filter.NodeTypeFilter; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteAssociationPolicy; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnAddAspectPolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateAssociationPolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnDownloadNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy; @@ -50,6 +56,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -71,7 +78,9 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; * * @author Jamal Kaabi-Mofrad */ -public class EventGenerator extends AbstractLifecycleBean implements InitializingBean, EventSupportedPolicies +public class EventGenerator extends AbstractLifecycleBean implements InitializingBean, EventSupportedPolicies, + ChildAssociationEventSupportedPolicies, + PeerAssociationEventSupportedPolicies { private static final Log LOGGER = LogFactory.getLog(EventGenerator.class); @@ -86,8 +95,10 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin private PersonService personService; private NodeTypeFilter nodeTypeFilter; + private ChildAssociationTypeFilter childAssociationTypeFilter; private EventUserFilter userFilter; private NodeResourceHelper nodeResourceHelper; + private QNameHelper qNameHelper; private final EventTransactionListener transactionListener = new EventTransactionListener(); @Override @@ -104,10 +115,13 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin PropertyCheck.mandatory(this, "personService", personService); this.nodeTypeFilter = eventFilterRegistry.getNodeTypeFilter(); + this.childAssociationTypeFilter = eventFilterRegistry.getChildAssociationTypeFilter(); this.userFilter = eventFilterRegistry.getEventUserFilter(); - this.nodeResourceHelper = new NodeResourceHelper(nodeService, namespaceService, dictionaryService, - personService, - eventFilterRegistry); + this.qNameHelper = new QNameHelper(namespaceService); + this.nodeResourceHelper = new NodeResourceHelper(nodeService, dictionaryService, + personService, + eventFilterRegistry, + qNameHelper); } private void bindBehaviours() @@ -128,6 +142,14 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin new JavaBehaviour(this, "onMoveNode")); policyComponent.bindClassBehaviour(OnDownloadNodePolicy.QNAME, this, new JavaBehaviour(this, "onDownloadNode")); + policyComponent.bindAssociationBehaviour(OnCreateChildAssociationPolicy.QNAME, this, + new JavaBehaviour(this, "onCreateChildAssociation")); + policyComponent.bindAssociationBehaviour(BeforeDeleteChildAssociationPolicy.QNAME, this, + new JavaBehaviour(this, "beforeDeleteChildAssociation")); + policyComponent.bindAssociationBehaviour(OnCreateAssociationPolicy.QNAME, this, + new JavaBehaviour(this, "onCreateAssociation")); + policyComponent.bindAssociationBehaviour(BeforeDeleteAssociationPolicy.QNAME, this, + new JavaBehaviour(this, "beforeDeleteAssociation")); } public void setPolicyComponent(PolicyComponent policyComponent) @@ -204,7 +226,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin { getEventConsolidator(nodeRef).onDownloadNode(nodeRef); } - + @Override public void beforeDeleteNode(NodeRef nodeRef) { @@ -223,13 +245,38 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin getEventConsolidator(nodeRef).onRemoveAspect(nodeRef, aspectTypeQName); } + @Override + public void onCreateChildAssociation(ChildAssociationRef childAssociationRef, boolean isNewNode) + { + getEventConsolidator(childAssociationRef).onCreateChildAssociation(childAssociationRef, isNewNode); + } + + @Override + public void beforeDeleteChildAssociation(ChildAssociationRef childAssociationRef) + { + getEventConsolidator(childAssociationRef).beforeDeleteChildAssociation(childAssociationRef); + } + + @Override + public void onCreateAssociation(AssociationRef associationRef) + { + getEventConsolidator(associationRef).onCreateAssociation(associationRef); + } + + @Override + public void beforeDeleteAssociation(AssociationRef associationRef) + { + getEventConsolidator(associationRef).beforeDeleteAssociation(associationRef); + } + /** * @return the {@link EventConsolidator} for the supplied {@code nodeRef} from * the current transaction context. */ private EventConsolidator getEventConsolidator(NodeRef nodeRef) { - Map nodeEvents = getTxnResourceMap(transactionListener); + Consolidators consolidators = getTxnConsolidators(transactionListener); + Map nodeEvents = consolidators.getNodes(); if (nodeEvents.isEmpty()) { AlfrescoTransactionSupport.bindListener(transactionListener); @@ -244,15 +291,60 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin return eventConsolidator; } - private Map getTxnResourceMap(Object resourceKey) + + private Consolidators getTxnConsolidators(Object resourceKey) { - Map map = AlfrescoTransactionSupport.getResource(resourceKey); - if (map == null) + Consolidators consolidators = AlfrescoTransactionSupport.getResource(resourceKey); + if (consolidators == null) { - map = new LinkedHashMap<>(29); - AlfrescoTransactionSupport.bindResource(resourceKey, map); + consolidators = new Consolidators(); + AlfrescoTransactionSupport.bindResource(resourceKey, consolidators); } - return map; + return consolidators; + } + + /** + * @return the {@link EventConsolidator} for the supplied {@code childAssociationRef} from + * the current transaction context. + */ + private ChildAssociationEventConsolidator getEventConsolidator(ChildAssociationRef childAssociationRef) + { + Consolidators consolidators = getTxnConsolidators(transactionListener); + Map assocEvents = consolidators.getChildAssocs(); + if (assocEvents.isEmpty()) + { + AlfrescoTransactionSupport.bindListener(transactionListener); + } + + ChildAssociationEventConsolidator eventConsolidator = assocEvents.get(childAssociationRef); + if (eventConsolidator == null) + { + eventConsolidator = new ChildAssociationEventConsolidator(childAssociationRef, qNameHelper); + assocEvents.put(childAssociationRef, eventConsolidator); + } + return eventConsolidator; + } + + /** + * @return the {@link EventConsolidator} for the supplied {@code peerAssociationRef} from + * the current transaction context. + */ + private PeerAssociationEventConsolidator getEventConsolidator(AssociationRef peerAssociationRef) + { + Consolidators consolidators = getTxnConsolidators(transactionListener); + Map assocEvents = consolidators.getPeerAssocs(); + if (assocEvents.isEmpty()) + { + AlfrescoTransactionSupport.bindListener(transactionListener); + } + + PeerAssociationEventConsolidator eventConsolidator = assocEvents.get(peerAssociationRef); + if (eventConsolidator == null) + { + eventConsolidator = new PeerAssociationEventConsolidator(peerAssociationRef, qNameHelper); + assocEvents.put(peerAssociationRef, eventConsolidator); + } + return eventConsolidator; } private boolean isFiltered(QName nodeType, String user) @@ -260,6 +352,11 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin return (nodeTypeFilter.isExcluded(nodeType) || (userFilter.isExcluded(user))); } + private boolean isFilteredChildAssociation(QName childAssocType, String user) + { + return (childAssociationTypeFilter.isExcluded(childAssocType) || (userFilter.isExcluded(user))); + } + private EventInfo getEventInfo(String user) { return new EventInfo().setTimestamp(ZonedDateTime.now()) @@ -269,47 +366,6 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin .setSource(URI.create("/" + descriptorService.getCurrentRepositoryDescriptor().getId())); } - private void sendEvent(NodeRef nodeRef, EventConsolidator consolidator) - { - if (consolidator.isTemporaryNode()) - { - if (LOGGER.isTraceEnabled()) - { - LOGGER.trace("Ignoring temporary node: " + nodeRef); - } - return; - } - - final String user = AuthenticationUtil.getFullyAuthenticatedUser(); - // Get the repo event before the filtering, - // so we can take the latest node info into account - final RepoEvent event = consolidator.getRepoEvent(getEventInfo(user)); - - final QName nodeType = consolidator.getNodeType(); - if (isFiltered(nodeType, user)) - { - if (LOGGER.isTraceEnabled()) - { - LOGGER.trace("EventFilter - Excluding node: '" + nodeRef + "' of type: '" - + ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString()) - + "' created by: " + user); - } - return; - } - - if (LOGGER.isTraceEnabled()) - { - LOGGER.trace("List of Events:" + consolidator.getEventTypes()); - LOGGER.trace("Sending event:" + event); - } - // Need to execute this in another read txn because Camel expects it - transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback) () -> { - event2MessageProducer.send(event); - - return null; - }, true, false); - } - @Override protected void onBootstrap(ApplicationEvent applicationEvent) { @@ -329,18 +385,184 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin { try { - final Map changedNodes = getTxnResourceMap(this); - for (Map.Entry entry : changedNodes.entrySet()) + final Consolidators consolidators = getTxnConsolidators(this); + + // Node events + for (Map.Entry entry : consolidators.getNodes().entrySet()) { EventConsolidator eventConsolidator = entry.getValue(); sendEvent(entry.getKey(), eventConsolidator); } - } + + // Child assoc events + for (Map.Entry entry : consolidators.getChildAssocs().entrySet()) + { + ChildAssociationEventConsolidator eventConsolidator = entry.getValue(); + sendEvent(entry.getKey(), eventConsolidator); + } + + // Peer assoc events + for (Map.Entry entry : consolidators.getPeerAssocs().entrySet()) + { + PeerAssociationEventConsolidator eventConsolidator = entry.getValue(); + sendEvent(entry.getKey(), eventConsolidator); + } + } catch (Exception e) { // Must consume the exception to protect other TransactionListeners LOGGER.error("Unexpected error while sending repository events", e); } } + + private void sendEvent(NodeRef nodeRef, EventConsolidator consolidator) + { + if (consolidator.isTemporaryNode()) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("Ignoring temporary node: " + nodeRef); + } + return; + } + + final String user = AuthenticationUtil.getFullyAuthenticatedUser(); + // Get the repo event before the filtering, + // so we can take the latest node info into account + final RepoEvent event = consolidator.getRepoEvent(getEventInfo(user)); + + + final QName nodeType = consolidator.getNodeType(); + if (isFiltered(nodeType, user)) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("EventFilter - Excluding node: '" + nodeRef + "' of type: '" + + ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString()) + + "' created by: " + user); + } + return; + } + + if (event.getType().equals(EventType.NODE_UPDATED.getType()) && consolidator.isResourceBeforeAllFieldsNull()) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("Ignoring node updated event as no fields have been updated: " + nodeRef); + } + return; + } + + logAndSendEvent(event, consolidator.getEventTypes()); + } + + private void sendEvent(ChildAssociationRef childAssociationRef, ChildAssociationEventConsolidator consolidator) + { + if (consolidator.isTemporaryChildAssociation()) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("Ignoring temporary child association: " + childAssociationRef); + } + return; + } + + final String user = AuthenticationUtil.getFullyAuthenticatedUser(); + // Get the repo event before the filtering, + // so we can take the latest association info into account + final RepoEvent event = consolidator.getRepoEvent(getEventInfo(user)); + + final QName childAssocType = consolidator.getChildAssocType(); + if (isFilteredChildAssociation(childAssocType, user)) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("EventFilter - Excluding child association: '" + childAssociationRef + "' of type: '" + + ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString()) + + "' created by: " + user); + } + return; + } else if (childAssociationRef.isPrimary()) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("EventFilter - Excluding primary child association: '" + childAssociationRef + "' of type: '" + + ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString()) + + "' created by: " + user); + } + return; + } + + logAndSendEvent(event, consolidator.getEventTypes()); + } + + private void sendEvent(AssociationRef peerAssociationRef, PeerAssociationEventConsolidator consolidator) + { + if (consolidator.isTemporaryPeerAssociation()) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("Ignoring temporary peer association: " + peerAssociationRef); + } + return; + } + + final String user = AuthenticationUtil.getFullyAuthenticatedUser(); + // Get the repo event before the filtering, + // so we can take the latest association info into account + final RepoEvent event = consolidator.getRepoEvent(getEventInfo(user)); + + logAndSendEvent(event, consolidator.getEventTypes()); + } + + private void logAndSendEvent(RepoEvent event, Deque listOfEvents) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("List of Events:" + listOfEvents); + LOGGER.trace("Sending event:" + event); + } + // Need to execute this in another read txn because Camel expects it + transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback) () -> { + event2MessageProducer.send(event); + + return null; + }, true, false); + } + } + + + private static class Consolidators + { + private Map nodes; + private Map childAssocs; + private Map peerAssocs; + + public Map getNodes() + { + if (nodes == null) + { + nodes = new LinkedHashMap<>(29); + } + return nodes; + } + + public Map getChildAssocs() + { + if (childAssocs == null) + { + childAssocs = new LinkedHashMap<>(29); + } + return childAssocs; + } + + public Map getPeerAssocs() + { + if (peerAssocs == null) + { + peerAssocs = new LinkedHashMap<>(29); + } + return peerAssocs; + } } } diff --git a/src/main/java/org/alfresco/repo/event2/EventType.java b/src/main/java/org/alfresco/repo/event2/EventType.java index 7ec89cb1c7..1220c9dbff 100644 --- a/src/main/java/org/alfresco/repo/event2/EventType.java +++ b/src/main/java/org/alfresco/repo/event2/EventType.java @@ -32,21 +32,23 @@ package org.alfresco.repo.event2; */ public enum EventType { - NODE_CREATED("Created"), NODE_UPDATED("Updated"), NODE_DELETED("Deleted"), NODE_DOWNLOADED("Downloaded"); + NODE_CREATED(EventTypeConst.CREATED, ContextType.NODE), NODE_UPDATED(EventTypeConst.UPDATED, ContextType.NODE), NODE_DELETED(EventTypeConst.DELETED, ContextType.NODE), NODE_DOWNLOADED("Downloaded", ContextType.NODE), + CHILD_ASSOC_CREATED(EventTypeConst.CREATED, ContextType.CHILD_ASSOC), CHILD_ASSOC_DELETED(EventTypeConst.DELETED, ContextType.CHILD_ASSOC), + PEER_ASSOC_CREATED(EventTypeConst.CREATED, ContextType.PEER_ASSOC), PEER_ASSOC_DELETED(EventTypeConst.DELETED, ContextType.PEER_ASSOC); private static final String PREFIX = "org.alfresco.event."; - private static final String CONTEXT = "node."; private String type; + private ContextType contextType; - EventType(String type) + EventType(String type, ContextType contextType) { this.type = type; + this.contextType = contextType; } - // Should be overridden if a type requires different context. E.g. auth /* package*/ String getContext() { - return CONTEXT; + return contextType.getContext(); } @Override @@ -64,5 +66,27 @@ public enum EventType { return toString(); } + + private enum ContextType + { + NODE("node."), CHILD_ASSOC("assoc.child."), PEER_ASSOC("assoc.peer."); + private String context; + ContextType(String context) + { + this.context = context; + } + + String getContext() + { + return context; + } + } + + private static class EventTypeConst + { + private static final String CREATED = "Created"; + private static final String UPDATED = "Updated"; + private static final String DELETED = "Deleted"; + } } diff --git a/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java b/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java index 5dea5ba485..1af39e2d61 100644 --- a/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java +++ b/src/main/java/org/alfresco/repo/event2/NodeResourceHelper.java @@ -28,8 +28,7 @@ package org.alfresco.repo.event2; import java.io.Serializable; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -54,8 +53,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.security.NoSuchPersonException; import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.namespace.NamespaceException; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.PathUtil; import org.apache.commons.logging.Log; @@ -71,22 +68,23 @@ public class NodeResourceHelper private static final Log LOGGER = LogFactory.getLog(NodeResourceHelper.class); private final NodeService nodeService; - private final NamespaceService namespaceService; private final DictionaryService dictionaryService; private final PersonService personService; private final NodeAspectFilter nodeAspectFilter; private final NodePropertyFilter nodePropertyFilter; + private final QNameHelper qNameHelper; - public NodeResourceHelper(NodeService nodeService, NamespaceService namespaceService, + public NodeResourceHelper(NodeService nodeService, DictionaryService dictionaryService, PersonService personService, - EventFilterRegistry eventFilterRegistry) + EventFilterRegistry eventFilterRegistry, + QNameHelper qNameHelper) { this.nodeService = nodeService; - this.namespaceService = namespaceService; this.dictionaryService = dictionaryService; this.personService = personService; this.nodeAspectFilter = eventFilterRegistry.getNodeAspectFilter(); this.nodePropertyFilter = eventFilterRegistry.getNodePropertyFilter(); + this.qNameHelper = qNameHelper; } public NodeResource.Builder createNodeResourceBuilder(NodeRef nodeRef) @@ -227,19 +225,10 @@ public class NodeResourceHelper // returns it in the form {uri}local. public String getQNamePrefixString(QName k) { - String key; - try - { - key = k.toPrefixString(namespaceService); - } - catch (NamespaceException e) - { - key = k.toString(); - } - return key; + return qNameHelper.getQNamePrefixString(k); } - public Set mapToNodeAspects(Set aspects) + public Set mapToNodeAspects(Collection aspects) { Set filteredAspects = new HashSet<>(aspects.size()); diff --git a/src/main/java/org/alfresco/repo/event2/PeerAssociationEventConsolidator.java b/src/main/java/org/alfresco/repo/event2/PeerAssociationEventConsolidator.java new file mode 100644 index 0000000000..c708ee9a94 --- /dev/null +++ b/src/main/java/org/alfresco/repo/event2/PeerAssociationEventConsolidator.java @@ -0,0 +1,170 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.event2; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.alfresco.repo.event.v1.model.EventData; +import org.alfresco.repo.event.v1.model.PeerAssociationResource; +import org.alfresco.repo.event.v1.model.RepoEvent; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.namespace.QName; + +/** + * Encapsulates peer association events occurred in a single transaction. + * + * @author Sara Aspery + */ +public class PeerAssociationEventConsolidator implements PeerAssociationEventSupportedPolicies +{ + private final Deque eventTypes; + + private final AssociationRef associationRef; + private PeerAssociationResource resource; + private final QNameHelper helper; + + public PeerAssociationEventConsolidator(AssociationRef associationRef, QNameHelper helper) + { + this.eventTypes = new ArrayDeque<>(); + this.associationRef = associationRef; + this.helper = helper; + } + + /** + * Builds and returns the {@link RepoEvent} instance. + * + * @param eventInfo the object holding the event information + * @return the {@link RepoEvent} instance + */ + public RepoEvent getRepoEvent(EventInfo eventInfo) + { + EventType eventType = getDerivedEvent(); + + EventData.Builder eventDataBuilder = EventData.builder() + .setEventGroupId(eventInfo.getTxnId()) + .setResource(resource); + + EventData eventData = eventDataBuilder.build(); + return RepoEvent.builder() + .setId(eventInfo.getId()) + .setSource(eventInfo.getSource()) + .setTime(eventInfo.getTimestamp()) + .setType(eventType.getType()) + .setData(eventData) + .build(); + } + + /** + * Add peer association created event on create of a peer association. + * + * @param associationRef AssociationRef + */ + @Override + public void onCreateAssociation(AssociationRef associationRef) + { + eventTypes.add(EventType.PEER_ASSOC_CREATED); + resource = buildPeerAssociationResource(associationRef); + } + + /** + * Add peer association deleted event on delete of a peer association. + * + * @param associationRef AssociationRef + */ + @Override + public void beforeDeleteAssociation(AssociationRef associationRef) + { + eventTypes.add(EventType.PEER_ASSOC_DELETED); + resource = buildPeerAssociationResource(associationRef); + } + + private PeerAssociationResource buildPeerAssociationResource(AssociationRef associationRef) + { + String sourceId = associationRef.getSourceRef().getId(); + String targetId = associationRef.getTargetRef().getId(); + String assocType = helper.getQNamePrefixString(associationRef.getTypeQName()); + + return new PeerAssociationResource(sourceId, targetId, assocType); + } + + /** + * @return a derived event for a transaction. + */ + private EventType getDerivedEvent() + { + if (isTemporaryPeerAssociation()) + { + // This event will be filtered out, but we set the correct + // event type anyway for debugging purposes + return EventType.PEER_ASSOC_DELETED; + } + else if (eventTypes.contains(EventType.PEER_ASSOC_CREATED)) + { + return EventType.PEER_ASSOC_CREATED; + } + else if (eventTypes.getLast() == EventType.PEER_ASSOC_DELETED) + { + return EventType.PEER_ASSOC_DELETED; + } + else + { + // Default to first event + return eventTypes.getFirst(); + } + } + + /** + * Whether or not the association has been created and then deleted, i.e. a temporary association. + * + * @return {@code true} if the association has been created and then deleted, otherwise false + */ + public boolean isTemporaryPeerAssociation() + { + return eventTypes.contains(EventType.PEER_ASSOC_CREATED) && eventTypes.getLast() == EventType.PEER_ASSOC_DELETED; + } + + /** + * Get peer association type. + * + * @return QName the peer association type + */ + public QName getAssocType() + { + return associationRef.getTypeQName(); + } + + /** + * Get event types. + * + * @return Deque queue of event types + */ + public Deque getEventTypes() + { + return eventTypes; + } +} + diff --git a/src/main/java/org/alfresco/repo/event2/PeerAssociationEventSupportedPolicies.java b/src/main/java/org/alfresco/repo/event2/PeerAssociationEventSupportedPolicies.java new file mode 100644 index 0000000000..eb9171cee6 --- /dev/null +++ b/src/main/java/org/alfresco/repo/event2/PeerAssociationEventSupportedPolicies.java @@ -0,0 +1,39 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.repo.event2; + +import org.alfresco.repo.node.NodeServicePolicies; + +/** + * Event generator supported policies for peer associations. + * + * @author Sara Aspery + */ +public interface PeerAssociationEventSupportedPolicies extends NodeServicePolicies.OnCreateAssociationPolicy, + NodeServicePolicies.BeforeDeleteAssociationPolicy +{ +} diff --git a/src/main/java/org/alfresco/repo/event2/QNameHelper.java b/src/main/java/org/alfresco/repo/event2/QNameHelper.java new file mode 100644 index 0000000000..3f463ff8f2 --- /dev/null +++ b/src/main/java/org/alfresco/repo/event2/QNameHelper.java @@ -0,0 +1,66 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.event2; + +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Helper for {@link QName} objects. + * + * @author Sara Aspery + */ +public class QNameHelper +{ + private final NamespaceService namespaceService; + + public QNameHelper(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Returns the QName in the format prefix:local, but in the exceptional case where there is no registered prefix + * returns it in the form {uri}local. + * + * @param k QName + * @return a String representing the QName in the format prefix:local or {uri}local. + */ + public String getQNamePrefixString(QName k) + { + String key; + try + { + key = k.toPrefixString(namespaceService); + } + catch (NamespaceException e) + { + key = k.toString(); + } + return key; + } +} diff --git a/src/main/java/org/alfresco/repo/event2/filter/ChildAssociationTypeFilter.java b/src/main/java/org/alfresco/repo/event2/filter/ChildAssociationTypeFilter.java new file mode 100644 index 0000000000..dc6466240f --- /dev/null +++ b/src/main/java/org/alfresco/repo/event2/filter/ChildAssociationTypeFilter.java @@ -0,0 +1,63 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.event2.filter; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.service.namespace.QName; + +/** + * Implementation of the child association types filter. + * + * @author Sara Aspery + */ +public class ChildAssociationTypeFilter extends AbstractNodeEventFilter +{ + private final List assocTypesBlackList; + + public ChildAssociationTypeFilter(String filteredChildAssocTypes) + { + this.assocTypesBlackList = parseFilterList(filteredChildAssocTypes); + } + + /** + * + * @see org.alfresco.repo.event2.filter.AbstractNodeEventFilter#getExcludedTypes() + */ + @Override + public Set getExcludedTypes() + { + Set result = new HashSet<>(); + + // add child association types defined in repository.properties/alfresco-global.properties + assocTypesBlackList.forEach(childAssocType -> result.addAll(expandTypeDef(childAssocType))); + + return result; + } + +} diff --git a/src/main/java/org/alfresco/repo/event2/filter/EventFilterRegistry.java b/src/main/java/org/alfresco/repo/event2/filter/EventFilterRegistry.java index 371b6687c0..74c8cffb0a 100644 --- a/src/main/java/org/alfresco/repo/event2/filter/EventFilterRegistry.java +++ b/src/main/java/org/alfresco/repo/event2/filter/EventFilterRegistry.java @@ -85,6 +85,11 @@ public class EventFilterRegistry implements BeanFactoryAware return getFilter("event2NodePropertyFilter", NodePropertyFilter.class); } + public ChildAssociationTypeFilter getChildAssociationTypeFilter() + { + return getFilter("event2ChildAssociationTypeFilter", ChildAssociationTypeFilter.class); + } + public EventUserFilter getEventUserFilter() { return getFilter("event2UserFilter", EventUserFilter.class); diff --git a/src/main/resources/alfresco/events2-context.xml b/src/main/resources/alfresco/events2-context.xml index f170df4b5a..ecc12882f5 100644 --- a/src/main/resources/alfresco/events2-context.xml +++ b/src/main/resources/alfresco/events2-context.xml @@ -25,6 +25,10 @@ + + + + diff --git a/src/main/resources/alfresco/repository.properties b/src/main/resources/alfresco/repository.properties index 8c06bf3947..cbaa1de777 100644 --- a/src/main/resources/alfresco/repository.properties +++ b/src/main/resources/alfresco/repository.properties @@ -1314,7 +1314,8 @@ contentPropertyRestrictions.whitelist= # Type and aspect filters which should be excluded # Note: System folders node types are added by default repo.event2.filter.nodeTypes=sys:*, fm:*, cm:thumbnail, cm:failedThumbnail, cm:rating, rma:rmsite include_subtypes -repo.event2.filter.nodeAspects= +repo.event2.filter.nodeAspects=sys:* +repo.event2.filter.childAssocTypes=rn:rendition # Comma separated list of users which should be excluded # Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting repo.event2.filter.users=System, null diff --git a/src/test/java/org/alfresco/AllUnitTestsSuite.java b/src/test/java/org/alfresco/AllUnitTestsSuite.java index 1f6c8e2cac..6b6a8911ce 100644 --- a/src/test/java/org/alfresco/AllUnitTestsSuite.java +++ b/src/test/java/org/alfresco/AllUnitTestsSuite.java @@ -205,7 +205,8 @@ import org.junit.runners.Suite; org.alfresco.transform.client.registry.TransformServiceRegistryConfigTest.class, org.alfresco.repo.event2.EventFilterTest.class, - org.alfresco.repo.node.DownloadNotifierServiceImplUnitTest.class + org.alfresco.repo.node.DownloadNotifierServiceImplUnitTest.class, + org.alfresco.repo.event2.EventConsolidatorUnitTest.class }) public class AllUnitTestsSuite { diff --git a/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java b/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java index 392a1a3355..cd3e57b7f8 100644 --- a/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java +++ b/src/test/java/org/alfresco/repo/event2/AbstractContextAwareRepoEvent.java @@ -37,8 +37,10 @@ import javax.jms.ConnectionFactory; import org.alfresco.model.ContentModel; import org.alfresco.opencmis.CMISConnector; import org.alfresco.repo.event.databind.ObjectMapperFactory; +import org.alfresco.repo.event.v1.model.ChildAssociationResource; import org.alfresco.repo.event.v1.model.EventData; import org.alfresco.repo.event.v1.model.NodeResource; +import org.alfresco.repo.event.v1.model.PeerAssociationResource; import org.alfresco.repo.event.v1.model.RepoEvent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -237,6 +239,28 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest return resource; } + protected ChildAssociationResource getChildAssocResource(RepoEvent repoEvent) + { + assertNotNull(repoEvent); + EventData eventData = repoEvent.getData(); + assertNotNull(eventData); + ChildAssociationResource resource = eventData.getResource(); + assertNotNull(resource); + + return resource; + } + + protected PeerAssociationResource getPeerAssocResource(RepoEvent repoEvent) + { + assertNotNull(repoEvent); + EventData eventData = repoEvent.getData(); + assertNotNull(eventData); + PeerAssociationResource resource = eventData.getResource(); + assertNotNull(resource); + + return resource; + } + protected NodeResource getNodeResourceBefore(int eventSequenceNumber) { EventData eventData = getEventData(eventSequenceNumber); @@ -339,6 +363,15 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest events.clear(); } } + + public List> getChildAssocEvents(RepoEventContainer repoEventContainer, EventType eventType) + { + List> assocChildCreatedEvents = new ArrayList<>(); + for (int i = 1; i <= repoEventContainer.getEvents().size(); i++) + { + if (repoEventContainer.getEvent(i).getType().equals(eventType.getType())) + assocChildCreatedEvents.add(repoEventContainer.getEvent(i)); + } + return assocChildCreatedEvents; + } } - - diff --git a/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java b/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java new file mode 100644 index 0000000000..4b2559df0c --- /dev/null +++ b/src/test/java/org/alfresco/repo/event2/ChildAssociationRepoEventIT.java @@ -0,0 +1,723 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.repo.event2; + +import java.util.Arrays; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.event.v1.model.ChildAssociationResource; +import org.alfresco.repo.event.v1.model.EventData; +import org.alfresco.repo.event.v1.model.NodeResource; +import org.alfresco.repo.event.v1.model.RepoEvent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Adina Ababei + * @author Iulian Aftene + */ +public class ChildAssociationRepoEventIT extends AbstractContextAwareRepoEvent +{ + private RepoEventContainer repoEventsContainer; + + @Before + public void initContainer() + { + repoEventsContainer = getRepoEventsContainer(); + repoEventsContainer.reset(); + } + + @Test + public void testAddChildAssociation() + { + final NodeRef parentNodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(2); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), + resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), + resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild( + parentNodeRef, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + + List childAssociationRefs = retryingTransactionHelper.doInTransaction(() -> + nodeService.getChildAssocs(parentNodeRef)); + + assertEquals(1, childAssociationRefs.size()); + assertFalse(childAssociationRefs.get(0).isPrimary()); + + checkNumOfEvents(3); + + final RepoEvent childAssocRepoEvent = getChildAssocEvents(repoEventsContainer, + EventType.CHILD_ASSOC_CREATED).get(0); + + assertEquals("Wrong repo event type.", EventType.CHILD_ASSOC_CREATED.getType(), childAssocRepoEvent.getType()); + assertNotNull("Repo event ID is not available.", childAssocRepoEvent.getId()); + assertNotNull("Source is not available", childAssocRepoEvent.getSource()); + assertEquals("Repo event source is not available.", + "/" + descriptorService.getCurrentRepositoryDescriptor().getId(), + childAssocRepoEvent.getSource().toString()); + assertNotNull("Repo event creation time is not available.", childAssocRepoEvent.getTime()); + assertEquals("Invalid repo event datacontenttype", "application/json", + childAssocRepoEvent.getDatacontenttype()); + assertEquals(EventData.JSON_SCHEMA, childAssocRepoEvent.getDataschema()); + + final EventData nodeResourceEventData = getEventData(childAssocRepoEvent); + // EventData attributes + assertNotNull("Event data group ID is not available. ", nodeResourceEventData.getEventGroupId()); + assertNull("resourceBefore property is not available", nodeResourceEventData.getResourceBefore()); + + final ChildAssociationResource childAssociationResource = getChildAssocResource(childAssocRepoEvent); + assertEquals("Wrong parent", parentNodeRef.getId(), childAssociationResource.getParent().getId()); + assertEquals("Wrong child", childNodeRef.getId(), childAssociationResource.getChild().getId()); + assertEquals("Wrong assoc type", "cm:contains", childAssociationResource.getAssocType()); + } + + @Test + public void testRemoveChildAssociation() + { + final NodeRef parentNodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(2); + RepoEvent parentRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), parentRepoEvent.getType()); + + RepoEvent childRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), childRepoEvent.getType()); + + ChildAssociationRef childAssociationRef = retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild( + parentNodeRef, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + + List childAssociationRefs = retryingTransactionHelper.doInTransaction(() -> + nodeService.getChildAssocs(parentNodeRef)); + + assertEquals(1, childAssociationRefs.size()); + assertFalse(childAssociationRefs.get(0).isPrimary()); + + checkNumOfEvents(3); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.removeChildAssociation(childAssociationRef)); + + childAssociationRefs = retryingTransactionHelper.doInTransaction(() -> + nodeService.getChildAssocs(parentNodeRef)); + + assertEquals(0, childAssociationRefs.size()); + + checkNumOfEvents(4); + + final RepoEvent childAssocRepoEvent = getChildAssocEvents(repoEventsContainer, + EventType.CHILD_ASSOC_DELETED).get(0); + + assertEquals("Wrong repo event type.", EventType.CHILD_ASSOC_DELETED.getType(), childAssocRepoEvent.getType()); + assertNotNull("Repo event ID is not available. ", childAssocRepoEvent.getId()); + assertNotNull("Source is not available", childAssocRepoEvent.getSource()); + assertEquals("Repo event source is not available. ", + "/" + descriptorService.getCurrentRepositoryDescriptor().getId(), + childAssocRepoEvent.getSource().toString()); + assertNotNull("Repo event creation time is not available. ", childAssocRepoEvent.getTime()); + assertEquals("Repo event datacontenttype", "application/json", childAssocRepoEvent.getDatacontenttype()); + assertEquals(EventData.JSON_SCHEMA, childAssocRepoEvent.getDataschema()); + + final EventData nodeResourceEventData = getEventData(childAssocRepoEvent); + // EventData attributes + assertNotNull("Event data group ID is not available. ", nodeResourceEventData.getEventGroupId()); + assertNull("resourceBefore property is not available", nodeResourceEventData.getResourceBefore()); + + final ChildAssociationResource childAssociationResource = getChildAssocResource(childAssocRepoEvent); + assertEquals("Wrong parent", parentNodeRef.getId(), childAssociationResource.getParent().getId()); + assertEquals("Wrong child", childNodeRef.getId(), childAssociationResource.getChild().getId()); + assertEquals("Wrong assoc type", "cm:contains", childAssociationResource.getAssocType()); + } + + @Test + public void testOneChildListOfParentsAssociations() + { + final NodeRef parent1NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent2NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent3NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + List parents = Arrays.asList(parent1NodeRef, parent2NodeRef, parent3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild( + parents, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent1 = nodeService.getChildAssocs( + parent1NodeRef); + List childAssocParent2 = nodeService.getChildAssocs( + parent2NodeRef); + List childAssocParent3 = nodeService.getChildAssocs( + parent3NodeRef); + + assertEquals(1, childAssocParent1.size()); + assertEquals(1, childAssocParent2.size()); + assertEquals(1, childAssocParent3.size()); + return null; + }); + + checkNumOfEvents(7); + + // 3 assoc.child.Created events should be created + + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_CREATED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + } + + @Test + public void testOneChildMultipleParentsSameTransaction() + { + final NodeRef parent1NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent2NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent3NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + List parents = Arrays.asList(parent1NodeRef, parent2NodeRef, parent3NodeRef); + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> { + for (NodeRef parent : parents) + { + nodeService.addChild(parent, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate())); + } + return null; + }); + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent1 = nodeService.getChildAssocs( + parent1NodeRef); + List childAssocParent2 = nodeService.getChildAssocs( + parent2NodeRef); + List childAssocParent3 = nodeService.getChildAssocs( + parent3NodeRef); + + assertEquals(1, childAssocParent1.size()); + assertEquals(1, childAssocParent2.size()); + assertEquals(1, childAssocParent3.size()); + return null; + }); + + // 3 assoc.child.Created events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_CREATED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + + // All events in the transaction should have the same eventGroupId + String assocEventGroupID1 = getEventData(childAssocEvents.get(0)).getEventGroupId(); + String assocEventGroupID2 = getEventData(childAssocEvents.get(1)).getEventGroupId(); + String assocEventGroupID3 = getEventData(childAssocEvents.get(2)).getEventGroupId(); + + assertEquals(assocEventGroupID1, assocEventGroupID2); + assertEquals(assocEventGroupID2, assocEventGroupID3); + } + + @Test + public void testOneChildMultipleParentsDifferentTransaction() + { + final NodeRef parent1NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent2NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent3NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + List parents = Arrays.asList(parent1NodeRef, parent2NodeRef, parent3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + for (NodeRef parent : parents) + { + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild( + parent, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + } + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent1 = nodeService.getChildAssocs( + parent1NodeRef); + List childAssocParent2 = nodeService.getChildAssocs( + parent2NodeRef); + List childAssocParent3 = nodeService.getChildAssocs( + parent3NodeRef); + + assertEquals(1, childAssocParent1.size()); + assertEquals(1, childAssocParent2.size()); + assertEquals(1, childAssocParent3.size()); + return null; + }); + + // 3 assoc.child.Created events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_CREATED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + + assertEquals(parent1NodeRef.getId(), getChildAssocResource(childAssocEvents.get(0)).getParent().getId()); + assertEquals(childNodeRef.getId(), getChildAssocResource(childAssocEvents.get(0)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(0)).getAssocType()); + + assertEquals(parent2NodeRef.getId(), getChildAssocResource(childAssocEvents.get(1)).getParent().getId()); + assertEquals(childNodeRef.getId(), getChildAssocResource(childAssocEvents.get(1)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(1)).getAssocType()); + + assertEquals(parent3NodeRef.getId(), getChildAssocResource(childAssocEvents.get(2)).getParent().getId()); + assertEquals(childNodeRef.getId(), getChildAssocResource(childAssocEvents.get(2)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(2)).getAssocType()); + } + + @Test + public void testOneParentMultipleChildrenSameTransaction() + { + final NodeRef parentNodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef child1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef child2NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef child3NodeRef = createNode(ContentModel.TYPE_CONTENT); + + List children = Arrays.asList(child1NodeRef, child2NodeRef, child3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> { + for (NodeRef child : children) + { + nodeService.addChild(parentNodeRef, child, ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate())); + } + return null; + }); + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent = nodeService.getChildAssocs(parentNodeRef); + + assertEquals(3, childAssocParent.size()); + return null; + }); + + // 3 assoc.child.Created events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_CREATED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + } + + @Test + public void testOneParentMultipleChildrenDifferentTransaction() + { + final NodeRef parentNodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef child1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef child2NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef child3NodeRef = createNode(ContentModel.TYPE_CONTENT); + + List children = Arrays.asList(child1NodeRef, child2NodeRef, child3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + for (NodeRef child : children) + { + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild(parentNodeRef, child, ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + } + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent = nodeService.getChildAssocs(parentNodeRef); + + assertEquals(3, childAssocParent.size()); + return null; + }); + + // 3 assoc.child.Created events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_CREATED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + + assertEquals(parentNodeRef.getId(), getChildAssocResource(childAssocEvents.get(0)).getParent().getId()); + assertEquals(child1NodeRef.getId(), getChildAssocResource(childAssocEvents.get(0)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(0)).getAssocType()); + + assertEquals(parentNodeRef.getId(), getChildAssocResource(childAssocEvents.get(1)).getParent().getId()); + assertEquals(child2NodeRef.getId(), getChildAssocResource(childAssocEvents.get(1)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(1)).getAssocType()); + + assertEquals(parentNodeRef.getId(), getChildAssocResource(childAssocEvents.get(2)).getParent().getId()); + assertEquals(child3NodeRef.getId(), getChildAssocResource(childAssocEvents.get(2)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(2)).getAssocType()); + } + + @Test + public void testDeleteAssociationsOneChildMultipleParentsSameTransaction() + { + final NodeRef parent1NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent2NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent3NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + List parents = Arrays.asList(parent1NodeRef, parent2NodeRef, parent3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild(parents, childNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + + List listChildAssociationRefs = retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent1 = nodeService.getChildAssocs( + parent1NodeRef); + List childAssocParent2 = nodeService.getChildAssocs( + parent2NodeRef); + List childAssocParent3 = nodeService.getChildAssocs( + parent3NodeRef); + + assertEquals(1, childAssocParent1.size()); + assertEquals(1, childAssocParent2.size()); + assertEquals(1, childAssocParent3.size()); + + return Arrays.asList(childAssocParent1.get(0), childAssocParent2.get(0), childAssocParent3.get(0)); + }); + + retryingTransactionHelper.doInTransaction(() -> { + for (ChildAssociationRef childAssociationRef : listChildAssociationRefs) + { + nodeService.removeChildAssociation(childAssociationRef); + } + return null; + }); + + checkNumOfEvents(10); + + // 3 assoc.child.Deleted events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_DELETED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + } + + @Test + public void testDeleteAssociationOneParentMultipleChildrenDifferentTransactions() + { + final NodeRef parent1NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent2NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent3NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + List parents = Arrays.asList(parent1NodeRef, parent2NodeRef, parent3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild(parents, childNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + + List listChildAssociationRefs = retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent1 = nodeService.getChildAssocs( + parent1NodeRef); + List childAssocParent2 = nodeService.getChildAssocs( + parent2NodeRef); + List childAssocParent3 = nodeService.getChildAssocs( + parent3NodeRef); + + assertEquals(1, childAssocParent1.size()); + assertEquals(1, childAssocParent2.size()); + assertEquals(1, childAssocParent3.size()); + + return Arrays.asList(childAssocParent1.get(0), childAssocParent2.get(0), childAssocParent3.get(0)); + }); + + for (ChildAssociationRef childAssociationRef : listChildAssociationRefs) + { + retryingTransactionHelper.doInTransaction(() -> + nodeService.removeChildAssociation(childAssociationRef)); + } + + checkNumOfEvents(10); + + // 3 assoc.child.Deleted events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_DELETED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + + assertEquals(parent1NodeRef.getId(), getChildAssocResource(childAssocEvents.get(0)).getParent().getId()); + assertEquals(childNodeRef.getId(), getChildAssocResource(childAssocEvents.get(0)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(0)).getAssocType()); + + assertEquals(parent2NodeRef.getId(), getChildAssocResource(childAssocEvents.get(1)).getParent().getId()); + assertEquals(childNodeRef.getId(), getChildAssocResource(childAssocEvents.get(1)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(1)).getAssocType()); + + assertEquals(parent3NodeRef.getId(), getChildAssocResource(childAssocEvents.get(2)).getParent().getId()); + assertEquals(childNodeRef.getId(), getChildAssocResource(childAssocEvents.get(2)).getChild().getId()); + assertEquals("cm:contains", getChildAssocResource(childAssocEvents.get(2)).getAssocType()); + } + + @Test + public void testDeleteParentWithMultipleChildAssociations() + { + final NodeRef parentNodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef child1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef child2NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef child3NodeRef = createNode(ContentModel.TYPE_CONTENT); + + List children = Arrays.asList(child1NodeRef, child2NodeRef, child3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> { + for (NodeRef child : children) + { + nodeService.addChild(parentNodeRef, child, ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate())); + } + + return null; + }); + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent = nodeService.getChildAssocs(parentNodeRef); + + assertEquals(3, childAssocParent.size()); + return null; + }); + + deleteNode(parentNodeRef); + + checkNumOfEvents(11); + + // 3 assoc.child.Deleted events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_DELETED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + } + + @Test + public void testDeleteChildWithMultipleParentAssociations() + { + final NodeRef parent1NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent2NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef parent3NodeRef = createNode(ContentModel.TYPE_FOLDER); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + List parents = Arrays.asList(parent1NodeRef, parent2NodeRef, parent3NodeRef); + + checkNumOfEvents(4); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(4); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.addChild(parents, childNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate()))); + + retryingTransactionHelper.doInTransaction(() -> { + List childAssocParent1 = nodeService.getChildAssocs( + parent1NodeRef); + List childAssocParent2 = nodeService.getChildAssocs( + parent2NodeRef); + List childAssocParent3 = nodeService.getChildAssocs( + parent3NodeRef); + + assertEquals(1, childAssocParent1.size()); + assertEquals(1, childAssocParent2.size()); + assertEquals(1, childAssocParent3.size()); + return null; + }); + + deleteNode(childNodeRef); + + checkNumOfEvents(11); + + // 3 assoc.child.Deleted events should be created + List> childAssocEvents = getChildAssocEvents(repoEventsContainer, EventType.CHILD_ASSOC_DELETED); + assertEquals("Wrong association events number",3, childAssocEvents.size()); + } + + @Test + public void testUpdateNodeAddChildAssociationNodeEventsFirst() + { + final NodeRef parentNodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef childNodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(2); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + { + nodeService.setType(parentNodeRef, ContentModel.TYPE_FOLDER); + + return nodeService.addChild( + parentNodeRef, + childNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(TEST_NAMESPACE, GUID.generate())); + }); + + List childAssociationRefs = retryingTransactionHelper.doInTransaction(() -> + nodeService.getChildAssocs(parentNodeRef)); + + assertEquals(1, childAssociationRefs.size()); + assertFalse(childAssociationRefs.get(0).isPrimary()); + + checkNumOfEvents(4); + + // Check the node events occur before the child association event + List> repoEvents = getRepoEventsContainer().getEvents(); + assertEquals("org.alfresco.event.node.Created", repoEvents.get(0).getType()); + assertEquals("org.alfresco.event.node.Created", repoEvents.get(1).getType()); + assertEquals("org.alfresco.event.node.Updated", repoEvents.get(2).getType()); + assertEquals("org.alfresco.event.assoc.child.Created", repoEvents.get(3).getType()); + } +} diff --git a/src/test/java/org/alfresco/repo/event2/EventConsolidatorUnitTest.java b/src/test/java/org/alfresco/repo/event2/EventConsolidatorUnitTest.java new file mode 100644 index 0000000000..25fb458837 --- /dev/null +++ b/src/test/java/org/alfresco/repo/event2/EventConsolidatorUnitTest.java @@ -0,0 +1,296 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.repo.event2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.junit.Test; + +public class EventConsolidatorUnitTest +{ + private NodeResourceHelper nodeResourceHelper = mock(NodeResourceHelper.class); + + @Test + public void testGetMappedAspectsBeforeRemovedAndAddedEmpty() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(0, mappedAspectsBefore.size()); + } + + @Test + public void testGetMappedAspectsBefore_AspectRemoved() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + + Set removed = new HashSet<>(); + Set added = new HashSet<>(); + removed.add("cm:contains"); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(3, mappedAspectsBefore.size()); + } + + @Test + public void testGetMappedAspectsBefore_AspectAdded() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + + Set removed = new HashSet<>(); + Set added = new HashSet<>(); + added.add("cm:auditable"); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(1, mappedAspectsBefore.size()); + } + + + @Test + public void testGetMappedAspectsBefore_AspectAddedAndRemoved() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + + Set removed = new HashSet<>(); + removed.add("cm:contains"); + Set added = new HashSet<>(); + added.add("cm:contains"); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(2, mappedAspectsBefore.size()); + } + + @Test + public void testGetMappedAspectsBefore_AspectRemovedAndAdded() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + currentAspects.add("cm:contains"); + + Set removed = new HashSet<>(); + removed.add("cm:contains"); + Set added = new HashSet<>(); + added.add("cm:contains"); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(0, mappedAspectsBefore.size()); + } + + @Test + public void testGetMappedAspectsBefore_AspectAddedTwiceRemovedOnce() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + currentAspects.add("cm:contains"); + + Set removed = new HashSet<>(); + Set added = new HashSet<>(); + added.add("cm:contains"); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(2, mappedAspectsBefore.size()); + } + + + @Test + public void testGetMappedAspectsBefore_AspectRemovedTwiceAddedOnce() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + currentAspects.add("cm:contains"); + + Set removed = new HashSet<>(); + removed.add("cm:contains"); + Set added = new HashSet<>(); + added.add("cm:contains"); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(2, mappedAspectsBefore.size()); + } + + @Test + public void testGetMappedAspectsBefore_FilteredAspectAdded() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASPECT_COPIEDFROM); + + Set currentAspects = new HashSet<>(); + currentAspects.add("cm:geographic"); + currentAspects.add("cm:auditable"); + + Set removed = new HashSet<>(); + Set added = new HashSet<>(); + + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsRemoved())).thenReturn(removed); + when(nodeResourceHelper.mapToNodeAspects(eventConsolidator.getAspectsAdded())).thenReturn(added); + + Set mappedAspectsBefore = eventConsolidator.getMappedAspectsBefore(currentAspects); + + assertEquals(0, mappedAspectsBefore.size()); + } + + @Test + public void testAddAspect() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + assertEquals(1, eventConsolidator.getAspectsAdded().size()); + assertEquals(0, eventConsolidator.getAspectsRemoved().size()); + assertTrue(eventConsolidator.getAspectsAdded().contains(ContentModel.ASSOC_CONTAINS)); + } + + @Test + public void testRemoveAspect() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + + assertEquals(0, eventConsolidator.getAspectsAdded().size()); + assertEquals(1, eventConsolidator.getAspectsRemoved().size()); + assertTrue(eventConsolidator.getAspectsRemoved().contains(ContentModel.ASSOC_CONTAINS)); + } + + @Test + public void testAddAspectRemoveAspect() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + + assertEquals(0, eventConsolidator.getAspectsAdded().size()); + assertEquals(0, eventConsolidator.getAspectsRemoved().size()); + } + + @Test + public void testRemoveAspectAddAspect() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + assertEquals(0, eventConsolidator.getAspectsAdded().size()); + assertEquals(0, eventConsolidator.getAspectsRemoved().size()); + } + + @Test + public void testAddAspectTwiceRemoveAspectOnce() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + + assertEquals(1, eventConsolidator.getAspectsAdded().size()); + assertEquals(0, eventConsolidator.getAspectsRemoved().size()); + assertTrue(eventConsolidator.getAspectsAdded().contains(ContentModel.ASSOC_CONTAINS)); + } + + @Test + public void testAddAspectOnceRemoveAspectTwice() + { + EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS); + eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS); + + assertEquals(0, eventConsolidator.getAspectsAdded().size()); + assertEquals(1, eventConsolidator.getAspectsRemoved().size()); + assertTrue(eventConsolidator.getAspectsRemoved().contains(ContentModel.ASSOC_CONTAINS)); + } +} diff --git a/src/test/java/org/alfresco/repo/event2/EventFilterTest.java b/src/test/java/org/alfresco/repo/event2/EventFilterTest.java index cf943b46f7..5110ec7472 100644 --- a/src/test/java/org/alfresco/repo/event2/EventFilterTest.java +++ b/src/test/java/org/alfresco/repo/event2/EventFilterTest.java @@ -37,6 +37,8 @@ import java.util.Collections; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.event2.filter.ChildAssociationTypeFilter; import org.alfresco.repo.event2.filter.EventUserFilter; import org.alfresco.repo.event2.filter.NodeAspectFilter; import org.alfresco.repo.event2.filter.NodePropertyFilter; @@ -61,6 +63,7 @@ public class EventFilterTest private static NodePropertyFilter propertyFilter; private static NodeTypeFilter typeFilter; private static NodeAspectFilter aspectFilter; + private static ChildAssociationTypeFilter childAssociationTypeFilter; private static EventUserFilter caseInsensitive_userFilter; private static EventUserFilter caseSensitive_userFilter; @@ -80,6 +83,8 @@ public class EventFilterTest NamespaceService.CONTENT_MODEL_1_0_URI); namespaceService.registerNamespace(NamespaceService.FORUMS_MODEL_PREFIX, NamespaceService.FORUMS_MODEL_1_0_URI); + namespaceService.registerNamespace(NamespaceService.RENDITION_MODEL_PREFIX, + NamespaceService.RENDITION_MODEL_1_0_URI); propertyFilter = new NodePropertyFilter(); propertyFilter.setNamespaceService(namespaceService); @@ -96,6 +101,11 @@ public class EventFilterTest aspectFilter.setDictionaryService(dictionaryService); aspectFilter.init(); + childAssociationTypeFilter = new ChildAssociationTypeFilter("rn:rendition"); + childAssociationTypeFilter.setNamespaceService(namespaceService); + childAssociationTypeFilter.setDictionaryService(dictionaryService); + childAssociationTypeFilter.init(); + caseInsensitive_userFilter = new EventUserFilter("System, john.doe, null", false); caseSensitive_userFilter = new EventUserFilter("System, john.doe, null", true); } @@ -139,6 +149,15 @@ public class EventFilterTest assertFalse(aspectFilter.isExcluded(ContentModel.ASPECT_TITLED)); } + @Test + public void childAssociationTypeFilter() + { + assertTrue("Rendition child association type should have been filtered.", + childAssociationTypeFilter.isExcluded(RenditionModel.ASSOC_RENDITION)); + + assertFalse(childAssociationTypeFilter.isExcluded(ContentModel.ASSOC_CONTAINS)); + } + @Test public void userFilter_case_insensitive() { diff --git a/src/test/java/org/alfresco/repo/event2/PeerAssociationRepoEventIT.java b/src/test/java/org/alfresco/repo/event2/PeerAssociationRepoEventIT.java new file mode 100644 index 0000000000..82b6785dfb --- /dev/null +++ b/src/test/java/org/alfresco/repo/event2/PeerAssociationRepoEventIT.java @@ -0,0 +1,374 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ + +package org.alfresco.repo.event2; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.event.v1.model.EventData; +import org.alfresco.repo.event.v1.model.NodeResource; +import org.alfresco.repo.event.v1.model.PeerAssociationResource; +import org.alfresco.repo.event.v1.model.RepoEvent; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.junit.Before; +import org.junit.Test; + +/** + * @author Chris Shields + */ +public class PeerAssociationRepoEventIT extends AbstractContextAwareRepoEvent +{ + private RepoEventContainer repoEventsContainer; + + @Before + public void initContainer() + { + repoEventsContainer = getRepoEventsContainer(); + repoEventsContainer.reset(); + } + + @Test + public void testAddPeerAssociation() + { + final NodeRef content1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content2NodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(2); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> + nodeService.createAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL)); + + List peerAssociationRefs = retryingTransactionHelper.doInTransaction( + () -> + nodeService.getSourceAssocs(content2NodeRef, ContentModel.ASSOC_ORIGINAL)); + assertEquals(1, peerAssociationRefs.size()); + + checkNumOfEvents(4); + + final RepoEvent peerAssocRepoEvent = getChildAssocEvents(repoEventsContainer, + EventType.PEER_ASSOC_CREATED).get(0); + + assertEquals("Wrong repo event type.", + EventType.PEER_ASSOC_CREATED.getType(), + peerAssocRepoEvent.getType()); + assertNotNull("Repo event ID is not available. ", peerAssocRepoEvent.getId()); + assertNotNull("Source is not available", peerAssocRepoEvent.getSource()); + assertEquals("Repo event source is not available. ", + "/" + descriptorService.getCurrentRepositoryDescriptor().getId(), + peerAssocRepoEvent.getSource().toString()); + assertNotNull("Repo event creation time is not available. ", peerAssocRepoEvent.getTime()); + assertEquals("Repo event datacontenttype", "application/json", + peerAssocRepoEvent.getDatacontenttype()); + assertEquals(EventData.JSON_SCHEMA, peerAssocRepoEvent.getDataschema()); + + final EventData nodeResourceEventData = getEventData(peerAssocRepoEvent); + // EventData attributes + assertNotNull("Event data group ID is not available. ", nodeResourceEventData.getEventGroupId()); + assertNull("resourceBefore property is not available", nodeResourceEventData.getResourceBefore()); + + final PeerAssociationResource peerAssociationResource = getPeerAssocResource(peerAssocRepoEvent); + assertEquals("Wrong source", content1NodeRef.getId(), peerAssociationResource.getSource().getId()); + assertEquals("Wrong target", content2NodeRef.getId(), peerAssociationResource.getTarget().getId()); + assertEquals("Wrong assoc type", "cm:original", peerAssociationResource.getAssocType()); + } + + @Test + public void testAddMultiplePeerAssociationSameTransaction() + { + final NodeRef content1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content2NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content3NodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(3); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> { + nodeService.createAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + + nodeService.createAssociation( + content3NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + return null; + }); + + + List peerAssociationRefs = retryingTransactionHelper.doInTransaction(() -> + nodeService.getSourceAssocs(content2NodeRef, ContentModel.ASSOC_ORIGINAL)); + + assertEquals(2, peerAssociationRefs.size()); + + checkNumOfEvents(7); + + List> peerAssocRepoEvent = getChildAssocEvents(repoEventsContainer, EventType.PEER_ASSOC_CREATED); + + // we should have 2 assoc.peer.Created events + assertEquals("Wrong association events number",2, peerAssocRepoEvent.size()); + } + + @Test + public void testAddMultiplePeerAssociationDifferentTransaction() + { + final NodeRef content1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content2NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content3NodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(3); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(3); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + retryingTransactionHelper.doInTransaction(() -> { + nodeService.createAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + return null; + }); + + retryingTransactionHelper.doInTransaction(() -> { + nodeService.createAssociation( + content3NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + return null; + }); + + List peerAssociationRefs = retryingTransactionHelper.doInTransaction(() -> + nodeService.getSourceAssocs(content2NodeRef, ContentModel.ASSOC_ORIGINAL)); + + assertEquals(2, peerAssociationRefs.size()); + + checkNumOfEvents(7); + + List> peerAssocRepoEvent = getChildAssocEvents(repoEventsContainer, EventType.PEER_ASSOC_CREATED); + + // we should have 2 assoc.peer.Created events + assertEquals("Wrong association events number",2, peerAssocRepoEvent.size()); + + assertEquals("Wrong source", + content1NodeRef.getId(), + getPeerAssocResource(peerAssocRepoEvent.get(0)).getSource().getId()); + assertEquals("Wrong target", + content2NodeRef.getId(), + getPeerAssocResource(peerAssocRepoEvent.get(0)).getTarget().getId()); + assertEquals("Wrong assoc type", + "cm:original", + getPeerAssocResource(peerAssocRepoEvent.get(0)).getAssocType()); + + assertEquals("Wrong source", + content3NodeRef.getId(), + getPeerAssocResource(peerAssocRepoEvent.get(1)).getSource().getId()); + assertEquals("Wrong target", + content2NodeRef.getId(), + getPeerAssocResource(peerAssocRepoEvent.get(1)).getTarget().getId()); + assertEquals("Wrong assoc type", + "cm:original", + getPeerAssocResource(peerAssocRepoEvent.get(1)).getAssocType()); + } + + + @Test + public void testRemovePeerAssociation() + { + final NodeRef content1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content2NodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(2); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), resultRepoEvent.getType()); + + // Create peer association + retryingTransactionHelper.doInTransaction(() -> + nodeService.createAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL)); + + List peerAssociationRefs = retryingTransactionHelper.doInTransaction( + () -> + nodeService.getSourceAssocs(content2NodeRef, ContentModel.ASSOC_ORIGINAL)); + assertEquals(1, peerAssociationRefs.size()); + + checkNumOfEvents(4); + + // Remove peer association + retryingTransactionHelper.doInTransaction(() -> + { + nodeService.removeAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + return null; + }); + + peerAssociationRefs = retryingTransactionHelper.doInTransaction( + () -> + nodeService.getSourceAssocs(content2NodeRef, ContentModel.ASSOC_ORIGINAL)); + assertEquals(0, peerAssociationRefs.size()); + + checkNumOfEvents(6); + + // Check the peer assoc created event + final RepoEvent peerAssocRepoEvent = getChildAssocEvents(repoEventsContainer, + EventType.PEER_ASSOC_CREATED).get(0); + + assertEquals("Wrong repo event type.", + EventType.PEER_ASSOC_CREATED.getType(), + peerAssocRepoEvent.getType()); + assertNotNull("Repo event ID is not available. ", peerAssocRepoEvent.getId()); + assertNotNull("Source is not available", peerAssocRepoEvent.getSource()); + assertEquals("Repo event source is not available. ", + "/" + descriptorService.getCurrentRepositoryDescriptor().getId(), + peerAssocRepoEvent.getSource().toString()); + assertNotNull("Repo event creation time is not available. ", peerAssocRepoEvent.getTime()); + assertEquals("Repo event datacontenttype", "application/json", + peerAssocRepoEvent.getDatacontenttype()); + assertEquals(EventData.JSON_SCHEMA, peerAssocRepoEvent.getDataschema()); + + final EventData nodeResourceEventData = getEventData(peerAssocRepoEvent); + // EventData attributes + assertNotNull("Event data group ID is not available. ", + nodeResourceEventData.getEventGroupId()); + assertNull("resourceBefore property is not available", + nodeResourceEventData.getResourceBefore()); + + final PeerAssociationResource peerAssociationResource = getPeerAssocResource( + peerAssocRepoEvent); + assertEquals("Wrong source", content1NodeRef.getId(), + peerAssociationResource.getSource().getId()); + assertEquals("Wrong target", content2NodeRef.getId(), + peerAssociationResource.getTarget().getId()); + assertEquals("Wrong assoc type", "cm:original", + peerAssociationResource.getAssocType()); + + // Check the peer assoc deleted event + final RepoEvent peerAssocRepoEvent2 = getChildAssocEvents(repoEventsContainer, + EventType.PEER_ASSOC_DELETED).get(0); + + assertEquals("Wrong repo event type.", + EventType.PEER_ASSOC_DELETED.getType(), + peerAssocRepoEvent2.getType()); + assertNotNull("Repo event ID is not available. ", peerAssocRepoEvent2.getId()); + assertNotNull("Source is not available", peerAssocRepoEvent2.getSource()); + assertEquals("Repo event source is not available. ", + "/" + descriptorService.getCurrentRepositoryDescriptor().getId(), + peerAssocRepoEvent2.getSource().toString()); + assertNotNull("Repo event creation time is not available. ", peerAssocRepoEvent2.getTime()); + assertEquals("Repo event datacontenttype", "application/json", + peerAssocRepoEvent2.getDatacontenttype()); + assertEquals(EventData.JSON_SCHEMA, peerAssocRepoEvent2.getDataschema()); + + final EventData nodeResourceEventData2 = getEventData(peerAssocRepoEvent2); + // EventData attributes + assertNotNull("Event data group ID is not available. ", + nodeResourceEventData2.getEventGroupId()); + assertNull("resourceBefore property is not available", + nodeResourceEventData2.getResourceBefore()); + + final PeerAssociationResource peerAssociationResource2 = getPeerAssocResource( + peerAssocRepoEvent2); + assertEquals("Wrong source", content1NodeRef.getId(), + peerAssociationResource2.getSource().getId()); + assertEquals("Wrong target", content2NodeRef.getId(), + peerAssociationResource2.getTarget().getId()); + assertEquals("Wrong assoc type", "cm:original", + peerAssociationResource2.getAssocType()); + } + + + @Test + public void testAddAndRemovePeerAssociationSameTransaction() + { + final NodeRef content1NodeRef = createNode(ContentModel.TYPE_CONTENT); + final NodeRef content2NodeRef = createNode(ContentModel.TYPE_CONTENT); + + checkNumOfEvents(2); + + RepoEvent resultRepoEvent = repoEventsContainer.getEvent(1); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), + resultRepoEvent.getType()); + + resultRepoEvent = repoEventsContainer.getEvent(2); + assertEquals("Wrong repo event type.", EventType.NODE_CREATED.getType(), + resultRepoEvent.getType()); + + // Create peer association + retryingTransactionHelper.doInTransaction(() -> + + { + nodeService.createAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + + nodeService.removeAssociation( + content1NodeRef, + content2NodeRef, + ContentModel.ASSOC_ORIGINAL); + return null; + }); + + List peerAssociationRefs = retryingTransactionHelper.doInTransaction( + () -> + nodeService.getSourceAssocs(content2NodeRef, ContentModel.ASSOC_ORIGINAL)); + assertEquals(0, peerAssociationRefs.size()); + + checkNumOfEvents(2); + } +} diff --git a/src/test/java/org/alfresco/repo/event2/RepoEvent2ITSuite.java b/src/test/java/org/alfresco/repo/event2/RepoEvent2ITSuite.java index ca328b56b1..6fdd4d8568 100644 --- a/src/test/java/org/alfresco/repo/event2/RepoEvent2ITSuite.java +++ b/src/test/java/org/alfresco/repo/event2/RepoEvent2ITSuite.java @@ -33,7 +33,9 @@ import org.junit.runners.Suite.SuiteClasses; @SuiteClasses({ org.alfresco.repo.event2.CreateRepoEventIT.class, org.alfresco.repo.event2.UpdateRepoEventIT.class, org.alfresco.repo.event2.DeleteRepoEventIT.class, - org.alfresco.repo.event2.DownloadRepoEventIT.class }) + org.alfresco.repo.event2.DownloadRepoEventIT.class, + org.alfresco.repo.event2.ChildAssociationRepoEventIT.class, + org.alfresco.repo.event2.PeerAssociationRepoEventIT.class }) public class RepoEvent2ITSuite { } diff --git a/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java b/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java index 25bcb70196..e3584259a3 100644 --- a/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java +++ b/src/test/java/org/alfresco/repo/event2/UpdateRepoEventIT.java @@ -25,6 +25,7 @@ */ package org.alfresco.repo.event2; + import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -76,6 +77,8 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent return null; }); + checkNumOfEvents(2); + resultRepoEvent = getRepoEvent(2); assertEquals("Wrong repo event type.", EventType.NODE_UPDATED.getType(), resultRepoEvent.getType()); @@ -152,6 +155,8 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent return null; }); + checkNumOfEvents(2); + resultRepoEvent = getRepoEvent(2); assertEquals("Wrong repo event type.", EventType.NODE_UPDATED.getType(), resultRepoEvent.getType()); @@ -608,6 +613,8 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent return null; }); + checkNumOfEvents(4); + NodeResource resourceBefore = getNodeResourceBefore(4); NodeResource resource = getNodeResource(4); @@ -628,7 +635,7 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertNull(resourceBefore.getCreatedAt()); assertNull(resourceBefore.getCreatedByUser()); assertNull(resourceBefore.getProperties()); - assertNotNull(resourceBefore.getAspectNames()); + assertNull(resourceBefore.getAspectNames()); assertNotNull(resourceBefore.getPrimaryHierarchy()); assertNull("Content should have been null.", resource.getContent()); assertNull("Content should have been null.", resourceBefore.getContent()); @@ -820,4 +827,62 @@ public class UpdateRepoEventIT extends AbstractContextAwareRepoEvent assertEquals("Wrong node parent.", folder2ID, moveFileParentAfterMove); } + + @Test + public void testAddAspectRemoveAspectFromContentSameTransactionTest() + { + final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT); + NodeResource resource = getNodeResource(1); + final Set originalAspects = resource.getAspectNames(); + assertNotNull(originalAspects); + + + retryingTransactionHelper.doInTransaction(() -> { + // Add cm:geographic aspect with default value + nodeService.addAspect(nodeRef, ContentModel.ASPECT_GEOGRAPHIC, null); + + // Remove cm:geographic aspect + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_GEOGRAPHIC); + return null; + }); + + checkNumOfEvents(1); + } + + @Test + public void testAddAspectRemoveAspectAddAspectFromContentSameTransactionTest() + { + final NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT); + NodeResource resource = getNodeResource(1); + final Set originalAspects = resource.getAspectNames(); + assertNotNull(originalAspects); + + retryingTransactionHelper.doInTransaction(() -> { + // Add cm:geographic aspect with default value + nodeService.addAspect(nodeRef, ContentModel.ASPECT_GEOGRAPHIC, null); + + // Remove cm:geographic aspect + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_GEOGRAPHIC); + + // Add cm:geographic aspect with default value + nodeService.addAspect(nodeRef, ContentModel.ASPECT_GEOGRAPHIC, null); + + return null; + }); + + checkNumOfEvents(2); + + resource = getNodeResource(2); + Set aspectsAfter = resource.getAspectNames(); + assertNotNull(aspectsAfter); + assertEquals(2, aspectsAfter.size()); + assertTrue(aspectsAfter.contains("cm:auditable")); + assertTrue(aspectsAfter.contains("cm:auditable")); + + NodeResource resourceBefore = getNodeResourceBefore(2); + Set aspectsBefore = resourceBefore.getAspectNames(); + assertNotNull(aspectsBefore); + assertEquals(1, aspectsBefore.size()); + assertTrue(aspectsBefore.contains("cm:auditable")); + } }