Introduction
In today’s digital landscape, controlling and validating the types of content users can upload into systems is essential for security and data integrity. Alfresco, a leading open-source content services platform, offers a flexible public API that enables developers to create custom extensions and adapt the platform to specific organizational needs. This article provides a step-by-step guide to implementing a MIME type restriction feature in Alfresco’s Nodes API, allowing for more controlled and secure content uploads.
By setting up a project structure, defining a custom interface, and implementing content validation, you can quickly create a basic extension to manage acceptable file types in Alfresco. This example serves as a foundational approach for developers looking to get up to speed on Alfresco API extensions and implement practical restrictions on content uploads.
Overview
This documentation provides steps for extending the Alfresco Nodes public API to restrict uploads to an authorized MIME type list. The public API is a standardized REST API implemented in the „remote-api“ JAR. The JAR source code and API documentation are available at:
- API Documentation: Swagger Documentation
- remote-api Source Code: GitHub Repository
Structure of the Public API
The public API consists of multiple Java packages, with each subpackage (e.g., Nodes, People, Sites) representing an API. Each package contains:
- package-info.java: Defines the API scope, name, and version.
- Java Classes:Entity Resource Classes: Methods to manipulate objects (e.g., nodes, people). Relation Classes: Methods to manage relationships between entities.
Example URL for the Nodes API:
http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{nodeId}
Implementation Steps for Extension
1. Create Project Structure
Create a platform JAR extension project and define a package for customizations (e.g., rest/api/).
You can find the SDK here: https://github.com/Alfresco/alfresco-sdk
2. Custom Nodes Interface
Define the CustomNodes interface in the rest/api package, which will declare methods to check and control MIME types for uploaded content.
package com.hyland.customextensions.rest.api;
/**
* Custom interface for handling MIME type restrictions.
*/
public interface CustomNodes {
boolean isMimeTypeAllowed(String mimeType);
}
3. Implement the Custom Nodes Handler
Create the CustomNodesImpl class in rest/api/impl to implement the CustomNodes interface and check the MIME type of uploaded content:
package com.hyland.customextensions.rest.api.impl;
import com.hyland.customextensions.rest.api.CustomNodes;
import java.util.Arrays;
import java.util.List;
public class CustomNodesImpl implements CustomNodes {
private static final List<String> ALLOWED_MIME_TYPES = Arrays.asList("application/pdf", "image/jpeg");
@Override
public boolean isMimeTypeAllowed(String mimeType) {
return ALLOWED_MIME_TYPES.contains(mimeType);
}
}
4. Modifications for Entity Resources and Relations
NodesEntityResource
Create the NodesEntityResource class in rest/api/nodes to override content upload. The implementation uses updateProperty to verify the MIME type:
package com.hyland.customextensions.rest.api.nodes;
import com.hyland.customextensions.rest.api.CustomNodes;
import org.springframework.beans.factory.InitializingBean;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import java.io.InputStream;
/**
* Custom Entity Resource to enforce MIME type restrictions on content updates.
*/
@EntityResource(name = "nodes", title = "Nodes")
public class NodesEntityResource implements BinaryResourceAction.Update<Node>, InitializingBean {
private CustomNodes customNodes;
private Nodes nodes; // Reference to the original NodesEntityResource
// Setter for CustomNodes dependency
public void setCustomNodes(CustomNodes customNodes) {
this.customNodes = customNodes;
}
// Setter for NodesEntityResource dependency
public void setNodes(Nodes nodes) {
this.nodes = nodes;
}
@Override
public void afterPropertiesSet() throws Exception {
// Do nothing
}
@Override
@WebApiDescription(title = "Upload content", description = "Upload content")
@BinaryProperties({"content"})
public Node updateProperty(String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params) {
String mimeType = contentInfo.getMimeType();
if (!customNodes.isMimeTypeAllowed(mimeType)) {
throw new UnsupportedOperationException("MIME type " + mimeType + " is not allowed.");
}
return nodes.updateContent(entityId, contentInfo, stream, params);
}
}
NodeChildrenRelation
Create the NodeChildrenRelation class to override node creation with content. The implementation uses the create method to also verify the MIME type:
package com.hyland.customextensions.rest.api.nodes;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.WebApiParam;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.webscripts.WithResponse;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.webscripts.servlet.FormData;
import com.hyland.customextensions.rest.api.CustomNodes;
import org.apache.tika.Tika;
import java.io.InputStream;
/**
* Custom Relation Resource to enforce MIME type restrictions on node creation with content.
*/
@RelationshipResource(name = "children", entityResource = NodesEntityResource.class, title = "Folder children")
public class NodeChildrenRelation implements MultiPartRelationshipResourceAction.Create<Node>, InitializingBean {
private CustomNodes customNodes;
private Nodes nodes; // Reference to the original NodesEntityResource
// Setter for CustomNodes dependency
public void setCustomNodes(CustomNodes customNodes) {
this.customNodes = customNodes;
}
// Setter for NodesEntityResource dependency
public void setNodes(Nodes nodes) {
this.nodes = nodes;
}
@Override
public void afterPropertiesSet() throws Exception {
// Do Nothing
}
@Override
@WebApiDescription(title = "Upload file content and meta-data into the repository.")
@WebApiParam(name = "formData", title = "A single form data", description = "A single form data which holds FormFields.")
public Node create(String parentFolderNodeId, FormData formData, Parameters parameters, WithResponse withResponse) {
Tika tika = new Tika(); // Tika for MIME type detection
String mimeType = "";
// Iterate over form fields to find the uploaded file
for (FormData.FormField field : formData.getFields()) {
if (field.getIsFile()) {
try (InputStream stream = field.getInputStream()) {
mimeType = tika.detect(stream); // Detect MIME type
// Check MIME type
if (!customNodes.isMimeTypeAllowed(mimeType)) {
throw new UnsupportedOperationException("MIME type " + mimeType + " is not allowed.");
}
}
catch (UnsupportedOperationException e) {
throw e;
}
catch (Exception e) {
throw new UnsupportedOperationException("Error processing uploaded file", e);
}
}
}
return nodes.upload(parentFolderNodeId, formData, parameters);
}
}
5. Configuration in the Spring Context
Define the necessary Spring beans in bootstrap-context.xml to register and configure the custom entity resources and relations:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Register Custom Nodes Handler -->
<bean id="customNodes" class="com.hyland.customextensions.rest.api.impl.CustomNodesImpl">
</bean>
<bean id="CustomNodes" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.hyland.customextensions.rest.api.CustomNodes</value>
</property>
<property name="target">
<ref bean="customNodes" />
</property>
<property name="interceptorNames">
<list>
<value>legacyExceptionInterceptor</value>
</list>
</property>
</bean>
<!-- Custom Entity Resource -->
<bean class="com.hyland.customextensions.rest.api.nodes.NodesEntityResource">
<property name="nodes" ref="Nodes" />
<property name="customNodes" ref="CustomNodes" />
</bean>
<!-- Custom Relation -->
<bean class="com.hyland.customextensions.rest.api.nodes.NodeChildrenRelation">
<property name="nodes" ref="Nodes" />
<property name="customNodes" ref="CustomNodes" />
</bean>
</beans>
6. package-info.java
Define the metadata for the custom API in the package-info.java file:
@WebApi(name="customnodesapi", scope=Api.SCOPE.PUBLIC, version=1) package com.hyland.customextensions.rest.api.nodes; import org.alfresco.rest.framework.Api; import org.alfresco.rest.framework.WebApi;
This file defines the custom URL endpoint, which in this case will be:
http://localhost:8080/alfresco/api/-default-/public/customnodesapi/versions/1/nodes/{nodeId}/content
Final Folder Structure and summary
The following folder structure represents the organization of files for extending the Nodes public API.
project-root/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── hyland/
│ │ │ └── customextensions/
│ │ │ └── rest/
│ │ │ └── api/
│ │ │ ├── CustomNodes.java # Interface defining custom methods
│ │ │ ├── impl/
│ │ │ │ └── CustomNodesImpl.java # Implementation of CustomNodes interface
│ │ │ └── nodes/
│ │ │ ├── NodesEntityResource.java # Custom entity resource for nodes
│ │ │ ├── NodeChildrenRelation.java # Custom relation for node children
│ │ │ └── package-info.java # Metadata for custom API
│ │ └── resources/
│ │ └── alfresco/
│ │ └── module/
│ │ └── custom-api-extension/
│ │ └── context/
│ │ └── bootstrap-context.xml # Spring configuration file
└── pom.xml # Maven project file
Conclusion: A Starting Point for Alfresco API Customizations
This guide provides an introductory approach to extending the Alfresco Nodes API, demonstrating how to create a custom MIME type restriction feature. While this example highlights the core steps to build and configure such an extension, a production-ready solution would benefit from additional validations, security checks, and performance optimizations. This guide is intended to help developers quickly understand the basics of API customization in Alfresco, offering a foundation they can expand upon for more comprehensive, real-world applications.