With the release of version 7 of Alfresco an additional feature, called Out of Process Events, has been added to the Alfresco architecture. For specific events, such as document create, an event is created on a message queue which an external event handler can then process.
Event processing is traditionaly supported by Alfresco using the Policy/Behaviour framework. Events such as onCreate or onAddAspect are examples of policies which may have behaviours attached. This allows for an Alfresco solution to be customised to react to events that occur to content as it is added, updated, moved etc. A common example is to automatically add custom metadata to a document when it is added to a specific folder. Behaviours run in the same container as the running Alfresco instance and can therefore be termed In-Process event handlers. We have used Policy/Behaviours in most of our customer solutions. Its a powerful and effective tool for a whole range of things such as managing business based content model data, applying security to content based on the current business rules or auto filing content.
So why the need for an Out of Process Event framework. One requirement it supports is for integrating with other applications. Content management systems are generally implemented as part of an enterprise ecosystem. If you wanted to integrate with for example, an Insurance processing application, you could write a specific Out of Process spring application that reads the events generated by Alfresco and updates the Insurance app with the required data. This follows the micro-services architecture that Alfresco has been following of late.
Another positive for the Out of Process Events system is that it allows for greater fault tollerance and scalability. The Alfresco content server has been enhanced with a new Alfresco Event system that raises events and writes them to an external Message Queue (Apache ActiveMQ by default). The external event handlers then read from the queue. Queues ensure data integrity between restarts and thefore are a more robust means of ensuring that all events raised through the Alfresco content server are available to be managed. The external event handlers, implemented as a spring boot app, run independently of the Alfresco content servers and therefore may not use the infrastructure resources of the Alfresco content server.
The last reason we think that Alfresco has created the Out of Process Event Handlers is for supportability of on premise and the Alfresco public cloud instances. We know from talking with Alfresco support that supporting solutions that have badly implemented behaviours is very challenging for them. Currently, Alfresco have said that they will continue to support Policies and Behaviours, however, it seems logical to me that at some point they will change this and only support Out of Process Event handlers.
SDKs
Alfresco now supports two SDKs, the SDK 4.2 which supports development of Policy/Behaviour event processing and the SDK 5 which is solely for building Out of Process Events solutions.
In-Process SDK (SDK 4.2)
In-Process SDK (SDK 4.2) mainly consists of custom code that is run together with the product code in the same process and the deployable code is normally in the form of jars or amps. The list below shows few of the supported extension points by the In-Process SDK.
- Content Model
- Behaviour Policies
- Actions
- Webscripts
- Share Extensions
- Scheduled Jobs
- Custom Roles (Permissions)
- Bootstrap Content
- Custom Mimetypes
- Audit Log
- Metadata Extractors
For more details and complete list please see below link;
https://docs.alfresco.com/content-services/latest/develop/repo-ext-points/
Out-of-Process SDK (SDK 5.0)
Out-of-Process SDK (SDK 5.0) consists custom code running in a separate process (outside of ACS) and the deployable code is normally in form of a spring boot app. The SDK includes the Alfresco Java Events API which allows to work with the new Alfresco Event system with Java. The SDK also comes with a Java REST API wrapper library. This allows working with the Alfresco REST API from a Java client with standard Java classes, without the need to parse JSON or create HTTP requests.
Creating an Out-of-Process extension project for both event handling and Java REST API
Pre-Requisites
- ACS 7.0 or above running
- Java version 11 or above installed
- Maven version 3.3 or above installed
Create Spring Boot project
The easiest way to get started is to use the Spring Initializr website and create a starting point project from there. Go to https://start.spring.io/ and fill in Project Metadata section as shown below.
Click GENERATE to generate and download your default Spring Boot project.
Make the following changes in the Spring Boot project you just downloaded;
Add the following to the pom.xml of the project so that Maven knows about the Alfresco Artifacts Repository (Nexus).
<repositories> <repository> <id>alfresco-public</id> <url>https://artifacts.alfresco.com/nexus/content/groups/public</url> </repository> </repositories>
Change the parent of the Maven project (i.e. in pom.xml) so it uses the Alfresco Java SDK (i.e. SDK 5).
<parent> <groupId>org.alfresco</groupId> <artifactId>alfresco-java-sdk</artifactId> <version>5.0.0</version> </parent>
Delete the Spring Boot test dependency in the POM file,
<!--<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>-->
Also delete the test java file which is au/com/seedim/demo/DemoApplicationTests.java.
Remove the default Spring Boot starter dependency
<!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> -->
Add the following dependency in the pom.xml
<dependency> <groupId>org.alfresco</groupId> <artifactId>alfresco-java-event-api-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency>
By adding the alfresco-java-event-api-spring-boot-starter, all the following auto-configurations are enabled;
- Define a Spring Integration flow to read the event messages from the ActiveMQ topic using a JMS channel adapter.
- Transform the message payload from JSON to a RepoEvent object.
- Route the corresponding event messages to up to 2 other channels:
- A channel to use pure Spring Integration handling if the property alfresco.events.enableSpringIntegration is enabled (Spring Integration handling not covered in this blog).
- A channel to use event handling (from the event handling library) if the property alfresco.events.enableHandlers is enabled.
In order to be able to use the Alfresco JAVA REST API, add the following dependency
<dependency> <groupId>org.alfresco</groupId> <artifactId>alfresco-java-rest-api-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency>
Add the following two dependencies which will use later in this blog
<!-- Required for deserializing dates --> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> <!-- required for LinkedTreeMap --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency>
Configure the Spring Boot App
Add the following to application.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=false
-
# Turn off plain Java event handlers
alfresco.events.enableHandlers=true
-
# HTTP Basic Authentication that will be used by the API
content.service.security.basicAuth.username=admin content.service.security.basicAuth.password=admin
-
# Location of the server and API endpoints
content.service.url=http://localhost:8080 content.service.path=/alfresco/api/-default-/public/alfresco/versions/1 search.service.path=/alfresco/api/-default-/public/search/versions/1
At this point we have configured our springboot app and required dependencies added. So we should be able to build and start the app using the following command
mvn spring-boot:run -Dlicense.skip=true
Alternatively you can also use the command to package/build and then start the app
mvn clean package -Dlicense.skip=true
java -jar target/demo-0.0.1-SNAPSHOT.jar
If the app has been set up correctly, you should see something like below;
Now we are ready to start implementing our event handler. We will create a java event handler and custom filter to set the Title and Description every time a document is created in a specific folder location.
Custom Event Filter
An event filter allows to easily define conditions that an event must match for the code to be executed. Alfresco offers a number of re-defined filters out-of-the-box which covers the most common use cases such as checking for an aspect, file, folder, property added/changed and so on.
Alfresco also allows creating your own custom event filter. We are going to implement an event filter which checks if a passed in node ID is equal to a desired parent folder node ID. This event filter can be used to check if a file or folder is located in a specific folder.
Create the class called ParentFolderFilter in au.com.seedim.demo.event.filter package as shown below;
/** * Filter that can be used when a node needs to be in a specific folder. */ public class ParentFolderFilter extends AbstractEventFilter { // The node ID for the folder we want to check against private final String parentId; // Private constructor, make sure ID is not null private ParentFolderFilter(final String parentId) { this.parentId = Objects.requireNonNull(parentId); } // When using the filter, pass in the folder node ID we want to check against public static ParentFolderFilter of(final String parentId) { return new ParentFolderFilter(parentId); } // The actual test: // get the node resource we are testing (such as a file node), // then get its primary parent folder ID and check if it matches desired folder Node ID public boolean test(RepoEvent<DataAttributes<Resource>> event) { NodeResource resource = (NodeResource) event.getData().getResource(); boolean parentFound = resource.getPrimaryHierarchy().get(0).equals(parentId); return isNodeEvent(event) && parentFound; } }
The class extends the org.alfresco.event.sdk.handling.filter.AbstractEventFilter class and implement the test method which contains the logic we want to carry out which in this case is getting the primary parent of the node from the Resource Event and comparing it to the passed in parent node id. Now let’s implement our Event handler and use our custom Event filter.
Event Handler
We need to create an event handler that will be triggered when a new document/file is uploaded to a specific folder location and then add a title and description to the uploaded document.
The list below shows the currently supported event handler interfaces;
- Node created – OnNodeCreatedEventHandler.
- Node updated – OnNodeUpdatedEventHandler.
- Node deleted – OnNodeDeletedEventHandler.
- Parent-Child Association created – OnChildAssocCreatedEventHandler.
- Parent-Child Association deleted – OnChildAssocDeletedEventHandler.
- Peer-Peer Association Created – OnPeerAssocCreatedEventHandler.
- Peer-Peer Association Deleted – OnPeerAssocDeletedEventHandler.
- Permission updated – OnPermissionUpdatedEventHandler.
Create the following java class in the au.com.seedim.demo.event.handler package.
/** * Sample event handler to demonstrate reacting to a document/file being uploaded to the repository. */ @Component public class ContentUploadedEventHandler implements OnNodeCreatedEventHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ContentUploadedEventHandler.class); @Autowired NodesApi nodesApi; private String folderID = "4fda83a8-65a0-477d-bbff-ab3fac164107"; public void handleEvent(final RepoEvent<DataAttributes<Resource>> repoEvent) { NodeResource nodeResource = (NodeResource) repoEvent.getData().getResource(); LOGGER.info("Handler - A file was uploaded to the repository: {}, {}, {}", nodeResource.getId(), nodeResource.getNodeType(), nodeResource.getName()); LinkedTreeMap<String, Object> properties = new LinkedTreeMap<>(); properties.put("cm:title", "ALFRESCO"); properties.put("cm:description", "The Description"); NodeBodyUpdate nodeBodyUpdate = new NodeBodyUpdate(); LOGGER.info("Handler - Setting title and description"); nodesApi.updateNode(nodeResource.getId(), nodeBodyUpdate.properties(properties), null, null); } public EventFilter getEventFilter() { LOGGER.info("Event Filter method entered"); return IsFileFilter.get() .and(ParentFolderFilter.of(folderID)); } }
- The JAVA class implements the org.alfresco.event.sdk.handling.handler. OnNodeCreatedEventHandler interface.
- @Component is being used to detect the custom bean, instantiate it and inject any specified dependencies into it and Inject it wherever needed
- @Autowired is being used to inject the NodesApi bean when the ContentUploadedEventHandler is created.
- Within the handleEvent method, we are using the RepoEvent data as a NodeResource object to get the document ID and also using the NodesApi from the JAVA REST API to update the document Title and Description property.
- Within the getEventFilter method, we are checking that the node is a document using the out-of-the-box IsFileFilter event filter and also using our custom event filter (ParentFolderFilter) to validate whether the document node primary parent is the same as the id we passing (this has been hardcoded as part of this demo).
Testing
- Stop our springboot app using CTRL+C
- Build and start using mvn spring-boot:run -Dlicense.skip=true
- Log in Alfresco Share and upload a document to the folder you have hardcoded in the handler. You should see the Title and Description has been set for the uploaded document.
Conclusion
The Out of Process Events system released with Alfresco 7 is a powerful new entension point for integrating with Alfresco with external applications. It also provides a mechanism for the processing of content that is in your repository in a microservice that is both faulth tollerant and can be scaled seperately from your Alfresco instances. Alfresco recommends using the event system and the new SDK 5 to implement a business logic if the latter can be lifted out and implemented as an external service. At Seed as we bring on new customers and upgrade our existing customers to Alfresco 7 we will consider both options for event handling and determine the best fit. In conclusion, we think the Out of Process Events extension is a great new feature that will definitely add to the Alfresco solution.