Alfresco is an enterprise content management platform known for its flexibility and extensibility. One powerful way to extend its functionality is through Behaviors, which allow you to run custom logic whenever specific repository events occur. For example, you can trigger custom actions whenever nodes are created, updated, or deleted.
In this article, we’ll walk through creating a custom Alfresco Behavior – AutoMoveFolderBehavior – that automatically moves newly created documents into dynamically determined folders. This tutorial is designed for developers new to Alfresco who want to understand how behaviors work and how to leverage them for custom repository actions.
Alfresco Behaviors
Alfresco Behaviors are essentially event-driven programming hooks that allow developers to trigger custom code when certain actions occur on a node. These actions can include node creation, deletion, modification, or even custom lifecycle events.
In this guide, you will learn:
- How to define and register a custom Alfresco Behavior.
- How to inject and use Alfresco services through Spring.
- How to dynamically resolve folder paths based on node metadata.
- How to move new documents into organized, metadata-driven folder structures.
Prerequisites:
- Basic knowledge of Java.
- Familiarity with Alfresco’s architecture and content models.
- Understanding of the Spring Framework and dependency injection.
Key Concepts of the AutoMoveFolderBehavior Example
Scenario: You have a custom content type (e.g., CustomSuperDocument) and want any newly created nodes of this type to be automatically placed in a folder structure determined by their metadata. The AutoMoveFolderBehavior listens for node creation events and moves these new nodes into folders constructed based on a template path.
Main Steps:
- Registering the Behavior: Bind the behavior to OnCreateNode events for specific node types.
- Resolving Dynamic Paths: Use a template that references node properties (e.g., date-based folders, metadata properties) to determine where the node should be filed.
- Moving the Node: Once the target folder is resolved, the behavior moves the newly created node into the correct location.
The Project Structure and Code Walkthrough
Project Structure (Example):
project-root/
├─ src/
│ ├─ main/
│ │ ├─ java/
│ │ │ └─ com/
│ │ │ └─ hyland/
│ │ │ ├─ behavior/
│ │ │ │ └─ AutoMoveFolderBehavior.java // Core behavior logic
│ │ │ ├─ model/
│ │ │ │ └─ CustomSuperDocument.java // Custom model definition (TYPE constant)
│ │ ├─ resources/
│ │ │ ├─ alfresco/
│ │ │ │ ├─ module/
│ │ │ │ │ └─ hyland-behavior/
│ │ │ │ │ ├─ context/
│ │ │ │ │ │ └─ bootstrap-context.xml // Spring configuration
│ │ │ │ │ └─ model/
│ │ │ │ │ └─ custom-super-document-model.xml // Custom type definition
Note on Custom Types: CustomSuperDocument.TYPE refers to a custom content type defined in custom-super-document-model.xml. Ensure your custom model is deployed and registered with Alfresco.
1. Dependencies and Initialization
The AutoMoveFolderBehavior class imports various Alfresco services:
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
These imports are used to register and execute the behavior whenever a node is created.
Here are the main fields and dependency setters:
private NodeService nodeService;
private FileFolderService fileFolderService;
private Repository repositoryHelper;
private NamespaceService namespaceService;
private String filingPathTemplate;
private PolicyComponent policyComponent;
The dependencies are injected using Spring, which helps connect this class with Alfresco services like NodeService, FileFolderService, etc.
Init Method: The init() method is called by Spring after dependency injection is complete.
public void init() {
logger.info("INIT AutoMoveFolderBehavior");
// Binding the behavior
policyComponent.bindClassBehaviour(
NodeServicePolicies.OnCreateNodePolicy.QNAME,
ContentModel.TYPE_CONTENT,
new JavaBehaviour(this, "onCreateNode"));
policyComponent.bindClassBehaviour(
NodeServicePolicies.OnCreateNodePolicy.QNAME,
CustomSuperDocument.TYPE,
new JavaBehaviour(this, "onCreateNode"));
}
The init() method binds our behavior to a specific Alfresco policy. In this case, it listens for nodes of type ContentModel.TYPE_CONTENT and CustomSuperDocument.TYPE.
2. The Behavior Logic: onCreateNode()
The main logic for the behavior resides in the onCreateNode() method. This method is called every time a node that matches the registered types is created.
@Override
public void onCreateNode(ChildAssociationRef childAssocRef) {
NodeRef createdNodeRef = childAssocRef.getChildRef();
logger.info("New node created: {}", createdNodeRef);
// Check if the created node is of type CustomSuperDocument
QName nodeType = nodeService.getType(createdNodeRef);
if (nodeType.equals(CustomSuperDocument.TYPE)) {
try {
logger.info("Got inside: {}", createdNodeRef);
// Resolve the target folder dynamically
NodeRef targetFolderRef = resolveTargetFolder(createdNodeRef);
if (targetFolderRef == null) {
logger.error("Failed to resolve target folder for node: {}", createdNodeRef);
return;
}
// Move the node to the resolved folder
moveNode(createdNodeRef, targetFolderRef);
} catch (Exception e) {
logger.error("Error moving node: {}", e.getMessage(), e);
}
}
}
What this does:
- Checks if the newly created node is our custom type.
- Dynamically determines the destination folder.
- Moves the node into that folder.
3. Handling Templates and Dynamic Folder Paths
The dynamic folder path resolution is an interesting part of this example. The path is built based on a template, which can include metadata or date placeholders.
private NodeRef resolveTargetFolder(NodeRef nodeRef) {
try {
List<String> pathElements = convertTemplateToPathElements(nodeRef);
NodeRef companyHome = repositoryHelper.getCompanyHome();
NodeRef currentFolder = companyHome;
// Create folders if they don't exist
for (String pathElement : pathElements) {
NodeRef child = fileFolderService.searchSimple(currentFolder, pathElement);
if (child == null) {
child = fileFolderService.create(currentFolder, pathElement, ContentModel.TYPE_FOLDER).getNodeRef();
logger.info("Created folder: {}", child);
}
currentFolder = child;
}
return currentFolder;
} catch (Exception e) {
logger.error("Error resolving target folder: {}", e.getMessage(), e);
return null;
}
}
Template Example: The template can be configured in the alfresco-global.properties file:
hyland.filing.path.template=/sites/hyland/SuperStore/${hyland_random_prop_a}/${hyland_random_prop_b}/${hyland_document_created_at#yyyy}/${hyland_document_created_at#MM}
The method convertTemplateToPathElements() breaks down this template into components and translates them into dynamic folder names based on the properties of the created node.
4. Moving the Node
Once the target folder is resolved, the moveNode() method takes care of moving the newly created node:
private void moveNode(NodeRef nodeRef, NodeRef targetFolderRef) {
try {
String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String uniqueName = getUniqueName(targetFolderRef, nodeName);
fileFolderService.move(nodeRef, targetFolderRef, uniqueName);
logger.info("Node '{}' moved to '{}'.", nodeName, targetFolderRef);
} catch (Exception e) {
logger.error("Failed to move node: {}", e.getMessage(), e);
}
}
The getUniqueName() method ensures that if a folder with the same name already exists, a unique name is generated to prevent conflicts.
5. Spring Configuration (bootstrap-context.xml)
Spring configuration is used to define the AutoMoveFolderBehavior bean:
<bean id="autoMoveFolderBehavior" class="com.hyland.behavior.AutoMoveFolderBehavior" init-method="init">
<property name="policyComponent" ref="policyComponent" />
<property name="nodeService" ref="NodeService" />
<property name="fileFolderService" ref="FileFolderService" />
<property name="repositoryHelper" ref="repositoryHelper" />
<property name="namespaceService" ref="NamespaceService" />
<property name="filingPathTemplate" value="${hyland.filing.path.template}" />
</bean>
This bean configuration injects all the dependencies that the behavior requires, such as the NodeService, FileFolderService, and the template string (filingPathTemplate).
What We Have Learned
- Behaviors in Alfresco: We saw how to create a custom behavior that listens for OnCreateNode events and runs custom logic in response.
- Spring Dependency Injection: By using Spring for DI, we easily accessed Alfresco services like NodeService and FileFolderService without manually managing instances.
- Dynamic Folder Path Creation: Using metadata, dates, and property placeholders, we resolved a folder path to organize newly created documents automatically.
This example is a starting point. It is not production-ready, but it demonstrates the core concepts. In a real environment, you’d add more robust error handling, logging, and potentially support more node types or more complex filing rules.
Next Steps
- Extend the Behavior: Add logic to handle additional content types or dynamically choose target paths based on more metadata properties.
- Refine Error Handling: Implement more granular logging and error checks for a production setting.
- Combine Approaches: Explore other Alfresco customization methods—such as actions, rules, or workflows—to complement behaviors for a fully automated content lifecycle.
Alternative Approaches for Automating File Organization
While the behavior-based solution outlined here is a great starting point, there are other ways to achieve similar results in Alfresco. Here are some alternatives:
- Alfresco Rules: Configure rules through the Alfresco Share UI to move content without custom code, at the cost of flexibility.
- Custom Actions: Create actions that can be triggered by workflows or users, offering a balance between automation and manual control.
- Workflows: Use Activiti (or Flowable in newer versions) workflows for complex, multi-step processes that include file organization.
- REST API Integration: Integrate external applications via REST APIs to run organizational logic externally.
- Repository JavaScript Scripts: Implement simpler scripts in JavaScript for quicker development cycles.
Conclusion
This article provided a basic introduction to creating an Alfresco behavior for automating file organization. While the example is designed for beginners, it is intended strictly as a demonstration and not a production-ready solution. It lays a solid foundation for understanding the Alfresco SDK and showcases the basic principles of behavior creation.
As you gain more experience, you can explore alternative methods, combine multiple approaches, and develop more robust automations tailored to your specific needs and production environments.
With Alfresco, the possibilities are vast, and this demo is just the beginning. Experiment, learn, and unlock the full potential of content management for your organization.
Happy coding!