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

Your MSPA Goes Smart – Step-by-Step to a DIY Smart Home Hot Tub (Wi-Fi Upgrade)

Introduction

In this article, I’ll show you how I successfully reverse-engineered the serial communication protocol of an MSPA Muse Carlton hot tub. The goal was to read remote control commands and send custom ones. I used an ESP32 Dev Board[*] for this. This protocol likely works with other MSPA models as well.

This article is for makers, home automation enthusiasts, and tech fans who enjoy diving deep into technical systems.


Weiter >>

Dein MSPA wird smart – Schritt-für-Schritt zum DIY Smart Home Whirlpool (WIFI/WLAN Upgrade) / Reverse Engineering eines proprietären UART-Protokolls am Beispiel eines Whirlpool-Steuergeräts (MSPA Muse Carlton)

Einleitung

In diesem Artikel zeige ich dir, wie ich das serielle Kommunikationsprotokoll eines MSPA Muse Carlton Whirlpools erfolgreich reverse-engineered habe. Ziel war es, die Fernbedienungsbefehle auszulesen und eigene Kommandos zu senden. Dafür kam ein ESP32 Dev Board[*] zum Einsatz. Dieses Protokoll funktioniert vermutlich auch bei anderen MSPA-Modellen.

Dieser Artikel richtet sich an alle Maker, Home-Automatisierer und Technik-Fans, die gern tief in die Technik eintauchen.


Weiter >>

Der perfekte Einstieg in die Welt der Vinyls: Der Denon DP-300F und 2 Alternativen

Warum Vinyl?

Vinyl erlebt seit einigen Jahren ein riesiges Comeback. Der warme, analoge Klang, das bewusste Musikhören und das Sammeln von Schallplatten faszinieren immer mehr Musikliebhaber. Dabei ist nicht nur das nostalgische Feeling ausschlaggebend, sondern vor allem auch der unverwechselbare Klangcharakter von Vinyl – ein Klang, der trotz moderner digitaler Verfahren nach wie vor viele Fans begeistert.

Denon DP-300F[*] – Der ideale Allrounder für Einsteiger

Der Denon DP-300F[*] ist nach wie vor ein beliebter vollautomatischer Plattenspieler im Einsteigerbereich. Sein automatischer Tonarm sorgt für einen schonenden Umgang mit Nadel und Platte – perfekt, wenn du ohne großen Aufwand direkt in den Vinylgenuss starten möchtest.


Weiter >>

Ein Leitfaden für Senioren: Günstige Smartphones bis 100 Euro – Unsere 4 Favoriten & Erfahrungsbericht einer maßgeschneiderten Senioren-Lösung – UPDATE 2024/25

In diesem Beitrag stellen wir dir vier günstige Smartphones vor, die aktuell (Stand Dezember 2024) für unter 100 Euro erhältlich sind. Zusätzlich teilen wir eine ganz besondere Geschichte aus dem Familienkreis: Wie wir eines dieser Geräte für die Oma meiner Verlobten eingerichtet haben, damit sie trotz ihrer Parkinson-Erkrankung gut damit zurechtkommt.


Weiter >>