Enhancing Alfresco’s Public API (ACS): A Step-by-Step Guide to Custom Node Extensions with MIME Type Restrictions

| | Allgemein

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:

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:

  1. package-info.java: Defines the API scope, name, and version.
  2. 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.

Neueste Beiträge

Getting Started with Alfresco SDK/Development: A Beginner’s Guide to Automating File Organization with Alfresco Behaviors

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.


Weiter >>

Warum der Air Assist unverzichtbar ist – Mein Erfahrungsbericht

Nachdem ich meinen ATOMSTACK A12 Ultra Laser[*] und die R2 V2 Drehwalze[*] in Betrieb genommen hatte, war es nur eine Frage der Zeit, bis ich mir zusätzlich ein Air Assist System zugelegt habe. Ich entschied mich für das DEWALLIE Air Assist Set[*], und ich kann schon vorweg sagen: Es war eine der besten Ergänzungen für meine Lasergravur-Setups, vor allem beim Arbeiten mit Holz!


Weiter >>

Mein neues Setup: Der ATOMSTACK R2 V2 Drehwalze und A12 Ultra/Pro Laser – Perfekt für Gravuren auf runden Objekten!

Als ich mir kürzlich den ATOMSTACK A12 Ultra Laser[*] zugelegt habe, war mir schnell klar, dass ich das volle Potenzial dieses leistungsstarken Gravierers ausschöpfen wollte. Also habe ich nicht lange gezögert und gleich die ATOMSTACK R2 V2 Drehwalze[*] dazu gekauft, die es ermöglicht, zylindrische Objekte wie Trinkflaschen, Gläser oder Stifte zu gravieren.


Weiter >>