Make sure you have completed prerequisites (see Prerequisites) and created a starter project (see Create a starting point Spring project).
To use Spring Integration based event handlers follow these steps:
Add the following dependency in the Maven project file (i.e. pom.xml):
<dependencies>
<!-- Alfresco Java SDK 6 Spring Integration Event Handler API Spring Boot Starter -->
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-java-event-api-spring-boot-starter</artifactId>
<version>6.2.0</version>
</dependency>
</dependencies>
Enable Spring Integration handlers in the src/main/resources/application.properties configuration file (by default pure Java event handlers is expected), add the following two extra properties:
# Where is Alfresco Active MQ JMS Broker running? spring.activemq.brokerUrl=tcp://localhost:61616 # This property is required if you want Spring Boot to auto-define the ActiveMQConnectionFactory, # otherwise you can define that bean in Spring config spring.jms.cache.enabled=false # Enable Spring Integration based event handlers alfresco.events.enableSpringIntegration=true # Turn off plain Java event handlers alfresco.events.enableHandlers=false
Remove the default Spring Boot starter dependency (i.e. <artifactId>spring-boot-starter</artifactId>).
Test it:
$ mvn clean package -Dlicense.skip=true
[INFO] Scanning for projects...
...
$ java -jar target/events-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.2)
2021-03-29 13:44:49.441 INFO 2599 --- [ main] o.a.tutorial.events.EventsApplication : Starting EventsApplication v0.0.1-SNAPSHOT using Java 11.0.2 on MBP512-MBERGLJUNG-0917 with PID 2599 (/Users/mbergljung/IDEAProjects/docs-new/sdk5/sdk5-spring-integration-events-sample/target/events-0.0.1-SNAPSHOT.jar started by mbergljung in /Users/mbergljung/IDEAProjects/docs-new/sdk5/sdk5-spring-integration-events-sample)
2021-03-29 13:44:49.446 INFO 2599 --- [ main] o.a.tutorial.events.EventsApplication : No active profile set, falling back to default profiles: default
2021-03-29 13:44:50.606 INFO 2599 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2021-03-29 13:44:50.613 INFO 2599 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
2021-03-29 13:44:50.621 INFO 2599 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2021-03-29 13:44:50.775 INFO 2599 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.integration.config.IntegrationManagementConfiguration' of type [org.springframework.integration.config.IntegrationManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-29 13:44:50.802 INFO 2599 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationChannelResolver' of type [org.springframework.integration.support.channel.BeanFactoryChannelResolver] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-29 13:44:50.803 INFO 2599 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'integrationDisposableAutoCreatedBeans' of type [org.springframework.integration.config.annotation.Disposables] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-03-29 13:44:51.924 INFO 2599 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2021-03-29 13:44:52.014 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2021-03-29 13:44:52.015 INFO 2599 --- [ main] o.s.i.channel.PublishSubscribeChannel : Channel 'application.errorChannel' has 1 subscriber(s).
2021-03-29 13:44:52.015 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean '_org.springframework.integration.errorLogger'
2021-03-29 13:44:52.015 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {transformer} as a subscriber to the 'acsEventsListeningFlow.channel#0' channel
2021-03-29 13:44:52.015 INFO 2599 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.acsEventsListeningFlow.channel#0' has 1 subscriber(s).
2021-03-29 13:44:52.015 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'acsEventsListeningFlow.org.springframework.integration.config.ConsumerEndpointFactoryBean#0'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsListeningFlow'
2021-03-29 13:44:52.015 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {recipient-list-router} as a subscriber to the 'acsEventsListeningFlow.channel#1' channel
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.acsEventsListeningFlow.channel#1' has 1 subscriber(s).
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'acsEventsListeningFlow.org.springframework.integration.config.ConsumerEndpointFactoryBean#1'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsListeningFlow'
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {bridge} as a subscriber to the 'alfresco.events.si.channel' channel
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.alfresco.events.si.channel' has 1 subscriber(s).
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'acsEventsSpringIntegrationFlow.org.springframework.integration.config.ConsumerEndpointFactoryBean#0'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsSpringIntegrationFlow'
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {bridge} as a subscriber to the 'acsEventsSpringIntegrationFlow.channel#1' channel
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.acsEventsSpringIntegrationFlow.channel#1' has 1 subscriber(s).
2021-03-29 13:44:52.016 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'acsEventsSpringIntegrationFlow.org.springframework.integration.config.ConsumerEndpointFactoryBean#1'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsSpringIntegrationFlow'
2021-03-29 13:44:52.017 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {bridge} as a subscriber to the 'alfresco.events.handlers.channel' channel
2021-03-29 13:44:52.017 INFO 2599 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.alfresco.events.handlers.channel' has 1 subscriber(s).
2021-03-29 13:44:52.017 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'acsEventsHandlersFlow.org.springframework.integration.config.ConsumerEndpointFactoryBean#0'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsHandlersFlow'
2021-03-29 13:44:52.017 INFO 2599 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.acsEventsHandlersFlow.channel#1' has 1 subscriber(s).
2021-03-29 13:44:52.018 INFO 2599 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started bean 'acsEventsHandlersFlow.org.springframework.integration.config.ConsumerEndpointFactoryBean#1'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsHandlersFlow'
2021-03-29 13:44:52.018 INFO 2599 --- [ main] ishingJmsMessageListener$GatewayDelegate : started org.springframework.integration.jms.ChannelPublishingJmsMessageListener$GatewayDelegate@d8305c2
2021-03-29 13:44:52.539 INFO 2599 --- [ main] o.s.i.jms.JmsMessageDrivenEndpoint : started bean 'acsEventsListeningFlow.jms:message-driven-channel-adapter#0'; defined in: 'class path resource [org/alfresco/event/sdk/autoconfigure/AlfrescoEventsAutoConfiguration.class]'; from source: 'bean method acsEventsListeningFlow'
2021-03-29 13:44:52.561 INFO 2599 --- [ main] o.a.tutorial.events.EventsApplication : Started EventsApplication in 3.849 seconds (JVM running for 4.638)
Looks ready for some event handler code.
Now, start adding your event handler code, let’s add an event handler that will be triggered when a new document/file is uploaded. To do this we need to create a class that implements the org.springframework.integration.dsl.IntegrationFlow interface, we can use a helper adapter class (i.e. IntegrationFlowAdapter) for this:
package org.alfresco.tutorial.events;
import org.alfresco.event.sdk.handling.filter.EventTypeFilter;
import org.alfresco.event.sdk.integration.EventChannels;
import org.alfresco.event.sdk.integration.filter.IntegrationEventFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.dsl.IntegrationFlowAdapter;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.stereotype.Component;
/**
* Spring Integration based event handler that will execute code when a file is uploaded
*/
@Component
public class NewContentFlow extends IntegrationFlowAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(NewContentFlow.class);
// Use builder to create an integration flow based on alfresco.events.main.channel event channel
@Override
protected IntegrationFlowDefinition<?> buildFlow() {
return from(EventChannels.MAIN) // Listen to events coming from the Alfresco events channel
.filter(IntegrationEventFilter.of(EventTypeFilter.NODE_CREATED)) // Filter events and select only node created events
.handle(t -> LOGGER.info("File uploaded: {}", t.getPayload().toString())); // Handle event with a bit of logging
}
}
Add the Spring Bean class into the same directory as the Spring Boot starter class. It doesn’t have to be added to this directory, but in this case we are just testing it, so no need to organize too much.
Now stop, build and start it up again:
$ ^C ... $ mvn spring-boot:run -Dlicense.skip=true ...
Add a file via the Share user interface, you should see the following in the logs:
2021-03-30 10:09:22.738 INFO 9603 --- [erContainer#0-1] o.a.tutorial.events.NewContentFlow : File uploaded: RepoEvent [specversion=1.0, type=org.alfresco.event.node.Created, id=12100b22-8dae-4ebe-b114-ba9dc2f9755b, source=/3bc24dba-d1ae-4c04-af60-0294a4c68a7f, time=2021-03-30T09:09:22.711117Z, dataschema=https://api.alfresco.com/schema/event/repo/v1/nodeCreated, datacontenttype=application/json, data=EventData [eventGroupId=3196f0f6-b4aa-4834-9aa2-a58eaa8f121f, resource=NodeResource [id=4e1f0830-2452-4a4c-b20a-7146402ce665, name=somefile-again.txt, nodeType=cm:content, isFile=true, isFolder=false, createdByUser=UserInfo [id=admin, displayName=Administrator], createdAt=2021-03-30T09:09:22.324Z, modifiedByUser=UserInfo [id=admin, displayName=Administrator], modifiedAt=2021-03-30T09:09:22.324Z, content=ContentInfo [mimeType=text/plain, sizeInBytes=0, encoding=UTF-8], properties={cm:title=, app:editInline=true, cm:description=}, aspectNames=[app:inlineeditable, cm:titled, cm:auditable], primaryHierarchy=[19e067a9-5d2a-43ba-ac93-d273d938050c, 30afd06d-6ec7-4434-b1d6-1f7671b6b9a7, 7a82ddff-0869-430e-8cc8-623d97b98dc4]], resourceBefore=null]]
Now, this event handler will actually also be triggered when a folder is created. So how can we fix so the handler is only triggered when a file is created/uploaded? By adding a so called event filter to the class (see ):
package org.alfresco.tutorial.events;
import org.alfresco.event.sdk.handling.filter.EventTypeFilter;
import org.alfresco.event.sdk.handling.filter.IsFileFilter;
import org.alfresco.event.sdk.integration.EventChannels;
import org.alfresco.event.sdk.integration.filter.IntegrationEventFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.dsl.IntegrationFlowAdapter;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.stereotype.Component;
/**
* Spring Integration based event handler that will execute code when a file is uploaded
*/
@Component
public class NewContentFlow extends IntegrationFlowAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(NewContentFlow.class);
// Use builder to create an integration flow based on alfresco.events.main.channel event channel
@Override
protected IntegrationFlowDefinition<?> buildFlow() {
return from(EventChannels.MAIN) // Listen to events coming from the Alfresco events channel
.filter(IntegrationEventFilter.of(EventTypeFilter.NODE_CREATED)) // Filter events and select only node created events
.filter(IntegrationEventFilter.of(IsFileFilter.get())) // Filter node and make sure it is a file node
.handle(t -> LOGGER.info("File uploaded: {}", t.getPayload().toString())); // Handle event with a bit of logging
}
}
Here we are using the org.alfresco.event.sdk.handling.filter.IsFileFilter, which will make sure that the event handler is triggered only when the node type is cm:content or subtype thereof, which represents files. To use this filter with Spring Integration we use the IntegrationEventFilter wrapper.
If you are thinking, do I really need a whole class just to process an event? No you don’t, you can include a bean definition directly in the Spring Boot app class as follows:
package org.alfresco.tutorial.events;
import org.alfresco.event.sdk.handling.filter.EventTypeFilter;
import org.alfresco.event.sdk.handling.filter.IsFileFilter;
import org.alfresco.event.sdk.integration.EventChannels;
import org.alfresco.event.sdk.integration.filter.IntegrationEventFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
@SpringBootApplication
public class EventsApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(EventsApplication.class);
public static void main(String[] args) {
SpringApplication.run(EventsApplication.class, args);
}
@Bean
public IntegrationFlow logCreateFileNode() {
return IntegrationFlows.from(EventChannels.MAIN) // Listen to events coming from the Alfresco events channel
.filter(IntegrationEventFilter.of(EventTypeFilter.NODE_CREATED)) // Filter events and select only node created events
.filter(IntegrationEventFilter.of(IsFileFilter.get())) // Filter node and make sure it is a file node
.handle(t -> LOGGER.info("File uploaded: {}", t.getPayload().toString())) // Handle event with a bit of logging
.get();
}
}
It also makes sense to add error management code to the application class as follows:
@SpringBootApplication
public class EventsApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(EventsApplication.class);
public static void main(String[] args) {
SpringApplication.run(EventsApplication.class, args);
}
@Bean
public IntegrationFlow logError() {
return IntegrationFlows.from(EventChannels.ERROR).handle(t -> {
LOGGER.info("Error: {}", t.getPayload().toString());
MessageHandlingException exception = (MessageHandlingException) t.getPayload();
exception.printStackTrace();
}).get();
}
...
}
For a complete list of events with sample code, see the Events Extension Points section in the Alfresco Content Services documentation.. For a complete list of Event Filters available in the SDK see Event handling library.
For information on how to implement a custom event filter see Implementing custom event filters.
For more information about how to extract all the properties from the message payload see The NodeResource object.