mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
ACS-5451: Toggle for direct Event sending (#2082)
* ACS-5451: Toggle for direct Event sending - added new bean allowing direct event sending and a toggle (switch) property - refactored EventGenerator logic related with creating and sending events - refactored consolidators - renamed EventConsolidator -> NodeEventConsolidator, and moved common logic to new abstract EventConsolidator - added integration tests - added JavaDoc - refactored events related tests
This commit is contained in:
parent
3c242bc62b
commit
e8a27dd68d
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2023 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@ -53,7 +53,7 @@ public interface TransactionListener
|
||||
* on the state of the transaction.
|
||||
* <p>
|
||||
* Although all transaction resources are still available, this method should
|
||||
* be used only for cleaning up resources after a commit has occured.
|
||||
* be used only for cleaning up resources after a commit has occurred.
|
||||
*/
|
||||
void afterCommit();
|
||||
|
||||
@ -64,7 +64,7 @@ public interface TransactionListener
|
||||
* on the state of the transaction.
|
||||
* <p>
|
||||
* Although all transaction resources are still available, this method should
|
||||
* be used only for cleaning up resources after a rollback has occured.
|
||||
* be used only for cleaning up resources after a rollback has occurred.
|
||||
*/
|
||||
void afterRollback();
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -25,13 +25,8 @@
|
||||
*/
|
||||
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.DataAttributes;
|
||||
import org.alfresco.repo.event.v1.model.EventData;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
@ -41,51 +36,12 @@ import org.alfresco.service.namespace.QName;
|
||||
* @author Chris Shields
|
||||
* @author Sara Aspery
|
||||
*/
|
||||
public class ChildAssociationEventConsolidator implements ChildAssociationEventSupportedPolicies
|
||||
public class ChildAssociationEventConsolidator extends EventConsolidator<ChildAssociationRef, ChildAssociationResource> implements ChildAssociationEventSupportedPolicies
|
||||
{
|
||||
private final Deque<EventType> eventTypes;
|
||||
|
||||
protected final ChildAssociationRef childAssociationRef;
|
||||
|
||||
private ChildAssociationResource resource;
|
||||
private final NodeResourceHelper helper;
|
||||
|
||||
public ChildAssociationEventConsolidator(ChildAssociationRef childAssociationRef, NodeResourceHelper 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<DataAttributes<ChildAssociationResource>> getRepoEvent(EventInfo eventInfo)
|
||||
{
|
||||
EventType eventType = getDerivedEvent();
|
||||
|
||||
DataAttributes<ChildAssociationResource> eventData = buildEventData(eventInfo, resource);
|
||||
|
||||
return RepoEvent.<DataAttributes<ChildAssociationResource>>builder()
|
||||
.setId(eventInfo.getId())
|
||||
.setSource(eventInfo.getSource())
|
||||
.setTime(eventInfo.getTimestamp())
|
||||
.setType(eventType.getType())
|
||||
.setData(eventData)
|
||||
.setDataschema(EventJSONSchema.getSchemaV1(eventType))
|
||||
.build();
|
||||
}
|
||||
|
||||
protected DataAttributes<ChildAssociationResource> buildEventData(EventInfo eventInfo, ChildAssociationResource resource)
|
||||
{
|
||||
return EventData.<ChildAssociationResource>builder()
|
||||
.setEventGroupId(eventInfo.getTxnId())
|
||||
.setResource(resource)
|
||||
.build();
|
||||
super(childAssociationRef, helper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,12 +77,10 @@ public class ChildAssociationEventConsolidator implements ChildAssociationEventS
|
||||
return new ChildAssociationResource(parentId, childId, assocType, assocQName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a derived event for a transaction.
|
||||
*/
|
||||
private EventType getDerivedEvent()
|
||||
@Override
|
||||
protected EventType getDerivedEvent()
|
||||
{
|
||||
if (isTemporaryChildAssociation())
|
||||
if (isTemporaryEntity())
|
||||
{
|
||||
// This event will be filtered out, but we set the correct
|
||||
// event type anyway for debugging purposes
|
||||
@ -147,33 +101,15 @@ public class ChildAssociationEventConsolidator implements ChildAssociationEventS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
@Override
|
||||
public boolean isTemporaryEntity()
|
||||
{
|
||||
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()
|
||||
@Override
|
||||
public QName getEntityType()
|
||||
{
|
||||
return childAssociationRef.getTypeQName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event types.
|
||||
*
|
||||
* @return Deque<EventType> queue of event types
|
||||
*/
|
||||
public Deque<EventType> getEventTypes()
|
||||
{
|
||||
return eventTypes;
|
||||
return entityReference.getTypeQName();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* Sends a message to a destination in the current thread.
|
||||
*/
|
||||
public class DirectEventSender implements EventSender, InitializingBean
|
||||
{
|
||||
protected Event2MessageProducer event2MessageProducer;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet()
|
||||
{
|
||||
PropertyCheck.mandatory(this, "event2MessageProducer", event2MessageProducer);
|
||||
}
|
||||
|
||||
public void setEvent2MessageProducer(Event2MessageProducer event2MessageProducer)
|
||||
{
|
||||
this.event2MessageProducer = event2MessageProducer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Callable<Optional<RepoEvent<?>>> eventProducer)
|
||||
{
|
||||
try
|
||||
{
|
||||
eventProducer.call().ifPresent(event -> event2MessageProducer.send(event));
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Unexpected error while executing maker function for repository event", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -25,6 +25,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@ -36,40 +37,33 @@ import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/*
|
||||
* This queue allows to create asynchronously the RepoEvent offloading the work to a ThreadPool but
|
||||
* at the same time it preserves the order of the events
|
||||
/**
|
||||
* Enqueuing event sender allows to create asynchronously the RepoEvent offloading the work to a ThreadPool but
|
||||
* at the same time it preserves the order of the events.
|
||||
*/
|
||||
public class EventGeneratorQueue implements InitializingBean
|
||||
public class EnqueuingEventSender extends DirectEventSender
|
||||
{
|
||||
protected static final Log LOGGER = LogFactory.getLog(EventGeneratorQueue.class);
|
||||
|
||||
protected static final Log LOGGER = LogFactory.getLog(EnqueuingEventSender.class);
|
||||
|
||||
protected Executor enqueueThreadPoolExecutor;
|
||||
protected Executor dequeueThreadPoolExecutor;
|
||||
protected Event2MessageProducer event2MessageProducer;
|
||||
protected BlockingQueue<EventInMaking> queue = new LinkedBlockingQueue<>();
|
||||
protected Runnable listener = createListener();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
public void afterPropertiesSet()
|
||||
{
|
||||
super.afterPropertiesSet();
|
||||
PropertyCheck.mandatory(this, "enqueueThreadPoolExecutor", enqueueThreadPoolExecutor);
|
||||
PropertyCheck.mandatory(this, "dequeueThreadPoolExecutor", dequeueThreadPoolExecutor);
|
||||
PropertyCheck.mandatory(this, "event2MessageProducer", event2MessageProducer);
|
||||
}
|
||||
|
||||
public void setEvent2MessageProducer(Event2MessageProducer event2MessageProducer)
|
||||
{
|
||||
this.event2MessageProducer = event2MessageProducer;
|
||||
}
|
||||
|
||||
public void setEnqueueThreadPoolExecutor(Executor enqueueThreadPoolExecutor)
|
||||
{
|
||||
this.enqueueThreadPoolExecutor = enqueueThreadPoolExecutor;
|
||||
}
|
||||
|
||||
|
||||
public void setDequeueThreadPoolExecutor(Executor dequeueThreadPoolExecutor)
|
||||
{
|
||||
this.dequeueThreadPoolExecutor = dequeueThreadPoolExecutor;
|
||||
@ -78,11 +72,12 @@ public class EventGeneratorQueue implements InitializingBean
|
||||
|
||||
/**
|
||||
* Procedure to enqueue the callback functions that creates an event.
|
||||
* @param maker Callback function that creates an event.
|
||||
* @param eventProducer Callback function that creates an event.
|
||||
*/
|
||||
public void accept(Callable<RepoEvent<?>> maker)
|
||||
@Override
|
||||
public void accept(Callable<Optional<RepoEvent<?>>> eventProducer)
|
||||
{
|
||||
EventInMaking eventInMaking = new EventInMaking(maker);
|
||||
EventInMaking eventInMaking = new EventInMaking(eventProducer);
|
||||
queue.offer(eventInMaking);
|
||||
enqueueThreadPoolExecutor.execute(() -> {
|
||||
try
|
||||
@ -102,78 +97,67 @@ public class EventGeneratorQueue implements InitializingBean
|
||||
*/
|
||||
private Runnable createListener()
|
||||
{
|
||||
return new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
return () -> {
|
||||
try
|
||||
{
|
||||
try
|
||||
while (!Thread.interrupted())
|
||||
{
|
||||
while (!Thread.interrupted())
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
EventInMaking eventInMaking = queue.take();
|
||||
RepoEvent<?> event = eventInMaking.getEventWhenReady();
|
||||
if (event != null)
|
||||
{
|
||||
event2MessageProducer.send(event);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected error while dequeuing and sending repository event" + e);
|
||||
}
|
||||
queue.take().getEventWhenReady().ifPresent(event -> event2MessageProducer.send(event));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected error while dequeuing and sending repository event " + e);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LOGGER.warn("Unexpected: rescheduling the listener thread.");
|
||||
dequeueThreadPoolExecutor.execute(listener);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LOGGER.warn("Unexpected: rescheduling the listener thread.");
|
||||
dequeueThreadPoolExecutor.execute(listener);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Simple class that makes events and allows to retrieve them when ready
|
||||
*/
|
||||
private static class EventInMaking
|
||||
{
|
||||
private Callable<RepoEvent<?>> maker;
|
||||
private final Callable<Optional<RepoEvent<?>>> maker;
|
||||
private volatile RepoEvent<?> event;
|
||||
private CountDownLatch latch;
|
||||
|
||||
public EventInMaking(Callable<RepoEvent<?>> maker)
|
||||
private final CountDownLatch latch;
|
||||
|
||||
public EventInMaking(Callable<Optional<RepoEvent<?>>> maker)
|
||||
{
|
||||
this.maker = maker;
|
||||
this.latch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
|
||||
public void make() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
event = maker.call();
|
||||
event = maker.call().orElse(null);
|
||||
}
|
||||
finally
|
||||
finally
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public RepoEvent<?> getEventWhenReady() throws InterruptedException
|
||||
|
||||
public Optional<RepoEvent<?>> getEventWhenReady() throws InterruptedException
|
||||
{
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
return event;
|
||||
return Optional.ofNullable(event);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return maker.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -25,60 +25,66 @@
|
||||
*/
|
||||
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;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.event.v1.model.ContentInfo;
|
||||
import org.alfresco.repo.event.v1.model.DataAttributes;
|
||||
import org.alfresco.repo.event.v1.model.EventData;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
import org.alfresco.repo.event.v1.model.NodeResource;
|
||||
import org.alfresco.repo.event.v1.model.NodeResource.Builder;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.repo.event.v1.model.UserInfo;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.repo.event.v1.model.Resource;
|
||||
import org.alfresco.service.cmr.repository.EntityRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Encapsulates events occurred in a single transaction.
|
||||
*
|
||||
* @author Jamal Kaabi-Mofrad
|
||||
* @param <REF> entity (e.g. node, child association, peer association) reference type
|
||||
* @param <RES> entity resource type
|
||||
*/
|
||||
public class EventConsolidator implements EventSupportedPolicies
|
||||
public abstract class EventConsolidator<REF extends EntityRef, RES extends Resource>
|
||||
{
|
||||
private final NodeResourceHelper helper;
|
||||
protected final Deque<EventType> eventTypes;
|
||||
private final List<QName> aspectsAdded;
|
||||
private final List<QName> aspectsRemoved;
|
||||
protected final NodeResourceHelper helper;
|
||||
protected REF entityReference;
|
||||
protected RES resource;
|
||||
|
||||
protected NodeRef nodeRef;
|
||||
|
||||
private NodeResource.Builder resourceBuilder;
|
||||
private Map<QName, Serializable> propertiesBefore;
|
||||
private Map<QName, Serializable> propertiesAfter;
|
||||
private QName nodeType;
|
||||
private QName nodeTypeBefore;
|
||||
private List<String> primaryHierarchyBefore;
|
||||
private boolean resourceBeforeAllFieldsNull = true;
|
||||
|
||||
public EventConsolidator(NodeResourceHelper nodeResourceHelper)
|
||||
public EventConsolidator(final REF entityReference, final NodeResourceHelper nodeResourceHelper)
|
||||
{
|
||||
this.helper = nodeResourceHelper;
|
||||
this.eventTypes = new ArrayDeque<>();
|
||||
this.aspectsAdded = new ArrayList<>();
|
||||
this.aspectsRemoved = new ArrayList<>();
|
||||
this.entityReference = entityReference;
|
||||
this.helper = nodeResourceHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity (e.g. node, peer association, child association) type.
|
||||
*
|
||||
* @return QName the peer association type
|
||||
*/
|
||||
public abstract QName getEntityType();
|
||||
|
||||
/**
|
||||
* Whether the entity has been created and then deleted, e.g. a temporary node.
|
||||
*
|
||||
* @return {@code true} if the node has been created and then deleted, otherwise false
|
||||
*/
|
||||
public abstract boolean isTemporaryEntity();
|
||||
|
||||
/**
|
||||
* Get a derived event for a transaction.
|
||||
*
|
||||
* @return a derived event type
|
||||
*/
|
||||
protected abstract EventType getDerivedEvent();
|
||||
|
||||
/**
|
||||
* Get event types.
|
||||
*
|
||||
* @return Deque<EventType> queue of event types
|
||||
*/
|
||||
public Deque<EventType> getEventTypes()
|
||||
{
|
||||
return eventTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,424 +93,30 @@ public class EventConsolidator implements EventSupportedPolicies
|
||||
* @param eventInfo the object holding the event information
|
||||
* @return the {@link RepoEvent} instance
|
||||
*/
|
||||
public RepoEvent<DataAttributes<NodeResource>> getRepoEvent(EventInfo eventInfo)
|
||||
public RepoEvent<DataAttributes<RES>> getRepoEvent(EventInfo eventInfo)
|
||||
{
|
||||
NodeResource resource = buildNodeResource();
|
||||
EventType eventType = getDerivedEvent();
|
||||
|
||||
DataAttributes<NodeResource> eventData = buildEventData(eventInfo, resource, eventType);
|
||||
DataAttributes<RES> eventData = buildEventData(eventInfo, resource, eventType);
|
||||
|
||||
return RepoEvent.<DataAttributes<NodeResource>>builder()
|
||||
.setId(eventInfo.getId())
|
||||
.setSource(eventInfo.getSource())
|
||||
.setTime(eventInfo.getTimestamp())
|
||||
.setType(eventType.getType())
|
||||
.setData(eventData)
|
||||
.setDataschema(EventJSONSchema.getSchemaV1(eventType))
|
||||
.build();
|
||||
}
|
||||
|
||||
protected DataAttributes<NodeResource> buildEventData(EventInfo eventInfo, NodeResource resource, EventType eventType)
|
||||
{
|
||||
EventData.Builder<NodeResource> eventDataBuilder = EventData.<NodeResource>builder()
|
||||
.setEventGroupId(eventInfo.getTxnId())
|
||||
.setResource(resource);
|
||||
|
||||
if (eventType == EventType.NODE_UPDATED)
|
||||
{
|
||||
eventDataBuilder.setResourceBefore(buildNodeResourceBeforeDelta(resource));
|
||||
}
|
||||
|
||||
return eventDataBuilder.build();
|
||||
return RepoEvent.<DataAttributes<RES>>builder()
|
||||
.setId(eventInfo.getId())
|
||||
.setSource(eventInfo.getSource())
|
||||
.setTime(eventInfo.getTimestamp())
|
||||
.setType(eventType.getType())
|
||||
.setData(eventData)
|
||||
.setDataschema(EventJSONSchema.getSchemaV1(eventType))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder instance if absent or {@code forceUpdate} is requested.
|
||||
* It also, sets the required fields.
|
||||
*
|
||||
* @param nodeRef the nodeRef in the txn
|
||||
* @param forceUpdate if {@code true}, will get the latest node info and ignores
|
||||
* the existing builder object.
|
||||
* Provides primary event data.
|
||||
*/
|
||||
protected void createBuilderIfAbsent(NodeRef nodeRef, boolean forceUpdate)
|
||||
protected DataAttributes<RES> buildEventData(EventInfo eventInfo, RES resource, EventType eventType)
|
||||
{
|
||||
if (resourceBuilder == null || forceUpdate)
|
||||
{
|
||||
this.resourceBuilder = helper.createNodeResourceBuilder(nodeRef);
|
||||
this.nodeRef = nodeRef;
|
||||
this.nodeType = helper.getNodeType(nodeRef);
|
||||
}
|
||||
return EventData.<RES>builder()
|
||||
.setEventGroupId(eventInfo.getTxnId())
|
||||
.setResource(resource)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder instance if absent, and sets the required fields.
|
||||
*
|
||||
* @param nodeRef the nodeRef in the txn
|
||||
*/
|
||||
protected void createBuilderIfAbsent(NodeRef nodeRef)
|
||||
{
|
||||
createBuilderIfAbsent(nodeRef, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_CREATED);
|
||||
|
||||
NodeRef nodeRef = childAssocRef.getChildRef();
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
|
||||
// Sometimes onCreateNode policy is out of order
|
||||
this.propertiesBefore = null;
|
||||
setBeforeProperties(Collections.emptyMap());
|
||||
setAfterProperties(helper.getProperties(nodeRef));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
|
||||
createBuilderIfAbsent(newChildAssocRef.getChildRef());
|
||||
setBeforePrimaryHierarchy(helper.getPrimaryHierarchy(oldChildAssocRef.getParentRef(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetNodeType(NodeRef nodeRef, QName before, QName after)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
nodeTypeBefore = before;
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateProperties(NodeRef nodeRef, Map<QName, Serializable> before, Map<QName, Serializable> after)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
|
||||
// Sometime we don't get the 'before', so just use the latest
|
||||
if (before.isEmpty() && this.propertiesAfter != null)
|
||||
{
|
||||
before = this.propertiesAfter;
|
||||
}
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
setBeforeProperties(before);
|
||||
setAfterProperties(after);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDeleteNode(NodeRef nodeRef)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_DELETED);
|
||||
createBuilderIfAbsent(nodeRef, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
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);
|
||||
removeAspect(aspectTypeQName);
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
}
|
||||
|
||||
void removeAspect(QName aspectTypeQName)
|
||||
{
|
||||
if (aspectsAdded.contains(aspectTypeQName))
|
||||
{
|
||||
aspectsAdded.remove(aspectTypeQName);
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectsRemoved.add(aspectTypeQName);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAfterProperties(Map<QName, Serializable> after)
|
||||
{
|
||||
propertiesAfter = after;
|
||||
}
|
||||
|
||||
private void setBeforeProperties(Map<QName, Serializable> before)
|
||||
{
|
||||
// Don't overwrite the original value if there are multiple calls.
|
||||
if (propertiesBefore == null)
|
||||
{
|
||||
propertiesBefore = before;
|
||||
}
|
||||
}
|
||||
|
||||
private void setBeforePrimaryHierarchy(List<String> before)
|
||||
{
|
||||
// Don't overwrite the original value if there are multiple calls.
|
||||
if (primaryHierarchyBefore == null)
|
||||
{
|
||||
primaryHierarchyBefore = before;
|
||||
}
|
||||
}
|
||||
|
||||
private NodeResource buildNodeResource()
|
||||
{
|
||||
if (resourceBuilder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (eventTypes.getLast() != EventType.NODE_DELETED)
|
||||
{
|
||||
// Check the node still exists.
|
||||
// This could happen in tests where a node is deleted before the afterCommit code is
|
||||
// executed (For example, see ThumbnailServiceImplTest#testIfNodesExistsAfterCreateThumbnail).
|
||||
if (helper.nodeExists(nodeRef))
|
||||
{
|
||||
// We are setting the details at the end of the Txn by getting the latest info
|
||||
createBuilderIfAbsent(nodeRef, true);
|
||||
}
|
||||
}
|
||||
// Now create an instance of NodeResource
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
protected NodeResource buildNodeResourceBeforeDelta(NodeResource after)
|
||||
{
|
||||
if (after == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Builder builder = NodeResource.builder();
|
||||
|
||||
ZonedDateTime modifiedAt = null;
|
||||
Map<QName, Serializable> changedPropsBefore = getBeforeMapChanges(propertiesBefore, propertiesAfter);
|
||||
if (!changedPropsBefore.isEmpty())
|
||||
{
|
||||
// Set only the changed properties
|
||||
Map<String, Serializable> mappedProps = helper.mapToNodeProperties(changedPropsBefore);
|
||||
if (!mappedProps.isEmpty())
|
||||
{
|
||||
builder.setProperties(mappedProps);
|
||||
resourceBeforeAllFieldsNull = false;
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> localizedProps =helper.getLocalizedPropertiesBefore(changedPropsBefore, after);
|
||||
if (!localizedProps.isEmpty())
|
||||
{
|
||||
builder.setLocalizedProperties(localizedProps);
|
||||
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;
|
||||
}
|
||||
modifiedAt =
|
||||
helper.getZonedDateTime((Date) changedPropsBefore.get(ContentModel.PROP_MODIFIED));
|
||||
}
|
||||
|
||||
// Handle case where the content does not exist on the propertiesBefore
|
||||
if (propertiesBefore != null && !propertiesBefore.containsKey(ContentModel.PROP_CONTENT) &&
|
||||
propertiesAfter != null && propertiesAfter.containsKey(ContentModel.PROP_CONTENT))
|
||||
{
|
||||
builder.setContent(new ContentInfo());
|
||||
resourceBeforeAllFieldsNull = false;
|
||||
}
|
||||
|
||||
Set<String> aspectsBefore = getMappedAspectsBefore(after.getAspectNames());
|
||||
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;
|
||||
}
|
||||
|
||||
// Only set modifiedAt if one of the other fields is also not null
|
||||
if (modifiedAt != null && !resourceBeforeAllFieldsNull)
|
||||
{
|
||||
builder.setModifiedAt(modifiedAt);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Set<String> getMappedAspectsBefore(Set<String> currentAspects)
|
||||
{
|
||||
if (currentAspects == null)
|
||||
{
|
||||
currentAspects = Collections.emptySet();
|
||||
}
|
||||
if (hasChangedAspect())
|
||||
{
|
||||
Set<String> removed = helper.mapToNodeAspects(aspectsRemoved);
|
||||
Set<String> added = helper.mapToNodeAspects(aspectsAdded);
|
||||
|
||||
Set<String> before = new HashSet<>();
|
||||
if (!removed.isEmpty() || !added.isEmpty())
|
||||
{
|
||||
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;
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private boolean hasChangedAspect()
|
||||
{
|
||||
if ((aspectsRemoved.isEmpty() && aspectsAdded.isEmpty()) ||
|
||||
org.apache.commons.collections.CollectionUtils.isEqualCollection(aspectsAdded, aspectsRemoved))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private <K, V> Map<K, V> getBeforeMapChanges(Map<K, V> before, Map<K, V> after)
|
||||
{
|
||||
if (before == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
if (after == null)
|
||||
{
|
||||
after = Collections.emptyMap();
|
||||
}
|
||||
// Get before values that changed
|
||||
Map<K, V> beforeDelta = new HashMap<>(before);
|
||||
Map<K, V> afterDelta = new HashMap<>(after);
|
||||
|
||||
beforeDelta.entrySet().removeAll(after.entrySet());
|
||||
|
||||
// Add nulls for before properties
|
||||
Set<K> beforeKeys = before.keySet();
|
||||
Set<K> newKeys = afterDelta.keySet();
|
||||
newKeys.removeAll(beforeKeys);
|
||||
|
||||
for (K key : newKeys)
|
||||
{
|
||||
beforeDelta.put(key, null);
|
||||
}
|
||||
|
||||
return beforeDelta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a derived event for a transaction.
|
||||
*/
|
||||
private EventType getDerivedEvent()
|
||||
{
|
||||
if (isTemporaryNode())
|
||||
{
|
||||
// This event will be filtered out, but we set the correct
|
||||
// event type anyway for debugging purposes
|
||||
return EventType.NODE_DELETED;
|
||||
}
|
||||
else if (eventTypes.contains(EventType.NODE_CREATED))
|
||||
{
|
||||
return EventType.NODE_CREATED;
|
||||
}
|
||||
else if (eventTypes.getLast() == EventType.NODE_DELETED)
|
||||
{
|
||||
return EventType.NODE_DELETED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to first event
|
||||
return eventTypes.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the node has been created and then deleted, i.e. a temporary node.
|
||||
*
|
||||
* @return {@code true} if the node has been created and then deleted, otherwise false
|
||||
*/
|
||||
public boolean isTemporaryNode()
|
||||
{
|
||||
return eventTypes.contains(EventType.NODE_CREATED) && eventTypes.getLast() == EventType.NODE_DELETED;
|
||||
}
|
||||
|
||||
public QName getNodeType()
|
||||
{
|
||||
return nodeType;
|
||||
}
|
||||
|
||||
public Deque<EventType> getEventTypes()
|
||||
{
|
||||
return eventTypes;
|
||||
}
|
||||
|
||||
|
||||
public List<QName> getAspectsAdded()
|
||||
{
|
||||
return aspectsAdded;
|
||||
}
|
||||
|
||||
public List<QName> getAspectsRemoved()
|
||||
{
|
||||
return aspectsRemoved;
|
||||
}
|
||||
|
||||
public boolean isResourceBeforeAllFieldsNull()
|
||||
{
|
||||
return resourceBeforeAllFieldsNull;
|
||||
}
|
||||
|
||||
protected void setResourceBeforeAllFieldsNull(boolean resourceBeforeAllFieldsNull){
|
||||
this.resourceBeforeAllFieldsNull = resourceBeforeAllFieldsNull;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -34,13 +34,16 @@ import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.repo.domain.node.NodeDAO;
|
||||
import org.alfresco.repo.domain.node.TransactionEntity;
|
||||
import org.alfresco.repo.event.v1.model.DataAttributes;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.repo.event.v1.model.Resource;
|
||||
import org.alfresco.repo.event2.filter.ChildAssociationTypeFilter;
|
||||
import org.alfresco.repo.event2.filter.EventFilterRegistry;
|
||||
import org.alfresco.repo.event2.filter.EventUserFilter;
|
||||
@ -63,9 +66,11 @@ import org.alfresco.repo.policy.PolicyComponent;
|
||||
import org.alfresco.repo.policy.ServiceBehaviourBinding;
|
||||
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.EntityRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
@ -74,6 +79,7 @@ import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.alfresco.util.TriPredicate;
|
||||
import org.alfresco.util.transaction.TransactionListenerAdapter;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@ -102,8 +108,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
private PersonService personService;
|
||||
protected NodeResourceHelper nodeResourceHelper;
|
||||
protected NodeDAO nodeDAO;
|
||||
|
||||
private EventGeneratorQueue eventGeneratorQueue;
|
||||
private EventSender eventSender;
|
||||
private NodeTypeFilter nodeTypeFilter;
|
||||
private ChildAssociationTypeFilter childAssociationTypeFilter;
|
||||
private EventUserFilter userFilter;
|
||||
@ -139,7 +144,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
PropertyCheck.mandatory(this, "personService", personService);
|
||||
PropertyCheck.mandatory(this, "nodeResourceHelper", nodeResourceHelper);
|
||||
PropertyCheck.mandatory(this, "nodeDAO", nodeDAO);
|
||||
PropertyCheck.mandatory(this, "eventGeneratorQueue", eventGeneratorQueue);
|
||||
PropertyCheck.mandatory(this, "eventSender", eventSender);
|
||||
|
||||
this.nodeTypeFilter = eventFilterRegistry.getNodeTypeFilter();
|
||||
this.childAssociationTypeFilter = eventFilterRegistry.getChildAssociationTypeFilter();
|
||||
@ -188,7 +193,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
|
||||
if (behaviours == null)
|
||||
{
|
||||
behaviours = new HashSet<Behaviour>();
|
||||
behaviours = new HashSet<>();
|
||||
afterPropertiesSet();
|
||||
bindBehaviours();
|
||||
|
||||
@ -230,8 +235,6 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
this.descriptorService = descriptorService;
|
||||
}
|
||||
|
||||
// To make IntelliJ stop complaining about unused method!
|
||||
@SuppressWarnings("unused")
|
||||
public void setEventFilterRegistry(EventFilterRegistry eventFilterRegistry)
|
||||
{
|
||||
this.eventFilterRegistry = eventFilterRegistry;
|
||||
@ -252,9 +255,14 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
this.nodeResourceHelper = nodeResourceHelper;
|
||||
}
|
||||
|
||||
public void setEventGeneratorQueue(EventGeneratorQueue eventGeneratorQueue)
|
||||
public void setEventSender(EventSender eventSender)
|
||||
{
|
||||
this.eventGeneratorQueue = eventGeneratorQueue;
|
||||
this.eventSender = eventSender;
|
||||
}
|
||||
|
||||
public EventSender getEventSender()
|
||||
{
|
||||
return eventSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -323,9 +331,9 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
getEventConsolidator(associationRef).beforeDeleteAssociation(associationRef);
|
||||
}
|
||||
|
||||
protected EventConsolidator createEventConsolidator()
|
||||
protected NodeEventConsolidator createEventConsolidator()
|
||||
{
|
||||
return new EventConsolidator(nodeResourceHelper);
|
||||
return new NodeEventConsolidator(nodeResourceHelper);
|
||||
}
|
||||
|
||||
protected ChildAssociationEventConsolidator createChildAssociationEventConsolidator(
|
||||
@ -370,13 +378,11 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
disableBehaviours(behaviours);
|
||||
}
|
||||
|
||||
protected void disableBehaviours(Set<Behaviour> bindedBehaviours)
|
||||
protected void disableBehaviours(Set<Behaviour> boundBehaviours)
|
||||
{
|
||||
if (bindedBehaviours != null)
|
||||
if (boundBehaviours != null)
|
||||
{
|
||||
bindedBehaviours.forEach(behaviour -> {
|
||||
behaviour.disable();
|
||||
});
|
||||
boundBehaviours.forEach(Behaviour::disable);
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,30 +391,28 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
enableBehaviours(behaviours);
|
||||
}
|
||||
|
||||
protected void enableBehaviours(Set<Behaviour> bindedBehaviours)
|
||||
protected void enableBehaviours(Set<Behaviour> boundBehaviours)
|
||||
{
|
||||
if (bindedBehaviours != null)
|
||||
if (boundBehaviours != null)
|
||||
{
|
||||
bindedBehaviours.forEach(behaviour -> {
|
||||
behaviour.enable();
|
||||
});
|
||||
boundBehaviours.forEach(Behaviour::enable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link EventConsolidator} for the supplied {@code nodeRef} from
|
||||
* @return the {@link NodeEventConsolidator} for the supplied {@code nodeRef} from
|
||||
* the current transaction context.
|
||||
*/
|
||||
protected EventConsolidator getEventConsolidator(NodeRef nodeRef)
|
||||
protected NodeEventConsolidator getEventConsolidator(NodeRef nodeRef)
|
||||
{
|
||||
Consolidators consolidators = getTxnConsolidators(transactionListener);
|
||||
Map<NodeRef, EventConsolidator> nodeEvents = consolidators.getNodes();
|
||||
Map<NodeRef, NodeEventConsolidator> nodeEvents = consolidators.getNodes();
|
||||
if (nodeEvents.isEmpty())
|
||||
{
|
||||
AlfrescoTransactionSupport.bindListener(transactionListener);
|
||||
}
|
||||
|
||||
EventConsolidator eventConsolidator = nodeEvents.get(nodeRef);
|
||||
NodeEventConsolidator eventConsolidator = nodeEvents.get(nodeRef);
|
||||
if (eventConsolidator == null)
|
||||
{
|
||||
eventConsolidator = createEventConsolidator();
|
||||
@ -430,7 +434,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link EventConsolidator} for the supplied {@code childAssociationRef} from
|
||||
* @return the {@link ChildAssociationEventConsolidator} for the supplied {@code childAssociationRef} from
|
||||
* the current transaction context.
|
||||
*/
|
||||
private ChildAssociationEventConsolidator getEventConsolidator(ChildAssociationRef childAssociationRef)
|
||||
@ -452,7 +456,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link EventConsolidator} for the supplied {@code peerAssociationRef} from
|
||||
* @return the {@link PeerAssociationEventConsolidator} for the supplied {@code peerAssociationRef} from
|
||||
* the current transaction context.
|
||||
*/
|
||||
private PeerAssociationEventConsolidator getEventConsolidator(AssociationRef peerAssociationRef)
|
||||
@ -506,7 +510,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
{
|
||||
return;
|
||||
}
|
||||
behaviours = new HashSet<Behaviour>();
|
||||
behaviours = new HashSet<>();
|
||||
bindBehaviours();
|
||||
}
|
||||
|
||||
@ -521,32 +525,11 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
@Override
|
||||
public void afterCommit()
|
||||
{
|
||||
if(isTransactionCommitted())
|
||||
if (isTransactionCommitted())
|
||||
{
|
||||
try
|
||||
{
|
||||
final Consolidators consolidators = getTxnConsolidators(this);
|
||||
|
||||
// Node events
|
||||
for (Map.Entry<NodeRef, EventConsolidator> entry : consolidators.getNodes().entrySet())
|
||||
{
|
||||
EventConsolidator eventConsolidator = entry.getValue();
|
||||
sendEvent(entry.getKey(), eventConsolidator);
|
||||
}
|
||||
|
||||
// Child assoc events
|
||||
for (Map.Entry<ChildAssociationRef, ChildAssociationEventConsolidator> entry : consolidators.getChildAssocs().entrySet())
|
||||
{
|
||||
ChildAssociationEventConsolidator eventConsolidator = entry.getValue();
|
||||
sendEvent(entry.getKey(), eventConsolidator);
|
||||
}
|
||||
|
||||
// Peer assoc events
|
||||
for (Map.Entry<AssociationRef, PeerAssociationEventConsolidator> entry : consolidators.getPeerAssocs().entrySet())
|
||||
{
|
||||
PeerAssociationEventConsolidator eventConsolidator = entry.getValue();
|
||||
sendEvent(entry.getKey(), eventConsolidator);
|
||||
}
|
||||
sendEvents();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -556,12 +539,6 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendEvent(NodeRef nodeRef, EventConsolidator consolidator)
|
||||
{
|
||||
EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
|
||||
eventGeneratorQueue.accept(()-> createEvent(nodeRef, consolidator, eventInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a node transaction is not only active, but also committed with modifications.
|
||||
* This means that a {@link TransactionEntity} object was created.
|
||||
@ -571,115 +548,154 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
return nodeDAO.getCurrentTransactionCommitTime() != null;
|
||||
}
|
||||
|
||||
private RepoEvent<?> createEvent(NodeRef nodeRef, EventConsolidator consolidator, EventInfo eventInfo)
|
||||
private void sendEvents()
|
||||
{
|
||||
String user = eventInfo.getPrincipal();
|
||||
final Consolidators consolidators = getTxnConsolidators(this);
|
||||
|
||||
if (consolidator.isTemporaryNode())
|
||||
// Node events
|
||||
for (Map.Entry<NodeRef, NodeEventConsolidator> entry : consolidators.getNodes().entrySet())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Ignoring temporary node: " + nodeRef);
|
||||
}
|
||||
return null;
|
||||
sendEvent(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
// Get the repo event before the filtering,
|
||||
// so we can take the latest node info into account
|
||||
final RepoEvent<?> event = consolidator.getRepoEvent(eventInfo);
|
||||
|
||||
final QName nodeType = consolidator.getNodeType();
|
||||
if (isFiltered(nodeType, user))
|
||||
// Child assoc events
|
||||
for (Map.Entry<ChildAssociationRef, ChildAssociationEventConsolidator> entry : consolidators.getChildAssocs().entrySet())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding node: '" + nodeRef + "' of type: '"
|
||||
+ ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return null;
|
||||
sendEvent(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (event.getType().equals(EventType.NODE_UPDATED.getType()) && consolidator.isResourceBeforeAllFieldsNull())
|
||||
// Peer assoc events
|
||||
for (Map.Entry<AssociationRef, PeerAssociationEventConsolidator> entry : consolidators.getPeerAssocs().entrySet())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Ignoring node updated event as no fields have been updated: " + nodeRef);
|
||||
}
|
||||
return null;
|
||||
sendEvent(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
logEvent(event, consolidator.getEventTypes());
|
||||
return event;
|
||||
protected void sendEvent(NodeRef nodeRef, NodeEventConsolidator consolidator)
|
||||
{
|
||||
sendEvent(nodeRef, consolidator, nodeToEventEligibilityVerifier());
|
||||
}
|
||||
|
||||
protected void sendEvent(ChildAssociationRef childAssociationRef, ChildAssociationEventConsolidator consolidator)
|
||||
{
|
||||
EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
|
||||
eventGeneratorQueue.accept(()-> createEvent(eventInfo, childAssociationRef, consolidator));
|
||||
}
|
||||
|
||||
private RepoEvent<?> createEvent(EventInfo eventInfo, ChildAssociationRef childAssociationRef, ChildAssociationEventConsolidator consolidator)
|
||||
{
|
||||
String user = eventInfo.getPrincipal();
|
||||
if (consolidator.isTemporaryChildAssociation())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Ignoring temporary child association: " + childAssociationRef);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the repo event before the filtering,
|
||||
// so we can take the latest association info into account
|
||||
final RepoEvent<?> event = consolidator.getRepoEvent(eventInfo);
|
||||
|
||||
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 null;
|
||||
} 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 null;
|
||||
}
|
||||
|
||||
logEvent(event, consolidator.getEventTypes());
|
||||
return event;
|
||||
sendEvent(childAssociationRef, consolidator, childAssociationToEventEligibilityVerifier());
|
||||
}
|
||||
|
||||
protected void sendEvent(AssociationRef peerAssociationRef, PeerAssociationEventConsolidator consolidator)
|
||||
{
|
||||
EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
|
||||
eventGeneratorQueue.accept(()-> createEvent(eventInfo, peerAssociationRef, consolidator));
|
||||
sendEvent(peerAssociationRef, consolidator, null);
|
||||
}
|
||||
|
||||
private RepoEvent<?> createEvent(EventInfo eventInfo, AssociationRef peerAssociationRef, PeerAssociationEventConsolidator consolidator)
|
||||
/**
|
||||
* Handles all kinds of events and sends them within dedicated transaction.
|
||||
*
|
||||
* @param entityReference - reference to an entity (e.g. node, child association, peer association)
|
||||
* @param eventConsolidator - object encapsulating events occurred in a transaction
|
||||
* @param entityToEventEligibilityVerifier - allows to verify if entity is eligible to generate an even. If null no verification is necessary
|
||||
* @param <REF> - entity reference type (e.g. {@link NodeRef}, {@link AssociationRef}, {@link ChildAssociationRef})
|
||||
* @param <CON> - event consolidator type - extension of {@link EventConsolidator}
|
||||
*/
|
||||
private <REF extends EntityRef, CON extends EventConsolidator<REF, ? extends Resource>> void sendEvent(
|
||||
final REF entityReference, final CON eventConsolidator, final TriPredicate<REF, CON, EventInfo> entityToEventEligibilityVerifier)
|
||||
{
|
||||
if (consolidator.isTemporaryPeerAssociation())
|
||||
final EventInfo eventInfo = getEventInfo(AuthenticationUtil.getFullyAuthenticatedUser());
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback<Void>) () -> {
|
||||
eventSender.accept(() -> createEvent(entityReference, eventConsolidator, eventInfo, entityToEventEligibilityVerifier));
|
||||
return null;
|
||||
}, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates events from various kinds of entities.
|
||||
*
|
||||
* @param entityReference - reference to an entity (e.g. node, child association, peer association)
|
||||
* @param eventConsolidator - object encapsulating events occurred in a transaction
|
||||
* @param eventInfo - object holding the event information
|
||||
* @param entityToEventEligibilityVerifier - allows to verify if entity is eligible to generate an even. If null no verification is necessary
|
||||
* @param <REF> - entity reference type (e.g. {@link NodeRef}, {@link AssociationRef}, {@link ChildAssociationRef})
|
||||
* @param <CON> - event consolidator type - extension of {@link EventConsolidator}
|
||||
*/
|
||||
private <REF extends EntityRef, CON extends EventConsolidator<REF, ? extends Resource>> Optional<RepoEvent<?>> createEvent(
|
||||
final REF entityReference, final CON eventConsolidator, final EventInfo eventInfo,
|
||||
final TriPredicate<REF, CON, EventInfo> entityToEventEligibilityVerifier)
|
||||
{
|
||||
if (eventConsolidator.isTemporaryEntity())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Ignoring temporary peer association: " + peerAssociationRef);
|
||||
LOGGER.trace("Ignoring temporary entity: " + entityReference);
|
||||
}
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
RepoEvent<?> event = consolidator.getRepoEvent(eventInfo);
|
||||
logEvent(event, consolidator.getEventTypes());
|
||||
return event;
|
||||
// get the repo event before verifying entity eligibility to generate event, so we can take the latest node info into account
|
||||
final RepoEvent<? extends DataAttributes<? extends Resource>> event = eventConsolidator.getRepoEvent(eventInfo);
|
||||
|
||||
// verify if entity is eligible to generate an event
|
||||
if (entityToEventEligibilityVerifier != null && !entityToEventEligibilityVerifier.test(entityReference, eventConsolidator, eventInfo))
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
logEvent(event, eventConsolidator.getEventTypes());
|
||||
return Optional.of(event);
|
||||
}
|
||||
|
||||
private TriPredicate<NodeRef, NodeEventConsolidator, EventInfo> nodeToEventEligibilityVerifier()
|
||||
{
|
||||
return (nodeReference, eventConsolidator, eventInfo) -> {
|
||||
final String user = eventInfo.getPrincipal();
|
||||
final QName nodeType = eventConsolidator.getEntityType();
|
||||
if (isFiltered(nodeType, user))
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding node: '" + nodeReference + "' of type: '"
|
||||
+ ((nodeType == null) ? "Unknown' " : nodeType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventConsolidator.isEventTypeEqualTo(EventType.NODE_UPDATED) && eventConsolidator.isResourceBeforeAllFieldsNull())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Ignoring node updated event as no fields have been updated: " + nodeReference);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private TriPredicate<ChildAssociationRef, ChildAssociationEventConsolidator, EventInfo> childAssociationToEventEligibilityVerifier()
|
||||
{
|
||||
return (childAssociationReference, eventConsolidator, eventInfo) -> {
|
||||
final String user = eventInfo.getPrincipal();
|
||||
final QName childAssocType = eventConsolidator.getEntityType();
|
||||
if (isFilteredChildAssociation(childAssocType, user))
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding child association: '" + childAssociationReference + "' of type: '"
|
||||
+ ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (childAssociationReference.isPrimary())
|
||||
{
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("EventFilter - Excluding primary child association: '" + childAssociationReference + "' of type: '"
|
||||
+ ((childAssocType == null) ? "Unknown' " : childAssocType.toPrefixString())
|
||||
+ "' created by: " + user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private void logEvent(RepoEvent<?> event, Deque<EventType> listOfEvents)
|
||||
@ -692,14 +708,13 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class Consolidators
|
||||
{
|
||||
private Map<NodeRef, EventConsolidator> nodes;
|
||||
private Map<NodeRef, NodeEventConsolidator> nodes;
|
||||
private Map<ChildAssociationRef, ChildAssociationEventConsolidator> childAssocs;
|
||||
private Map<AssociationRef, PeerAssociationEventConsolidator> peerAssocs;
|
||||
|
||||
public Map<NodeRef, EventConsolidator> getNodes()
|
||||
public Map<NodeRef, NodeEventConsolidator> getNodes()
|
||||
{
|
||||
if (nodes == null)
|
||||
{
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
|
||||
/**
|
||||
* Interface representing an asynchronous event sender.
|
||||
*/
|
||||
public interface EventSender
|
||||
{
|
||||
/**
|
||||
* Accepts a callback function creating an event and sends this event to specified destination.
|
||||
* @param eventProducer - callback function that creates an event
|
||||
*/
|
||||
void accept(Callable<Optional<RepoEvent<?>>> eventProducer);
|
||||
}
|
@ -0,0 +1,481 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.event.v1.model.ContentInfo;
|
||||
import org.alfresco.repo.event.v1.model.DataAttributes;
|
||||
import org.alfresco.repo.event.v1.model.EventData;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
import org.alfresco.repo.event.v1.model.NodeResource;
|
||||
import org.alfresco.repo.event.v1.model.NodeResource.Builder;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.repo.event.v1.model.UserInfo;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
|
||||
/**
|
||||
* Encapsulates node events occurred in a single transaction.
|
||||
*
|
||||
* @author Jamal Kaabi-Mofrad
|
||||
*/
|
||||
public class NodeEventConsolidator extends EventConsolidator<NodeRef, NodeResource> implements EventSupportedPolicies
|
||||
{
|
||||
private final List<QName> aspectsAdded;
|
||||
private final List<QName> aspectsRemoved;
|
||||
|
||||
private NodeResource.Builder resourceBuilder;
|
||||
private Map<QName, Serializable> propertiesBefore;
|
||||
private Map<QName, Serializable> propertiesAfter;
|
||||
private QName nodeType;
|
||||
private QName nodeTypeBefore;
|
||||
private List<String> primaryHierarchyBefore;
|
||||
private boolean resourceBeforeAllFieldsNull = true;
|
||||
|
||||
public NodeEventConsolidator(NodeResourceHelper nodeResourceHelper)
|
||||
{
|
||||
super(null, nodeResourceHelper);
|
||||
this.aspectsAdded = new ArrayList<>();
|
||||
this.aspectsRemoved = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepoEvent<DataAttributes<NodeResource>> getRepoEvent(EventInfo eventInfo)
|
||||
{
|
||||
resource = buildNodeResource();
|
||||
return super.getRepoEvent(eventInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataAttributes<NodeResource> buildEventData(EventInfo eventInfo, NodeResource resource, EventType eventType)
|
||||
{
|
||||
EventData.Builder<NodeResource> eventDataBuilder = EventData.<NodeResource>builder()
|
||||
.setEventGroupId(eventInfo.getTxnId())
|
||||
.setResource(resource);
|
||||
|
||||
if (eventType == EventType.NODE_UPDATED)
|
||||
{
|
||||
eventDataBuilder.setResourceBefore(buildNodeResourceBeforeDelta(resource));
|
||||
}
|
||||
|
||||
return eventDataBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder instance if absent or {@code forceUpdate} is requested.
|
||||
* It also, sets the required fields.
|
||||
*
|
||||
* @param nodeRef the nodeRef in the txn
|
||||
* @param forceUpdate if {@code true}, will get the latest node info and ignores
|
||||
* the existing builder object.
|
||||
*/
|
||||
protected void createBuilderIfAbsent(NodeRef nodeRef, boolean forceUpdate)
|
||||
{
|
||||
if (resourceBuilder == null || forceUpdate)
|
||||
{
|
||||
this.resourceBuilder = helper.createNodeResourceBuilder(nodeRef);
|
||||
this.entityReference = nodeRef;
|
||||
this.nodeType = helper.getNodeType(nodeRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder instance if absent, and sets the required fields.
|
||||
*
|
||||
* @param nodeRef the nodeRef in the txn
|
||||
*/
|
||||
protected void createBuilderIfAbsent(NodeRef nodeRef)
|
||||
{
|
||||
createBuilderIfAbsent(nodeRef, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateNode(ChildAssociationRef childAssocRef)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_CREATED);
|
||||
|
||||
NodeRef nodeRef = childAssocRef.getChildRef();
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
|
||||
// Sometimes onCreateNode policy is out of order
|
||||
this.propertiesBefore = null;
|
||||
setBeforeProperties(Collections.emptyMap());
|
||||
setAfterProperties(helper.getProperties(nodeRef));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
|
||||
createBuilderIfAbsent(newChildAssocRef.getChildRef());
|
||||
setBeforePrimaryHierarchy(helper.getPrimaryHierarchy(oldChildAssocRef.getParentRef(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetNodeType(NodeRef nodeRef, QName before, QName after)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
nodeTypeBefore = before;
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateProperties(NodeRef nodeRef, Map<QName, Serializable> before, Map<QName, Serializable> after)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
|
||||
// Sometimes we don't get the 'before', so just use the latest
|
||||
if (before.isEmpty() && this.propertiesAfter != null)
|
||||
{
|
||||
before = this.propertiesAfter;
|
||||
}
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
setBeforeProperties(before);
|
||||
setAfterProperties(after);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDeleteNode(NodeRef nodeRef)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_DELETED);
|
||||
createBuilderIfAbsent(nodeRef, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
|
||||
{
|
||||
eventTypes.add(EventType.NODE_UPDATED);
|
||||
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);
|
||||
removeAspect(aspectTypeQName);
|
||||
createBuilderIfAbsent(nodeRef);
|
||||
}
|
||||
|
||||
void removeAspect(QName aspectTypeQName)
|
||||
{
|
||||
if (aspectsAdded.contains(aspectTypeQName))
|
||||
{
|
||||
aspectsAdded.remove(aspectTypeQName);
|
||||
}
|
||||
else
|
||||
{
|
||||
aspectsRemoved.add(aspectTypeQName);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAfterProperties(Map<QName, Serializable> after)
|
||||
{
|
||||
propertiesAfter = after;
|
||||
}
|
||||
|
||||
private void setBeforeProperties(Map<QName, Serializable> before)
|
||||
{
|
||||
// Don't overwrite the original value if there are multiple calls.
|
||||
if (propertiesBefore == null)
|
||||
{
|
||||
propertiesBefore = before;
|
||||
}
|
||||
}
|
||||
|
||||
private void setBeforePrimaryHierarchy(List<String> before)
|
||||
{
|
||||
// Don't overwrite the original value if there are multiple calls.
|
||||
if (primaryHierarchyBefore == null)
|
||||
{
|
||||
primaryHierarchyBefore = before;
|
||||
}
|
||||
}
|
||||
|
||||
private NodeResource buildNodeResource()
|
||||
{
|
||||
if (resourceBuilder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (eventTypes.getLast() != EventType.NODE_DELETED)
|
||||
{
|
||||
// Check the node still exists.
|
||||
// This could happen in tests where a node is deleted before the afterCommit code is
|
||||
// executed (For example, see ThumbnailServiceImplTest#testIfNodesExistsAfterCreateThumbnail).
|
||||
if (helper.nodeExists(entityReference))
|
||||
{
|
||||
// We are setting the details at the end of the Txn by getting the latest info
|
||||
createBuilderIfAbsent(entityReference, true);
|
||||
}
|
||||
}
|
||||
// Now create an instance of NodeResource
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
protected NodeResource buildNodeResourceBeforeDelta(NodeResource after)
|
||||
{
|
||||
if (after == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Builder builder = NodeResource.builder();
|
||||
|
||||
ZonedDateTime modifiedAt = null;
|
||||
Map<QName, Serializable> changedPropsBefore = getBeforeMapChanges(propertiesBefore, propertiesAfter);
|
||||
if (!changedPropsBefore.isEmpty())
|
||||
{
|
||||
// Set only the changed properties
|
||||
Map<String, Serializable> mappedProps = helper.mapToNodeProperties(changedPropsBefore);
|
||||
if (!mappedProps.isEmpty())
|
||||
{
|
||||
builder.setProperties(mappedProps);
|
||||
resourceBeforeAllFieldsNull = false;
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> localizedProps =helper.getLocalizedPropertiesBefore(changedPropsBefore, after);
|
||||
if (!localizedProps.isEmpty())
|
||||
{
|
||||
builder.setLocalizedProperties(localizedProps);
|
||||
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;
|
||||
}
|
||||
modifiedAt =
|
||||
helper.getZonedDateTime((Date) changedPropsBefore.get(ContentModel.PROP_MODIFIED));
|
||||
}
|
||||
|
||||
// Handle case where the content does not exist on the propertiesBefore
|
||||
if (propertiesBefore != null && !propertiesBefore.containsKey(ContentModel.PROP_CONTENT) &&
|
||||
propertiesAfter != null && propertiesAfter.containsKey(ContentModel.PROP_CONTENT))
|
||||
{
|
||||
builder.setContent(new ContentInfo());
|
||||
resourceBeforeAllFieldsNull = false;
|
||||
}
|
||||
|
||||
Set<String> aspectsBefore = getMappedAspectsBefore(after.getAspectNames());
|
||||
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;
|
||||
}
|
||||
|
||||
// Only set modifiedAt if one of the other fields is also not null
|
||||
if (modifiedAt != null && !resourceBeforeAllFieldsNull)
|
||||
{
|
||||
builder.setModifiedAt(modifiedAt);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Set<String> getMappedAspectsBefore(Set<String> currentAspects)
|
||||
{
|
||||
if (currentAspects == null)
|
||||
{
|
||||
currentAspects = Collections.emptySet();
|
||||
}
|
||||
if (hasChangedAspect())
|
||||
{
|
||||
Set<String> removed = helper.mapToNodeAspects(aspectsRemoved);
|
||||
Set<String> added = helper.mapToNodeAspects(aspectsAdded);
|
||||
|
||||
Set<String> before = new HashSet<>();
|
||||
if (!removed.isEmpty() || !added.isEmpty())
|
||||
{
|
||||
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;
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private boolean hasChangedAspect()
|
||||
{
|
||||
if ((aspectsRemoved.isEmpty() && aspectsAdded.isEmpty()) ||
|
||||
org.apache.commons.collections.CollectionUtils.isEqualCollection(aspectsAdded, aspectsRemoved))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private <K, V> Map<K, V> getBeforeMapChanges(Map<K, V> before, Map<K, V> after)
|
||||
{
|
||||
if (before == null)
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
if (after == null)
|
||||
{
|
||||
after = Collections.emptyMap();
|
||||
}
|
||||
// Get before values that changed
|
||||
Map<K, V> beforeDelta = new HashMap<>(before);
|
||||
Map<K, V> afterDelta = new HashMap<>(after);
|
||||
|
||||
beforeDelta.entrySet().removeAll(after.entrySet());
|
||||
|
||||
// Add nulls for before properties
|
||||
Set<K> beforeKeys = before.keySet();
|
||||
Set<K> newKeys = afterDelta.keySet();
|
||||
newKeys.removeAll(beforeKeys);
|
||||
|
||||
for (K key : newKeys)
|
||||
{
|
||||
beforeDelta.put(key, null);
|
||||
}
|
||||
|
||||
return beforeDelta;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EventType getDerivedEvent()
|
||||
{
|
||||
if (isTemporaryEntity())
|
||||
{
|
||||
// This event will be filtered out, but we set the correct
|
||||
// event type anyway for debugging purposes
|
||||
return EventType.NODE_DELETED;
|
||||
}
|
||||
else if (eventTypes.contains(EventType.NODE_CREATED))
|
||||
{
|
||||
return EventType.NODE_CREATED;
|
||||
}
|
||||
else if (eventTypes.getLast() == EventType.NODE_DELETED)
|
||||
{
|
||||
return EventType.NODE_DELETED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to first event
|
||||
return eventTypes.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTemporaryEntity()
|
||||
{
|
||||
return eventTypes.contains(EventType.NODE_CREATED) && eventTypes.getLast() == EventType.NODE_DELETED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QName getEntityType()
|
||||
{
|
||||
return nodeType;
|
||||
}
|
||||
|
||||
public List<QName> getAspectsAdded()
|
||||
{
|
||||
return aspectsAdded;
|
||||
}
|
||||
|
||||
public List<QName> getAspectsRemoved()
|
||||
{
|
||||
return aspectsRemoved;
|
||||
}
|
||||
|
||||
public boolean isResourceBeforeAllFieldsNull()
|
||||
{
|
||||
return resourceBeforeAllFieldsNull;
|
||||
}
|
||||
|
||||
public boolean isEventTypeEqualTo(EventType eventType)
|
||||
{
|
||||
return this.getDerivedEvent().getType().equals(eventType.getType());
|
||||
}
|
||||
|
||||
protected void setResourceBeforeAllFieldsNull(boolean resourceBeforeAllFieldsNull){
|
||||
this.resourceBeforeAllFieldsNull = resourceBeforeAllFieldsNull;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -25,14 +25,8 @@
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import org.alfresco.repo.event.v1.model.DataAttributes;
|
||||
import org.alfresco.repo.event.v1.model.EventData;
|
||||
import org.alfresco.repo.event.v1.model.EventType;
|
||||
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;
|
||||
|
||||
@ -41,50 +35,12 @@ import org.alfresco.service.namespace.QName;
|
||||
*
|
||||
* @author Sara Aspery
|
||||
*/
|
||||
public class PeerAssociationEventConsolidator implements PeerAssociationEventSupportedPolicies
|
||||
public class PeerAssociationEventConsolidator extends EventConsolidator<AssociationRef, PeerAssociationResource> implements PeerAssociationEventSupportedPolicies
|
||||
{
|
||||
private final Deque<EventType> eventTypes;
|
||||
|
||||
protected final AssociationRef associationRef;
|
||||
|
||||
private PeerAssociationResource resource;
|
||||
private final NodeResourceHelper helper;
|
||||
|
||||
public PeerAssociationEventConsolidator(AssociationRef associationRef, NodeResourceHelper 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<DataAttributes<PeerAssociationResource>> getRepoEvent(EventInfo eventInfo)
|
||||
{
|
||||
EventType eventType = getDerivedEvent();
|
||||
|
||||
DataAttributes<PeerAssociationResource> eventData = buildEventData(eventInfo, resource);
|
||||
|
||||
return RepoEvent.<DataAttributes<PeerAssociationResource>>builder()
|
||||
.setId(eventInfo.getId())
|
||||
.setSource(eventInfo.getSource())
|
||||
.setTime(eventInfo.getTimestamp())
|
||||
.setType(eventType.getType())
|
||||
.setData(eventData)
|
||||
.setDataschema(EventJSONSchema.getSchemaV1(eventType))
|
||||
.build();
|
||||
}
|
||||
|
||||
protected DataAttributes<PeerAssociationResource> buildEventData(EventInfo eventInfo, PeerAssociationResource resource)
|
||||
{
|
||||
return EventData.<PeerAssociationResource>builder()
|
||||
.setEventGroupId(eventInfo.getTxnId())
|
||||
.setResource(resource)
|
||||
.build();
|
||||
super(associationRef, helper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,12 +76,10 @@ public class PeerAssociationEventConsolidator implements PeerAssociationEventSup
|
||||
return new PeerAssociationResource(sourceId, targetId, assocType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a derived event for a transaction.
|
||||
*/
|
||||
private EventType getDerivedEvent()
|
||||
@Override
|
||||
protected EventType getDerivedEvent()
|
||||
{
|
||||
if (isTemporaryPeerAssociation())
|
||||
if (isTemporaryEntity())
|
||||
{
|
||||
// This event will be filtered out, but we set the correct
|
||||
// event type anyway for debugging purposes
|
||||
@ -146,34 +100,16 @@ public class PeerAssociationEventConsolidator implements PeerAssociationEventSup
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
@Override
|
||||
public boolean isTemporaryEntity()
|
||||
{
|
||||
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()
|
||||
@Override
|
||||
public QName getEntityType()
|
||||
{
|
||||
return associationRef.getTypeQName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event types.
|
||||
*
|
||||
* @return Deque<EventType> queue of event types
|
||||
*/
|
||||
public Deque<EventType> getEventTypes()
|
||||
{
|
||||
return eventTypes;
|
||||
return entityReference.getTypeQName();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -767,7 +767,7 @@ public abstract class AbstractEventsService extends TransactionListenerAdapter
|
||||
|
||||
void addEvents(List<Event> events)
|
||||
{
|
||||
events.addAll(events);
|
||||
this.events.addAll(events);
|
||||
}
|
||||
|
||||
void clear()
|
||||
|
83
repository/src/main/java/org/alfresco/util/TriPredicate.java
Normal file
83
repository/src/main/java/org/alfresco/util/TriPredicate.java
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a predicate (boolean-valued function) of three arguments. This is the three-arity specialization of Predicate.
|
||||
* This is a functional interface whose functional method is test(Object, Object, Object).
|
||||
*
|
||||
* @param <T> - type of the first argument to the predicate
|
||||
* @param <U> - type of the second argument the predicate
|
||||
* @param <V> - type of the third argument the predicate
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TriPredicate<T, U, V>
|
||||
{
|
||||
|
||||
/**
|
||||
* Evaluates this predicate on the given arguments.
|
||||
*
|
||||
* @param t - first input argument
|
||||
* @param u - second input argument
|
||||
* @param v - third input argument
|
||||
* @return true if the input arguments match the predicate, otherwise false
|
||||
*/
|
||||
boolean test(T t, U u, V v);
|
||||
|
||||
/**
|
||||
* Creates a composed predicate that represents a logical AND of this predicate and another.
|
||||
*
|
||||
* @param other - predicate that will be logically-ANDed with this predicate
|
||||
* @return composed predicate
|
||||
*/
|
||||
default TriPredicate<T, U, V> and(TriPredicate<? super T, ? super U, ? super V> other)
|
||||
{
|
||||
Objects.requireNonNull(other);
|
||||
return (T t, U u, V v) -> test(t, u, v) && other.test(t, u, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a predicate that represents the logical negation of this predicate
|
||||
*/
|
||||
default TriPredicate<T, U, V> negate()
|
||||
{
|
||||
return (T t, U u, V v) -> !test(t, u, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a composed predicate that represents a logical OR of this predicate and another.
|
||||
*
|
||||
* @param other - predicate that will be logically-ORed with this predicate
|
||||
* @return composed predicate
|
||||
*/
|
||||
default TriPredicate<T, U, V> or(TriPredicate<? super T, ? super U, ? super V> other)
|
||||
{
|
||||
Objects.requireNonNull(other);
|
||||
return (T t, U u, V v) -> test(t, u, v) || other.test(t, u, v);
|
||||
}
|
||||
}
|
@ -41,11 +41,9 @@
|
||||
<property name="transactionService" ref="transactionService"/>
|
||||
<property name="personService" ref="personService"/>
|
||||
<property name="nodeResourceHelper" ref="nodeResourceHelper"/>
|
||||
<property name="eventGeneratorQueue" ref="eventGeneratorQueue"/>
|
||||
<property name="eventSender" ref="#{ ${repo.event2.queue.skip} == true ? 'directEventSender' : 'enqueuingEventSender' }"/>
|
||||
<property name="nodeDAO" ref="nodeDAO"/>
|
||||
<property name="enabled">
|
||||
<value>${repo.event2.enabled}</value>
|
||||
</property>
|
||||
<property name="enabled" value="${repo.event2.enabled}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="baseNodeResourceHelper" abstract="true">
|
||||
@ -61,14 +59,13 @@
|
||||
|
||||
<bean id="eventGeneratorV2" class="org.alfresco.repo.event2.EventGenerator" parent="baseEventGeneratorV2"/>
|
||||
|
||||
<bean id="eventGeneratorQueue" class="org.alfresco.repo.event2.EventGeneratorQueue" >
|
||||
<property name="enqueueThreadPoolExecutor">
|
||||
<ref bean="eventAsyncEnqueueThreadPool" />
|
||||
</property>
|
||||
<property name="dequeueThreadPoolExecutor">
|
||||
<ref bean="eventAsyncDequeueThreadPool" />
|
||||
</property>
|
||||
<property name="event2MessageProducer" ref="event2MessageProducer"/>
|
||||
<bean id="directEventSender" class="org.alfresco.repo.event2.DirectEventSender">
|
||||
<property name="event2MessageProducer" ref="event2MessageProducer"/>
|
||||
</bean>
|
||||
|
||||
<bean id="enqueuingEventSender" class="org.alfresco.repo.event2.EnqueuingEventSender" parent="directEventSender" lazy-init="true">
|
||||
<property name="enqueueThreadPoolExecutor" ref="eventAsyncEnqueueThreadPool"/>
|
||||
<property name="dequeueThreadPoolExecutor" ref="eventAsyncDequeueThreadPool"/>
|
||||
</bean>
|
||||
|
||||
<bean id="eventAsyncEnqueueThreadPool" class="org.alfresco.util.ThreadPoolExecutorFactoryBean">
|
||||
|
@ -1229,6 +1229,8 @@ repo.event2.filter.childAssocTypes=rn:rendition
|
||||
repo.event2.filter.users=
|
||||
# Topic name
|
||||
repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2
|
||||
# Specifies if messages should be enqueued in in-memory queue or sent directly to the topic
|
||||
repo.event2.queue.skip=false
|
||||
# Thread pool for async enqueue of repo events
|
||||
repo.event2.queue.enqueueThreadPool.priority=1
|
||||
repo.event2.queue.enqueueThreadPool.coreSize=8
|
||||
|
@ -23,7 +23,6 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
@ -72,6 +71,7 @@ import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@ -108,23 +108,24 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
|
||||
@Autowired
|
||||
protected CustomModelService customModelService;
|
||||
|
||||
@Qualifier("descriptorComponent")
|
||||
@Autowired
|
||||
@Qualifier("descriptorComponent")
|
||||
protected DescriptorService descriptorService;
|
||||
|
||||
@Autowired
|
||||
protected ObjectMapper event2ObjectMapper;
|
||||
@Qualifier("event2ObjectMapper")
|
||||
protected ObjectMapper objectMapper;
|
||||
|
||||
@Autowired @Qualifier("eventGeneratorV2")
|
||||
@Autowired
|
||||
@Qualifier("eventGeneratorV2")
|
||||
protected EventGenerator eventGenerator;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("eventGeneratorQueue")
|
||||
protected EventGeneratorQueue eventQueue;
|
||||
|
||||
@Autowired
|
||||
private NamespaceDAO namespaceDAO;
|
||||
|
||||
@Value("${repo.event2.queue.skip}")
|
||||
protected boolean skipEventQueue;
|
||||
|
||||
protected NodeRef rootNodeRef;
|
||||
|
||||
@BeforeClass
|
||||
@ -134,7 +135,7 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterAll() throws Exception
|
||||
public static void afterAll()
|
||||
{
|
||||
CAMEL_CONTEXT.stop();
|
||||
}
|
||||
@ -144,7 +145,7 @@ public abstract class AbstractContextAwareRepoEvent extends BaseSpringTest
|
||||
{
|
||||
if (!isCamelConfigured)
|
||||
{
|
||||
dataFormat = new JacksonDataFormat(event2ObjectMapper, RepoEvent.class);
|
||||
dataFormat = new JacksonDataFormat(objectMapper, RepoEvent.class);
|
||||
configRoute();
|
||||
isCamelConfigured = true;
|
||||
}
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.ContextHierarchy;
|
||||
|
||||
@ContextHierarchy({
|
||||
// Context hierarchy inherits context config from parent classes and extends it with TestConfig from this class
|
||||
@ContextConfiguration(classes = DirectEventGeneratorTest.TestConfig.class)
|
||||
})
|
||||
public class DirectEventGeneratorTest extends EventGeneratorTest
|
||||
{
|
||||
@Autowired
|
||||
private InstantiatedBeansRegistry instantiatedBeansRegistry;
|
||||
|
||||
@Autowired
|
||||
private EventSender directEventSender;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass()
|
||||
{
|
||||
System.setProperty("repo.event2.queue.skip", "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfEnqueuingEventSenderIsNotInstantiated()
|
||||
{
|
||||
final Set<String> instantiatedBeans = this.instantiatedBeansRegistry.getBeans();
|
||||
|
||||
assertTrue(skipEventQueue);
|
||||
assertFalse(instantiatedBeans.contains("enqueuingEventSender"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfDirectSenderIsSetInEventGenerator()
|
||||
{
|
||||
assertTrue(skipEventQueue);
|
||||
assertEquals(directEventSender, eventGenerator.getEventSender());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class TestConfig
|
||||
{
|
||||
@Bean
|
||||
public BeanPostProcessor instantiatedBeansRegistry()
|
||||
{
|
||||
return new InstantiatedBeansRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class InstantiatedBeansRegistry implements BeanPostProcessor
|
||||
{
|
||||
private final Set<String> registeredBeans = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException
|
||||
{
|
||||
registeredBeans.add(beanName);
|
||||
return bean;
|
||||
}
|
||||
|
||||
public Set<String> getBeans() {
|
||||
return registeredBeans;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class EnqueuingEventGeneratorTest extends EventGeneratorTest
|
||||
{
|
||||
@Autowired
|
||||
private EventSender enqueuingEventSender;
|
||||
|
||||
@Test
|
||||
public void testIfEnqueuingSenderIsSetInEventGenerator()
|
||||
{
|
||||
assertFalse(skipEventQueue);
|
||||
assertEquals(enqueuingEventSender, eventGenerator.getEventSender());
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -35,6 +35,7 @@ import static org.mockito.Mockito.when;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
@ -51,9 +52,9 @@ import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class EventGeneratorQueueUnitTest
|
||||
public class EnqueuingEventSenderUnitTest
|
||||
{
|
||||
private EventGeneratorQueue queue;
|
||||
private EnqueuingEventSender eventSender;
|
||||
|
||||
private Event2MessageProducer bus;
|
||||
private ExecutorService enqueuePool;
|
||||
@ -64,15 +65,15 @@ public class EventGeneratorQueueUnitTest
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
queue = new EventGeneratorQueue();
|
||||
eventSender = new EnqueuingEventSender();
|
||||
|
||||
enqueuePool = newThreadPool();
|
||||
queue.setEnqueueThreadPoolExecutor(enqueuePool);
|
||||
eventSender.setEnqueueThreadPoolExecutor(enqueuePool);
|
||||
dequeuePool = newThreadPool();
|
||||
queue.setDequeueThreadPoolExecutor(dequeuePool);
|
||||
eventSender.setDequeueThreadPoolExecutor(dequeuePool);
|
||||
|
||||
bus = mock(Event2MessageProducer.class);
|
||||
queue.setEvent2MessageProducer(bus);
|
||||
eventSender.setEvent2MessageProducer(bus);
|
||||
|
||||
events = new HashMap<>();
|
||||
|
||||
@ -83,6 +84,7 @@ public class EventGeneratorQueueUnitTest
|
||||
public void teardown()
|
||||
{
|
||||
enqueuePool.shutdown();
|
||||
dequeuePool.shutdown();
|
||||
}
|
||||
|
||||
private void setupEventsRecorder()
|
||||
@ -104,7 +106,7 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldReceiveSingleQuickMessage() throws Exception
|
||||
{
|
||||
queue.accept(messageWithDelay("A", 55l));
|
||||
eventSender.accept(messageWithDelay("A", 55l));
|
||||
|
||||
sleep(150l);
|
||||
|
||||
@ -115,7 +117,7 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldNotReceiveEventsWhenMessageIsNull() throws Exception
|
||||
{
|
||||
queue.accept(() -> { return null; });
|
||||
eventSender.accept(() -> { return null; });
|
||||
|
||||
sleep(150l);
|
||||
|
||||
@ -124,9 +126,9 @@ public class EventGeneratorQueueUnitTest
|
||||
|
||||
@Test
|
||||
public void shouldReceiveMultipleMessagesPreservingOrderScenarioOne() throws Exception {
|
||||
queue.accept(messageWithDelay("A", 0l));
|
||||
queue.accept(messageWithDelay("B", 100l));
|
||||
queue.accept(messageWithDelay("C", 200l));
|
||||
eventSender.accept(messageWithDelay("A", 0l));
|
||||
eventSender.accept(messageWithDelay("B", 100l));
|
||||
eventSender.accept(messageWithDelay("C", 200l));
|
||||
|
||||
sleep(450l);
|
||||
|
||||
@ -139,9 +141,9 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldReceiveMultipleMessagesPreservingOrderScenarioTwo() throws Exception
|
||||
{
|
||||
queue.accept(messageWithDelay("A", 300l));
|
||||
queue.accept(messageWithDelay("B", 150l));
|
||||
queue.accept(messageWithDelay("C", 0l));
|
||||
eventSender.accept(messageWithDelay("A", 300l));
|
||||
eventSender.accept(messageWithDelay("B", 150l));
|
||||
eventSender.accept(messageWithDelay("C", 0l));
|
||||
|
||||
sleep(950l);
|
||||
|
||||
@ -154,10 +156,10 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenMakerPoisoned() throws Exception
|
||||
{
|
||||
queue.accept(messageWithDelay("A", 300l));
|
||||
queue.accept(() -> {throw new RuntimeException("Boom! (not to worry, this is a test)");});
|
||||
queue.accept(messageWithDelay("B", 55l));
|
||||
queue.accept(messageWithDelay("C", 0l));
|
||||
eventSender.accept(messageWithDelay("A", 300l));
|
||||
eventSender.accept(() -> {throw new RuntimeException("Boom! (not to worry, this is a test)");});
|
||||
eventSender.accept(messageWithDelay("B", 55l));
|
||||
eventSender.accept(messageWithDelay("C", 0l));
|
||||
|
||||
sleep(950l);
|
||||
|
||||
@ -170,12 +172,12 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenSenderPoisoned() throws Exception
|
||||
{
|
||||
Callable<RepoEvent<?>> makerB = messageWithDelay("B", 55l);
|
||||
RepoEvent<?> messageB = makerB.call();
|
||||
Callable<Optional<RepoEvent<?>>> makerB = messageWithDelay("B", 55l);
|
||||
RepoEvent<?> messageB = makerB.call().get();
|
||||
doThrow(new RuntimeException("Boom! (not to worry, this is a test)")).when(bus).send(messageB);
|
||||
queue.accept(messageWithDelay("A", 300l));
|
||||
queue.accept(makerB);
|
||||
queue.accept(messageWithDelay("C", 0l));
|
||||
eventSender.accept(messageWithDelay("A", 300l));
|
||||
eventSender.accept(makerB);
|
||||
eventSender.accept(messageWithDelay("C", 0l));
|
||||
|
||||
sleep(950l);
|
||||
|
||||
@ -187,10 +189,10 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenMakerPoisonedWithError() throws Exception
|
||||
{
|
||||
queue.accept(messageWithDelay("A", 300l));
|
||||
queue.accept(() -> {throw new OutOfMemoryError("Boom! (not to worry, this is a test)");});
|
||||
queue.accept(messageWithDelay("B", 55l));
|
||||
queue.accept(messageWithDelay("C", 0l));
|
||||
eventSender.accept(messageWithDelay("A", 300l));
|
||||
eventSender.accept(() -> {throw new OutOfMemoryError("Boom! (not to worry, this is a test)");});
|
||||
eventSender.accept(messageWithDelay("B", 55l));
|
||||
eventSender.accept(messageWithDelay("C", 0l));
|
||||
|
||||
sleep(950l);
|
||||
|
||||
@ -203,12 +205,12 @@ public class EventGeneratorQueueUnitTest
|
||||
@Test
|
||||
public void shouldReceiveMultipleMessagesPreservingOrderEvenWhenSenderPoisonedWithError() throws Exception
|
||||
{
|
||||
Callable<RepoEvent<?>> makerB = messageWithDelay("B", 55l);
|
||||
RepoEvent<?> messageB = makerB.call();
|
||||
Callable<Optional<RepoEvent<?>>> makerB = messageWithDelay("B", 55l);
|
||||
RepoEvent<?> messageB = makerB.call().get();
|
||||
doThrow(new OutOfMemoryError("Boom! (not to worry, this is a test)")).when(bus).send(messageB);
|
||||
queue.accept(messageWithDelay("A", 300l));
|
||||
queue.accept(makerB);
|
||||
queue.accept(messageWithDelay("C", 0l));
|
||||
eventSender.accept(messageWithDelay("A", 300l));
|
||||
eventSender.accept(makerB);
|
||||
eventSender.accept(messageWithDelay("C", 0l));
|
||||
|
||||
sleep(950l);
|
||||
|
||||
@ -217,33 +219,32 @@ public class EventGeneratorQueueUnitTest
|
||||
assertEquals("C", recordedEvents.get(1).getId());
|
||||
}
|
||||
|
||||
private Callable<RepoEvent<?>> messageWithDelay(String id, long delay)
|
||||
private Callable<Optional<RepoEvent<?>>> messageWithDelay(String id, long delay)
|
||||
{
|
||||
Callable<RepoEvent<?>> res = new Callable<RepoEvent<?>>() {
|
||||
|
||||
return new Callable<Optional<RepoEvent<?>>>()
|
||||
{
|
||||
@Override
|
||||
public RepoEvent<?> call() throws Exception
|
||||
public Optional<RepoEvent<?>> call() throws Exception
|
||||
{
|
||||
if(delay != 0)
|
||||
if (delay != 0)
|
||||
{
|
||||
sleep(delay);
|
||||
sleep(delay);
|
||||
}
|
||||
return newRepoEvent(id);
|
||||
}
|
||||
|
||||
return Optional.of(newRepoEvent(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
private RepoEvent<?> newRepoEvent(String id)
|
||||
{
|
||||
RepoEvent<?> ev = events.get(id);
|
||||
if (ev!=null)
|
||||
if (ev != null)
|
||||
return ev;
|
||||
|
||||
ev = mock(RepoEvent.class);
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -43,7 +43,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBeforeRemovedAndAddedEmpty()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
|
||||
Set<String> currentAspects = new HashSet<>();
|
||||
currentAspects.add("cm:geographic");
|
||||
@ -57,7 +57,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_AspectRemoved()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
Set<String> currentAspects = new HashSet<>();
|
||||
@ -79,7 +79,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_AspectAdded()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
Set<String> currentAspects = new HashSet<>();
|
||||
@ -102,7 +102,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_AspectAddedAndRemoved()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
Set<String> currentAspects = new HashSet<>();
|
||||
@ -125,7 +125,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_AspectRemovedAndAdded()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
@ -150,7 +150,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_AspectAddedTwiceRemovedOnce()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
@ -178,7 +178,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_AspectRemovedTwiceAddedOnce()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
@ -206,7 +206,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testGetMappedAspectsBefore_FilteredAspectAdded()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASPECT_COPIEDFROM);
|
||||
|
||||
Set<String> currentAspects = new HashSet<>();
|
||||
@ -227,7 +227,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testAddAspect()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
assertEquals(1, eventConsolidator.getAspectsAdded().size());
|
||||
@ -238,7 +238,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testRemoveAspect()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
assertEquals(0, eventConsolidator.getAspectsAdded().size());
|
||||
@ -249,7 +249,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testAddAspectRemoveAspect()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
@ -260,7 +260,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testRemoveAspectAddAspect()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
||||
@ -271,7 +271,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testAddAspectTwiceRemoveAspectOnce()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
@ -284,7 +284,7 @@ public class EventConsolidatorUnitTest
|
||||
@Test
|
||||
public void testAddAspectOnceRemoveAspectTwice()
|
||||
{
|
||||
EventConsolidator eventConsolidator = new EventConsolidator(nodeResourceHelper);
|
||||
NodeEventConsolidator eventConsolidator = new NodeEventConsolidator(nodeResourceHelper);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.addAspect(ContentModel.ASSOC_CONTAINS);
|
||||
eventConsolidator.removeAspect(ContentModel.ASSOC_CONTAINS);
|
||||
|
@ -25,94 +25,16 @@
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.jms.Destination;
|
||||
import jakarta.jms.JMSException;
|
||||
import jakarta.jms.Message;
|
||||
import jakarta.jms.MessageConsumer;
|
||||
import jakarta.jms.MessageListener;
|
||||
import jakarta.jms.Session;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.apache.activemq.ActiveMQConnection;
|
||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.command.ActiveMQTextMessage;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class EventGeneratorDisabledTest extends AbstractContextAwareRepoEvent
|
||||
public class EventGeneratorDisabledTest extends EventGeneratorTest
|
||||
{
|
||||
private static final String EVENT2_TOPIC_NAME = "alfresco.repo.event2";
|
||||
private static final String BROKER_URL = "tcp://localhost:61616";
|
||||
|
||||
@Autowired @Qualifier("event2ObjectMapper")
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Autowired
|
||||
protected ObjectMapper event2ObjectMapper;
|
||||
|
||||
//private EventGenerator eventGenerator;
|
||||
|
||||
private ActiveMQConnection connection;
|
||||
protected List<RepoEvent<?>> receivedEvents;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception
|
||||
{
|
||||
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
|
||||
connection = (ActiveMQConnection) connectionFactory.createConnection();
|
||||
connection.start();
|
||||
|
||||
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||
Destination destination = session.createTopic(EVENT2_TOPIC_NAME);
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
|
||||
receivedEvents = Collections.synchronizedList(new LinkedList<>());
|
||||
consumer.setMessageListener(new MessageListener()
|
||||
{
|
||||
@Override
|
||||
public void onMessage(Message message)
|
||||
{
|
||||
String text = getText(message);
|
||||
RepoEvent<?> event = toRepoEvent(text);
|
||||
receivedEvents.add(event);
|
||||
}
|
||||
|
||||
private RepoEvent<?> toRepoEvent(String json)
|
||||
{
|
||||
try
|
||||
{
|
||||
return objectMapper.readValue(json, RepoEvent.class);
|
||||
} catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdownTopicListener() throws Exception
|
||||
{
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotReceiveEvent2EventsOnNodeCreation() throws Exception
|
||||
public void shouldNotReceiveEvent2EventsOnNodeCreation()
|
||||
{
|
||||
if (eventGenerator.isEnabled())
|
||||
{
|
||||
@ -127,44 +49,29 @@ public class EventGeneratorDisabledTest extends AbstractContextAwareRepoEvent
|
||||
assertTrue(receivedEvents.size() == 0);
|
||||
|
||||
eventGenerator.enable();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReceiveEvent2EventsOnNodeCreation() throws Exception
|
||||
@Override
|
||||
public void shouldReceiveEvent2EventsOnNodeCreation()
|
||||
{
|
||||
if (!eventGenerator.isEnabled())
|
||||
{
|
||||
eventGenerator.enable();
|
||||
}
|
||||
|
||||
createNode(ContentModel.TYPE_CONTENT);
|
||||
|
||||
Awaitility.await().atMost(6, TimeUnit.SECONDS).until(() -> receivedEvents.size() == 1);
|
||||
|
||||
assertTrue(EVENT_CONTAINER.getEvents().size() == 1);
|
||||
assertTrue(receivedEvents.size() == 1);
|
||||
|
||||
RepoEvent<?> sent = getRepoEvent(1);
|
||||
RepoEvent<?> received = receivedEvents.get(0);
|
||||
assertEventsEquals("Events are different!", sent, received);
|
||||
}
|
||||
|
||||
private void assertEventsEquals(String message, RepoEvent<?> expected, RepoEvent<?> current)
|
||||
{
|
||||
assertEquals(message, expected, current);
|
||||
super.shouldReceiveEvent2EventsOnNodeCreation();
|
||||
}
|
||||
|
||||
private static String getText(Message message)
|
||||
@Test
|
||||
@Override
|
||||
public void shouldReceiveEvent2EventsInOrder()
|
||||
{
|
||||
try
|
||||
if (!eventGenerator.isEnabled())
|
||||
{
|
||||
ActiveMQTextMessage am = (ActiveMQTextMessage) message;
|
||||
return am.getText();
|
||||
} catch (JMSException e)
|
||||
{
|
||||
return null;
|
||||
eventGenerator.enable();
|
||||
}
|
||||
}
|
||||
|
||||
super.shouldReceiveEvent2EventsInOrder();
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ import jakarta.jms.MessageListener;
|
||||
import jakarta.jms.Session;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.event.databind.ObjectMapperFactory;
|
||||
import org.alfresco.repo.event.v1.model.RepoEvent;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.apache.activemq.ActiveMQConnection;
|
||||
@ -51,24 +50,25 @@ import org.apache.activemq.command.ActiveMQTopic;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
|
||||
public abstract class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
{
|
||||
private static final String EVENT2_TOPIC_NAME = "alfresco.repo.event2";
|
||||
|
||||
private static final long DUMP_BROKER_TIMEOUT = 50000000l;
|
||||
|
||||
@Autowired @Qualifier("event2ObjectMapper")
|
||||
private ObjectMapper objectMapper;
|
||||
private static final long DUMP_BROKER_TIMEOUT = 50000000L;
|
||||
|
||||
private ActiveMQConnection connection;
|
||||
protected List<RepoEvent<?>> receivedEvents;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass()
|
||||
{
|
||||
System.setProperty("repo.event2.queue.skip", "false");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void startupTopicListener() throws Exception
|
||||
{
|
||||
@ -116,11 +116,6 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
}
|
||||
}
|
||||
|
||||
protected ObjectMapper createObjectMapper()
|
||||
{
|
||||
return ObjectMapperFactory.createInstance();
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdownTopicListener() throws Exception
|
||||
{
|
||||
@ -129,30 +124,21 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReceiveEvent2EventsOnNodeCreation() throws Exception
|
||||
public void shouldReceiveEvent2EventsOnNodeCreation()
|
||||
{
|
||||
createNode(ContentModel.TYPE_CONTENT);
|
||||
|
||||
Awaitility.await().atMost(6, TimeUnit.SECONDS).until(() -> receivedEvents.size() == 1);
|
||||
|
||||
assertEquals(1, EVENT_CONTAINER.getEvents().size());
|
||||
assertEquals(1, receivedEvents.size());
|
||||
RepoEvent<?> sent = getRepoEvent(1);
|
||||
RepoEvent<?> received = receivedEvents.get(0);
|
||||
assertEventsEquals("Events are different!", sent, received);
|
||||
}
|
||||
|
||||
private void assertEventsEquals(String message, RepoEvent<?> expected, RepoEvent<?> current)
|
||||
{
|
||||
if (DEBUG)
|
||||
{
|
||||
System.err.println("XP: " + expected);
|
||||
System.err.println("CU: " + current);
|
||||
}
|
||||
|
||||
assertEquals(message, expected, current);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReceiveEvent2EventsInOrder() throws Exception
|
||||
public void shouldReceiveEvent2EventsInOrder()
|
||||
{
|
||||
NodeRef nodeRef = createNode(ContentModel.TYPE_CONTENT);
|
||||
updateNodeName(nodeRef, "TestFile-" + System.currentTimeMillis() + ".txt");
|
||||
@ -163,9 +149,20 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
RepoEvent<?> sentCreation = getRepoEvent(1);
|
||||
RepoEvent<?> sentUpdate = getRepoEvent(2);
|
||||
RepoEvent<?> sentDeletion = getRepoEvent(3);
|
||||
assertEquals("Expected create event!", sentCreation, (RepoEvent<?>) receivedEvents.get(0));
|
||||
assertEquals("Expected update event!", sentUpdate, (RepoEvent<?>) receivedEvents.get(1));
|
||||
assertEquals("Expected delete event!", sentDeletion, (RepoEvent<?>) receivedEvents.get(2));
|
||||
assertEquals("Expected create event!", sentCreation, receivedEvents.get(0));
|
||||
assertEquals("Expected update event!", sentUpdate, receivedEvents.get(1));
|
||||
assertEquals("Expected delete event!", sentDeletion, receivedEvents.get(2));
|
||||
}
|
||||
|
||||
private void assertEventsEquals(String message, RepoEvent<?> expected, RepoEvent<?> current)
|
||||
{
|
||||
if (DEBUG)
|
||||
{
|
||||
System.err.println("XP: " + expected);
|
||||
System.err.println("CU: " + current);
|
||||
}
|
||||
|
||||
assertEquals(message, expected, current);
|
||||
}
|
||||
|
||||
private static String getText(Message message)
|
||||
@ -174,7 +171,8 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
{
|
||||
ActiveMQTextMessage am = (ActiveMQTextMessage) message;
|
||||
return am.getText();
|
||||
} catch (JMSException e)
|
||||
}
|
||||
catch (JMSException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -206,7 +204,8 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
try
|
||||
{
|
||||
System.out.println("- " + queue.getQueueName());
|
||||
} catch (JMSException e)
|
||||
}
|
||||
catch (JMSException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -219,7 +218,8 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
try
|
||||
{
|
||||
System.out.println("- " + topic.getTopicName());
|
||||
} catch (JMSException e)
|
||||
}
|
||||
catch (JMSException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -230,18 +230,14 @@ public class EventGeneratorTest extends AbstractContextAwareRepoEvent
|
||||
MessageConsumer consumer = session.createConsumer(destination);
|
||||
|
||||
System.out.println("\nListening to topic " + EVENT2_TOPIC_NAME + "...");
|
||||
consumer.setMessageListener(new MessageListener()
|
||||
{
|
||||
@Override
|
||||
public void onMessage(Message message)
|
||||
{
|
||||
String text = getText(message);
|
||||
System.out.println("Received message " + message + "\n" + text + "\n");
|
||||
}
|
||||
consumer.setMessageListener(message -> {
|
||||
String text = getText(message);
|
||||
System.out.println("Received message " + message + "\n" + text + "\n");
|
||||
});
|
||||
|
||||
Thread.sleep(timeout);
|
||||
} finally
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.close();
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@ -35,7 +35,8 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
DeleteRepoEventIT.class,
|
||||
ChildAssociationRepoEventIT.class,
|
||||
PeerAssociationRepoEventIT.class,
|
||||
EventGeneratorTest.class,
|
||||
EnqueuingEventGeneratorTest.class,
|
||||
DirectEventGeneratorTest.class,
|
||||
EventGeneratorDisabledTest.class
|
||||
})
|
||||
public class RepoEvent2ITSuite
|
||||
|
@ -23,7 +23,6 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
@ -34,7 +33,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
@SuiteClasses({ EventFilterUnitTest.class,
|
||||
EventConsolidatorUnitTest.class,
|
||||
EventJSONSchemaUnitTest.class,
|
||||
EventGeneratorQueueUnitTest.class,
|
||||
EnqueuingEventSenderUnitTest.class,
|
||||
NodeResourceHelperUnitTest.class
|
||||
})
|
||||
public class RepoEvent2UnitSuite
|
||||
|
Loading…
x
Reference in New Issue
Block a user