Close

ReST API Java Wrapper Extension Point

One of the libraries provided by the SDK for out-of-process extensions is the Java ReST API wrapper library. It enables you to work with the Alfresco ReST API from a Java client with standard Java classes. There is no need to parse JSON or create HTTP requests.

To set up a project with the Java ReST API library follow these instructions.

Common parameters

There are some common parameters in the different API calls. They are documented here instead of repeating the docs for them under multiple sections:

Parameter Description Usage
include Use this parameter to return additional information about the node. The following optional fields can be requested: allowableOperations, aspectNames, association, isLink, isFavorite, isLocked, path, properties, permissions. List<String> include = new ArrayList<>(); include.add("permissions");
fields You can use this parameter to restrict the fields returned within a response if, for example, you want to save on overall bandwidth. The list applies to a returned individual entity or entries within a collection. If the API method also supports the include parameter, then the fields specified in include parameter are returned in addition to those specified in the fields parameter. List<String> fields = new ArrayList<>(); fields.add("content,createdAt");
Note that all fields have to be added as one item comma separated.
where Optionally filter the node list. Here are some examples:
(isFolder=true)
(isFile=true)
(nodeType='my:specialNodeType')
(nodeType='my:specialNodeType INCLUDESUBTYPES')
(id BETWEEN ('1', '79'))
includeSource Also include source in addition to entries with folder information on the parent node – either the specified parent nodeId, or as resolved by relativePath Boolean includeSource = false;
orderBy A string to control the order of the entities returned in a list. The default sort order for the returned list is for folders to be sorted before files, and by ascending name. You can override the default using orderBy to specify one or more fields to sort by. The default order is always ascending, but you can use an optional ASC or DESC modifier to specify an ascending or descending sort order. For example, specifying orderBy=name DESC returns a mixed folder/file list in descending name order. You can use any of the following fields to order the results: isFolder, name, mimeType, nodeType, sizeInBytes, modifiedAt, createdAt, modifiedByUser, createdByUser List<String> orderBy = new ArrayList<>(); orderBy.add("title");
skipCount The number of entities that exist in the collection before those included in this list, useful when implementing paging scenarios. If not supplied then the default value is 0. Integer skipCount = 0;
maxItems The maximum number of items to return in the list, useful when implementing paging scenarios. If not supplied then the default value is 100. Integer maxItems = 100;
autoRename If true, then a name clash will cause an attempt to auto rename by finding a unique name using an integer suffix. Boolean autoRename = true;
versioningEnabled Should versioning of files be enabled at all? Boolean versioningEnabled = true;
majorVersion If true, then it will be a major version, such as 1.0, 2.0 etc. If false, then the version change will be 1.1, 1.2, which is a minor version change. Boolean majorVersion = true;
updateComment Add a version comment which will appear in version history. Setting this parameter also enables versioning of this node, if it is not already versioned. String updateComment = "A comment";
updatedName Optional new name of the node, sets cm:name. This should include the file extension. The name must not contain spaces or the following special characters: * " < > \ / ? : |. The character . must not be used at the end of the name. String updatedName = null; if not updating or set to new name.

=== Managing Folders and Files ===

The following sections walk through how to use the Java ReST API wrapper services when managing folders and files.

List contents of a folder

To list contents of a folder in the repository use the listNodeChildren method of the NodesApi, which is one of the main APIs used when you want to manipulate folders and files.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListFolderContent {
    static final Logger LOGGER = LoggerFactory.getLogger(ListFolderContent.class);

    @Autowired
    NodesApi nodesApi;

    public void execute() throws IOException {
        NodeChildAssociationPagingList nodes = listFolderContent("-root-", null);
        NodeChildAssociationPagingList nodes2 = listFolderContent("-root-", "/Data Dictionary");
    }

    /**
     * List contents (i.e. folders and files) of a folder.
     *
     * @param rootNodeId         the id of the folder node that is the root. If relativeFolderPath is null, then content in this folder will be listed. Besides node ID the aliases -my-, -root- and -shared- are also supported.
     * @param relativeFolderPath path relative rootNodeId, if this is not null, then the content of this folder will be listed
     * @return a list of child node objects contained in the folder, or null if not found
     */
    private NodeChildAssociationPagingList listFolderContent(String rootNodeId, String relativeFolderPath) {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> include = null;
        List<String> fields = null;
        List<String> orderBy = null;
        String where = null;
        Boolean includeSource = false;

        LOGGER.info("Listing folder {}{}", rootNodeId, relativeFolderPath);
        NodeChildAssociationPagingList result = nodesApi.listNodeChildren(rootNodeId, skipCount, maxItems, orderBy, where, include,
                relativeFolderPath, includeSource, fields).getBody().getList();
        for (NodeChildAssociationEntry childNodeAssoc: result.getEntries()) {
            LOGGER.info("Found node [name=" + childNodeAssoc.getEntry().getName() + "]");
        }

        return result;
    }
}

See also manage associations for information on how to list secondary child associations for a node.

Filter contents of a folder

To filter listed contents of a folder in the repository use the listNodeChildren method of the NodesApi and set the where clause parameter.

More info about this ReST API endpoint

For a description of the common parameters, such as where and include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListFolderContent {
    static final Logger LOGGER = LoggerFactory.getLogger(ListFolderContent.class);

    @Autowired
    NodesApi nodesApi;

    public void execute() throws IOException {
        NodeChildAssociationPagingList nodes = listFolderContent("-root-", null);
        NodeChildAssociationPagingList nodes2 = listFolderContent("-root-", "/Data Dictionary");
    }

    /**
     * List sub-folders of a folder.
     *
     * @param rootNodeId         the id of the folder node that is the root. If relativeFolderPath is null, then content in this folder will be listed. Besides node ID the aliases -my-, -root- and -shared- are also supported.
     * @param relativeFolderPath path relative rootNodeId, if this is not null, then the content of this folder will be listed
     * @return a list of child folder node objects contained in the folder, or null if not found
     */
    private NodeChildAssociationPagingList listFolderContent(String rootNodeId, String relativeFolderPath) {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> include = null;
        List<String> fields = null;
        List<String> orderBy = null;
        String where = "(isFolder=true)";
        Boolean includeSource = false;

        LOGGER.info("Listing folder {}{} with filter {}", rootNodeId, relativeFolderPath, where);
        NodeChildAssociationPagingList result = nodesApi.listNodeChildren(rootNodeId, skipCount, maxItems, orderBy, where, include,
                relativeFolderPath, includeSource, fields).getBody().getList();
        for (NodeChildAssociationEntry childNodeAssoc: result.getEntries()) {
            LOGGER.info("Found folder node [name=" + childNodeAssoc.getEntry().getName() + "]");
        }

        return result;
    }
}

Get folder/file metadata

To get metadata for a node, such as a file or folder, in the repository use the getNode method of the NodesApi, which is one of the main APIs used when you want to manipulate folders and files.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.NodeEntry;
import org.alfresco.core.model.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class GetNodeMetadataCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetNodeMetadataCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute() throws IOException {
        Node node = getNode("-root-", null);
        Node node2 = getNode("-root-", "/Data Dictionary");
    }

    /**
     * Get a node (file/folder).
     *
     * @param nodeId             the id of the node that we want to fetch metadata for. If relativeFolderPath is specified, then metadata for this node will be returned. Besides node ID the aliases -my-, -root- and -shared- are also supported.
     * @param relativeFolderPath A path relative to the nodeId, if this is not null, then metadata is returned on the node resolved by this path
     * @return Node object if exist, or null if does not exist
     */
    private Node getNode(String nodeId,
                         String relativeFolderPath) {
        List<String> include = null;
        List<String> fields = null;

        NodeEntry result = nodesApi.getNode(nodeId, include, relativeFolderPath, fields).getBody();
        LOGGER.info("Got node {}", result.getEntry());
        return result.getEntry();
    }
}

Executing this code would give the following result:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar get-node        
                                       
2021-04-29 08:09:22.215  INFO 18370 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.536 seconds (JVM running for 4.449)
2021-04-29 08:09:22.217  INFO 18370 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: get-node
2021-04-29 08:09:22.485  INFO 18370 --- [           main] o.a.tutorial.restapi.GetNodeMetadataCmd  : Got node class Node {
    id: e439190c-3fe0-48a1-8a9a-374fbc54b570
    name: Company Home
    nodeType: cm:folder
    isFolder: true
    isFile: false
    isLocked: false
    modifiedAt: 2021-04-28T11:48:08.325Z
    modifiedByUser: class UserInfo {
        displayName: System
        id: System
    }
    createdAt: 2021-04-28T11:47:59.098Z
    createdByUser: class UserInfo {
        displayName: System
        id: System
    }
    parentId: null
    isLink: null
    isFavorite: null
    content: null
    aspectNames: [cm:titled, cm:auditable, app:uifacets]
    properties: {cm:title=Company Home, cm:description=The company root space, app:icon=space-icon-default}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-29 08:09:22.538  INFO 18370 --- [           main] o.a.tutorial.restapi.GetNodeMetadataCmd  : Got node class Node {
    id: 1219e0ff-941f-49df-9151-997b84a8359b
    name: Data Dictionary
    nodeType: cm:folder
    isFolder: true
    isFile: false
    isLocked: false
    modifiedAt: 2021-04-28T11:48:17.700Z
    modifiedByUser: class UserInfo {
        displayName: System
        id: System
    }
    createdAt: 2021-04-28T11:47:59.199Z
    createdByUser: class UserInfo {
        displayName: System
        id: System
    }
    parentId: e439190c-3fe0-48a1-8a9a-374fbc54b570
    isLink: null
    isFavorite: null
    content: null
    aspectNames: [cm:titled, cm:auditable, app:uifacets]
    properties: {cm:title=Data Dictionary, cm:description=User managed definitions, app:icon=space-icon-default}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

See also manage associations for information on how to list associations for a node.

Create a folder

To create a folder in the repository use the createNode method of the NodesApi, which is one of the main APIs used when you want to manipulate folders and files.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyCreate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;

@Component
public class CreateFolder {
    static final Logger LOGGER = LoggerFactory.getLogger(CreateFolder.class);

    @Autowired
    NodesApi nodesApi;

    public void execute() throws IOException {
        Node folderInCompanyHome = createFolder("Folder1", "Title1", "Desc1", "");
        Node folderInFolder1 = createFolder("Folder2", "Title2", "Desc2", "/Folder1");
    }

    /**
     * Make the remote call to create a folder in the repository, if it does not exist.
     *
     * @param folderName         the name of the folder
     * @param folderTitle        the title of the folder
     * @param folderDescription  the description of the folder
     * @param relativeFolderPath path relative to /Company Home
     * @return a node object for the newly created node, contains the ID,
     * such as e859588c-ae81-4c5e-a3b6-4c6109b6c905
     */
    private Node createFolder(String folderName,
                              String folderTitle,
                              String folderDescription,
                              String relativeFolderPath) throws IOException {
        Objects.requireNonNull(folderName);

        String rootPath = "-root-";       // /Company Home
        String folderType = "cm:folder";  // Standard out-of-the-box folder type

        List<String> folderAspects = new ArrayList<String>();
        folderAspects.add("cm:titled");
        Map<String, String> folderProps = new HashMap<>();
        folderProps.put("cm:title", folderTitle);
        folderProps.put("cm:description", folderDescription);
        
        String nodeId = rootPath; // The id of a node. You can also use one of these well-known aliases: -my-, -shared-, -root-
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(folderName);
        nodeBodyCreate.setNodeType(folderType);
        nodeBodyCreate.setAspectNames(folderAspects);
        nodeBodyCreate.setProperties(folderProps);
        nodeBodyCreate.setRelativePath(relativeFolderPath);

        List<String> include = null;
        List<String> fields = null;
        Boolean autoRename = true;
        Boolean majorVersion = true;
        // Should versioning be enabled at all?
        Boolean versioningEnabled = true;

        Node folderNode = nodesApi.createNode(nodeId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled,
                include, fields).getBody().getEntry();
        LOGGER.info("Created new folder: {}", folderNode);

        return folderNode;
    }
}

See also manage associations for more examples of node creation.

Upload a file

To upload a file to the repository use first the NodesApi.createNode method, which will create the metadata for the file, and then the NodesApi.updateNodeContent method that will set the content for the file.

At the moment it’s necessary to make two calls to upload a file, but enhancements are being developed in a new version of SDK 5 to provide a one method approach as this is supported in the ReST API.

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyCreate;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class CreateFile {
    static final Logger LOGGER = LoggerFactory.getLogger(CreateFile.class);

    private List<String> include = null;
    private List<String> fields = null;
    private String contentType = "cm:content"; // Out-of-the-box content type for generic file content
    private Boolean autoRename = true;
    private Boolean majorVersion = true;
    private Boolean versioningEnabled = true;
    private String updateComment = null;
    private String updatedName = null;

    @Autowired
    NodesApi nodesApi;

    public void execute() throws IOException {
        // Create a text file under the /Company Home/Guest Home folder
        Node newTextFile = createTextFile("-root-", "somestuff.txt", "TextfileTitle",
                "TextfileDesc", "/Guest Home", "Some text for the file");

        // Upload a file from disk to the '/Company Home/Guest Home' folder, the file that is being uploaded, 'somepicture.png' 
        // in this case, is located in the same directory as we are running the app from
        Node newFile = uploadFile("-root-", "somepicture.png", "PicturefileTitle",
                "PicturefileDesc", "/Guest Home", "somepicture.png");
    }

    /**
     * Upload a file from disk
     */
    private Node uploadFile(String parentFolderId, String fileName, String title, String description,
                            String relativeFolderPath, String filePath) {
        Node fileNode = createFileMetadata(parentFolderId, fileName, title, description, relativeFolderPath);

        // Get the file bytes
        File someFile = new File(filePath);
        byte[] fileData = null;
        try {
            fileData = FileUtils.readFileToByteArray(someFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Add the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNode.getId(),
                fileData, majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Created file with content: {}", updatedFileNode.toString());

        return updatedFileNode;
    }

    /**
     * Create a text file
     */
    private Node createTextFile(String parentFolderId, String fileName, String title, String description,
                                String relativeFolderPath, String textContent) {
        Node fileNode = createFileMetadata(parentFolderId, fileName, title, description, relativeFolderPath);

        // Add the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNode.getId(),
                textContent.getBytes(), majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Created file with content: {}", updatedFileNode.toString());

        return updatedFileNode;
    }

    /**
     * Create metadata for a file node
     *
     * @param parentFolderId the parent folder node ID where the file should be stored
     * @param fileName the name for the new file
     * @param title the title property value for the new file
     * @param description the description property value for the new file
     * @param relativeFolderPath path relative to /Company Home
     * @return a Node object with file metadata and the Node ID
     */
    private Node createFileMetadata(String parentFolderId, String fileName, String title, String description,
                                    String relativeFolderPath) {
        List<String> fileAspects = new ArrayList<String>();
        fileAspects.add("cm:titled");
        Map<String, String> fileProps = new HashMap<>();
        fileProps.put("cm:title", title);
        fileProps.put("cm:description", description);
        
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(fileName);
        nodeBodyCreate.setNodeType(contentType);
        nodeBodyCreate.setAspectNames(fileAspects);
        nodeBodyCreate.setProperties(fileProps);
        nodeBodyCreate.setRelativePath(relativeFolderPath);

        // Create the file node metadata
        Node fileNode = nodesApi.createNode(parentFolderId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled,
                include, fields).getBody().getEntry();

        return fileNode;
    }
}

See also manage associations for more examples of uploading and creating nodes.

Upload a file with custom type

Uploading a file with a custom type to the Repository means creating a node with a type other than cm:content. See upload a file for info on how to upload a file with the out-of-the-box content type cm:content set. There’s actually not much difference to how you upload a file with a custom type. Let’s say we have a content model type acme:document and an aspect acme:securityClassified and these are defined as follows:

<type name="acme:document">
   <title>Sample Document Type</title>
   <parent>cm:content</parent>
   <properties>
       <property name="acme:documentId">
           <title>Document Identification Number</title>
           <type>d:text</type>
       </property>
   </properties>
   <mandatory-aspects>
       <aspect>acme:securityClassified</aspect>
   </mandatory-aspects>
</type>

<aspect name="acme:securityClassified">
    <title>ACME Security Classified</title>
    <description>Content has been security classified</description>
    <properties>
        <property name="acme:securityClassification">
            <type>d:text</type>
            <index enabled="true">
                <atomic>true</atomic>
                <stored>false</stored>
                <tokenised>false</tokenised>
            </index>
            <constraints>
                <constraint ref="acme:securityClassificationOptions"/>
            </constraints>
        </property>
    </properties>
</aspect>

We got two custom properties acme:documentId and acme:securityClassification. For more information on how to implement and deploy this custom content model see this documentation.

Now, to upload a file and set this custom type and aspect we use the same code as for upload a file with minor updates to the contentType variable and the createFileMetadata method:

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyCreate;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class CreateFileCustomTypeCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(CreateFileCustomTypeCmd.class);

    private String contentType = "acme:document"; // Set custom content type
    private Boolean autoRename = true;
    private Boolean majorVersion = true;
    private Boolean versioningEnabled = true;
    private String updateComment = null;
    private String updatedName = null;
    private List<String> include = null;
    private List<String> fields = null;

    @Autowired
    NodesApi nodesApi;

    public void execute() throws IOException {
        // Create a text file under the /Company Home/Guest Home folder
        Node newTextFile = createTextFile("-root-", "somestuff2.txt", "TextfileTitle2",
                "TextfileDesc2", "/Guest Home", "Some text for the file2");

        // Upload a file from disk to the /Company Home/Guest Home folder, file resides in app dir
        Node newFile = uploadFile("-root-", "somepicture2.png", "PicturefileTitle2",
                "PicturefileDesc2", "/Guest Home", "somepicture.png");
    }

    /**
     * Upload a file from disk
     */
    private Node uploadFile(String parentFolderId, String fileName, String title, String description,
                            String relativeFolderPath, String filePath) {
        Node fileNode = createFileMetadata(parentFolderId, fileName, title, description, relativeFolderPath);

        // Get the file bytes
        File someFile = new File(filePath);
        byte[] fileData = null;
        try {
            fileData = FileUtils.readFileToByteArray(someFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Add the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNode.getId(),
                fileData, majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Created file with content: {}", updatedFileNode.toString());

        return updatedFileNode;
    }

    /**
     * Create a text file
     */
    private Node createTextFile(String parentFolderId, String fileName, String title, String description,
                                String relativeFolderPath, String textContent) {
        Node fileNode = createFileMetadata(parentFolderId, fileName, title, description, relativeFolderPath);

        // Add the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNode.getId(),
                textContent.getBytes(), majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Created file with content: {}", updatedFileNode.toString());

        return updatedFileNode;
    }

    /**
     * Create metadata for a file node
     *
     * @param parentFolderId the parent folder node ID where the file should be stored
     * @param fileName the name for the new file
     * @param title the title property value for the new file
     * @param description the description property value for the new file
     * @param relativeFolderPath path relative to /Company Home
     * @return a Node object with file metadata and the Node ID
     */
    private Node createFileMetadata(String parentFolderId, String fileName, String title, String description,
                                    String relativeFolderPath) {
        List<String> fileAspects = new ArrayList<String>();
        fileAspects.add("cm:titled");
        fileAspects.add("acme:securityClassified");
        Map<String, String> fileProps = new HashMap<>();
        fileProps.put("cm:title", title);
        fileProps.put("cm:description", description);
        fileProps.put("acme:documentId", "DOC-001");                          // custom prop from type
        fileProps.put("acme:securityClassification", "Company Confidential"); // custom prop from aspect
        
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(fileName);
        nodeBodyCreate.setNodeType(contentType);
        nodeBodyCreate.setAspectNames(fileAspects);
        nodeBodyCreate.setProperties(fileProps);
        nodeBodyCreate.setRelativePath(relativeFolderPath);

        // Create the file node metadata
        Node fileNode = nodesApi.createNode(parentFolderId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled,
                include, fields).getBody().getEntry();

        return fileNode;
    }
}

See also manage associations for more examples of uploading and creating nodes.

Upload a new version of file

To upload a new version of a file to the repository use the NodesApi.updateNodeContent method, which will set the new content for the file.

More info about this ReST API endpoint

For a description of the common parameters, such as majorVersion, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyCreate;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class UploadNewFileVersionCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(UploadNewFileVersionCmd.class);
    
    private Boolean majorVersion = true;
    private String updateComment = null;
    private String updatedName = null;
    private List<String> include = null;
    private List<String> fields = null;

    @Autowired
    NodesApi nodesApi;

    public void execute(String textFileNodeId, String binFileNodeId) throws IOException {
        // Update text content for a file
        Node newTextFile = updateTextFileContent(textFileNodeId,"Some UPDATED text for the file");

        // Upload a file as new content
        Node newFile = uploadNewFileVersion(binFileNodeId, "updatedpicture.png");
    }

    /**
     * Upload a file from disk as a new version
     */
    private Node uploadNewFileVersion(String fileNodeId, String filePath) {
        // Get the file bytes
        File someFile = new File(filePath);
        byte[] fileData = null;
        try {
            fileData = FileUtils.readFileToByteArray(someFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Update the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNodeId,
                fileData, majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Uploaded new content for file: {}", updatedFileNode.toString());

        return updatedFileNode;
    }

    /**
     * Update text content for a file
     */
    private Node updateTextFileContent(String fileNodeId, String textContent) {
        // Update the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNodeId,
                textContent.getBytes(), majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Updated text content for file: {}", updatedFileNode.toString());

        return updatedFileNode;
    }
}

We would execute this command class something like this, passing in the command name, text file Node ID, and the binary file node id:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar upload-new-version 0492460b-6269-4ca1-9668-0d934d2f3370 48413f7a-066d-4e38-b2e6-c84ede635493

2021-04-28 13:44:51.437  INFO 15466 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 2.782 seconds (JVM running for 3.471)
2021-04-28 13:44:51.439  INFO 15466 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: upload-new-version
2021-04-28 13:44:51.441  INFO 15466 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-28 13:44:51.441  INFO 15466 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 48413f7a-066d-4e38-b2e6-c84ede635493
2021-04-28 13:44:51.981  INFO 15466 --- [           main] o.a.t.restapi.UploadNewFileVersionCmd    : Updated text content for file: class Node {
    id: 0492460b-6269-4ca1-9668-0d934d2f3370
    name: somestuff2.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-28T12:44:51.578Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.143Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, cm:titled, cm:auditable, acme:securityClassified, cm:author, cm:thumbnailModification]
    properties: {cm:title=TextfileTitle2, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1619611506701], cm:description=TextfileDesc2}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-28 13:44:56.783  INFO 15466 --- [           main] o.a.t.restapi.UploadNewFileVersionCmd    : Uploaded new content for file: class Node {
    id: 48413f7a-066d-4e38-b2e6-c84ede635493
    name: somepicture2.png
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-28T12:44:52.055Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.621Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: image/png
        mimeTypeName: PNG Image
        sizeInBytes: 23174
        encoding: UTF-8
    }
    aspectNames: [rn:renditioned, cm:versionable, cm:titled, cm:auditable, acme:securityClassified, cm:author, cm:thumbnailModification, exif:exif]
    properties: {cm:title=PicturefileTitle2, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, exif:pixelYDimension=256, acme:securityClassification=Company Confidential, exif:pixelXDimension=256, cm:lastThumbnailModification=[doclib:1619611506392, imgpreview:1619611515611], cm:description=PicturefileDesc2}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

Note how the cm:versionLabel has bumped up to 3.0. Version 1.0 and 2.0 were created when the files were created in two steps.

Get file version history

To get the version history for a file use the VersionsApi.listVersionHistory method, which will retrieve a list of all the node versions.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.VersionsApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListVersionHistoryCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListVersionHistoryCmd.class);

    @Autowired
    VersionsApi versionsApi;

    public void execute(String fileNodeId) throws IOException {
        VersionPagingList nodes = listVersionHistory(fileNodeId);
    }
    
    /**
     * List the version history for a file node.
     *
     * @param fileNodeId the id of the file node
     * @return a list of child node objects contained in the folder, or null if not found
     */
    private VersionPagingList listVersionHistory(String fileNodeId) {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> include = null;
        List<String> fields = null;

        LOGGER.info("Listing versions for file node ID {}", fileNodeId);
        VersionPagingList result = versionsApi.listVersionHistory(fileNodeId, include, fields, skipCount, maxItems).getBody().getList();
        for (VersionEntry versionEntry: result.getEntries()) {
            LOGGER.info("Node version " + versionEntry.getEntry().toString());
        }

        return result;
    }
}

We would execute this command class something like this, passing in the file Node ID:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-file-versions 0492460b-6269-4ca1-9668-0d934d2f3370                   

2021-04-29 08:04:48.145  INFO 18326 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 6.498 seconds (JVM running for 7.686)
2021-04-29 08:04:48.148  INFO 18326 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-file-versions
2021-04-29 08:04:48.152  INFO 18326 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 08:04:48.152  INFO 18326 --- [           main] o.a.t.restapi.ListVersionHistoryCmd      : Listing versions for file node ID 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 08:04:48.990  INFO 18326 --- [           main] o.a.t.restapi.ListVersionHistoryCmd      : Node version class Version {
    id: 3.0
    versionComment: null
    name: somestuff2.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    modifiedAt: 2021-04-28T12:44:51.578Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: null
    properties: null
}
2021-04-29 08:04:48.990  INFO 18326 --- [           main] o.a.t.restapi.ListVersionHistoryCmd      : Node version class Version {
    id: 2.0
    versionComment: null
    name: somestuff2.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    modifiedAt: 2021-04-28T12:02:33.526Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 23
        encoding: ISO-8859-1
    }
    aspectNames: null
    properties: null
}
2021-04-29 08:04:48.990  INFO 18326 --- [           main] o.a.t.restapi.ListVersionHistoryCmd      : Node version class Version {
    id: 1.0
    versionComment: null
    name: somestuff2.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    modifiedAt: 2021-04-28T12:02:33.143Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 0
        encoding: UTF-8
    }
    aspectNames: null
    properties: null
}

Note the id property that contains the file version number. The versionComment property would contain any comments made when uploading a new version of the file. Folder nodes does not have content.

Download a file

To download a file use the NodesApi.getNodeContent method, which will download the content bytes for the file.

More info about this ReST API endpoint

import org.alfresco.core.handler.NodesApi;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.time.OffsetDateTime;

@Component
public class GetNodeContentCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetNodeContentCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String fileNodeId, String filePathOnDisk) throws IOException {
        Resource nodeContent = getNodeContent(fileNodeId);

        // Write file to disk
        File targetFile = new File(filePathOnDisk);
        FileUtils.copyInputStreamToFile(nodeContent.getInputStream(), targetFile);
    }

    /**
     * Get a file node content bytes (folders does not have content).
     *
     * @param nodeId   the id of the file node that we want to fetch content for.
     * @return Node content info object
     */
    private Resource getNodeContent(String nodeId) throws IOException {
        // Relevant when using API call from web browser, true is the default
        Boolean attachment = true;
        // Only download if modified since this time, optional
        OffsetDateTime ifModifiedSince = null;
        // The Range header indicates the part of a document that the server should return.
        // Single part request supported, for example: bytes=1-10., optional
        String range = null;

        Resource result = nodesApi.getNodeContent(nodeId, attachment, ifModifiedSince, range).getBody();
        LOGGER.info("Got node {} size: {}", result.getFilename(), result.contentLength());

        return result;
    }
}

Download multiple files

To download multiple files use the DownloadsApi.createDownload method to create a ZIP download on the server side, then check the status of the ZIP download with DownloadsApi.getDownload. When the download is ready get it with NodesApi.getNodeConent

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.DownloadsApi;
import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Download;
import org.alfresco.core.model.DownloadBodyCreate;
import org.alfresco.core.model.DownloadEntry;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.List;

@Component
public class GetMultipleNodeContentCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetMultipleNodeContentCmd.class);

    @Autowired
    DownloadsApi downloadsApi;

    @Autowired
    NodesApi nodesApi;

    public void execute(String[] fileNodeIds, String zipFilePathOnDisk) throws IOException, InterruptedException {
        DownloadEntry downloadEntry = createZipDownload(fileNodeIds);
        Resource zipNodeContent = getNodeContent(downloadEntry.getEntry().getId());

        // Write ZIP file to disk
        File targetFile = new File(zipFilePathOnDisk);
        FileUtils.copyInputStreamToFile(zipNodeContent.getInputStream(), targetFile);
    }

    /**
     * Create a ZIP download with multiple file nodes. This method waits until download is ready.
     *
     * @param nodeIds   the node ids for the files we want to download in one ZIP
     * @return download entry info object for the ZIP
     */
    private DownloadEntry createZipDownload(String[] nodeIds) throws InterruptedException {
        List<String> fields = null;

        // Set up POST data with node IDs we want to download
        DownloadBodyCreate downloads = new DownloadBodyCreate();
        for (String nodeId : nodeIds) {
            downloads.addNodeIdsItem(nodeId);
        }

        // First create the download on the server side
        DownloadEntry result = downloadsApi.createDownload(downloads, fields).getBody();

        LOGGER.info("Created ZIP download: {}", result.getEntry().toString());

        // Check the download status
        DownloadEntry download = downloadsApi.getDownload(result.getEntry().getId(), fields).getBody();
        while (!download.getEntry().getStatus().equals(Download.StatusEnum.DONE)) {
            LOGGER.info("Checking ZIP download status: {}", download.getEntry().getStatus());
            Thread.sleep(1000); // do nothing for 1000 milliseconds (1 second)
            download = downloadsApi.getDownload(result.getEntry().getId(), fields).getBody();
        }

        LOGGER.info("ZIP download is READY: {}", result.getEntry().getId());

        return download;
    }

    /**
     * Get a file node content bytes (folders does not have content).
     *
     * @param nodeId   the id of the file node that we want to fetch content for.
     * @return Node content info object
     */
    private Resource getNodeContent(String nodeId) throws IOException {
        // Relevant when using API call from web browser, true is the default
        Boolean attachment = true;
        // Only download if modified since this time, optional
        OffsetDateTime ifModifiedSince = null;
        // The Range header indicates the part of a document that the server should return.
        // Single part request supported, for example: bytes=1-10., optional
        String range = null;

        Resource result = nodesApi.getNodeContent(nodeId, attachment, ifModifiedSince, range).getBody();
        LOGGER.info("Got node {} size: {}", result.getFilename(), result.contentLength());

        return result;
    }
}

Executing the code gives a result looking something like this:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar get-multiple-file-content 0492460b-6269-4ca1-9668-0d934d2f3370 48413f7a-066d-4e38-b2e6-c84ede635493 mydownload.zip

2021-04-29 12:58:53.560  INFO 19432 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 2.956 seconds (JVM running for 3.436)
2021-04-29 12:58:53.562  INFO 19432 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: get-multiple-file-content
2021-04-29 12:58:53.564  INFO 19432 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 12:58:53.564  INFO 19432 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 48413f7a-066d-4e38-b2e6-c84ede635493
2021-04-29 12:58:53.564  INFO 19432 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[3]: mydownload.zip
2021-04-29 12:58:54.150  INFO 19432 --- [           main] o.a.t.restapi.GetMultipleNodeContentCmd  : Created ZIP download: class Download {
    filesAdded: 0
    bytesAdded: 0
    id: b73c36e5-112b-48a0-baa6-fa225bd9d53d
    totalFiles: 0
    totalBytes: 0
    status: PENDING
}
2021-04-29 12:58:54.167  INFO 19432 --- [           main] o.a.t.restapi.GetMultipleNodeContentCmd  : Checking ZIP download status: PENDING
2021-04-29 12:58:55.194  INFO 19432 --- [           main] o.a.t.restapi.GetMultipleNodeContentCmd  : ZIP download is READY: b73c36e5-112b-48a0-baa6-fa225bd9d53d
2021-04-29 12:58:55.223  INFO 19432 --- [           main] o.a.t.restapi.GetMultipleNodeContentCmd  : Got node archive.zip size: 23111

In this example we pass in two file node identifiers that will be requested in the zip download. The zip download file will be stored in current directory under the name mydownload.zip. Note that right after we have initialized the creation of the zip download there are no files added to the zip (filesAdded: 0), we have to wait for the zip download to be created on the server side. Then we can download with the usual getNodeContent method.

List file renditions

To list content renditions for a file use the RenditionsApi.listRenditions method.

More info about this ReST API endpoint

For a description of the common parameters, such as where, see this section.

import org.alfresco.core.handler.RenditionsApi;
import org.alfresco.core.model.RenditionEntry;
import org.alfresco.core.model.RenditionPagingList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListRenditionsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListRenditionsCmd.class);

    @Autowired
    RenditionsApi renditionsApi;

    public void execute(String fileNodeId) throws IOException {
        RenditionPagingList nodeRenditions = listRenditions(fileNodeId);
    }
    
    /**
     * List renditions for a file node.
     *
     * @param fileNodeId the id of the file node
     * @return a list of renditions, or null if not found
     */
    private RenditionPagingList listRenditions(String fileNodeId) {
        String where = null; // filter renditions

        LOGGER.info("Listing versions for file node ID {}", fileNodeId);
        RenditionPagingList result = renditionsApi.listRenditions(fileNodeId, where).getBody().getList();
        for (RenditionEntry renditionEntry: result.getEntries()) {
            LOGGER.info("Node rendition: " + renditionEntry.getEntry().toString());
        }

        return result;
    }
}

Executing this code looks like this, here we are listing renditions for a text file:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-renditions 0492460b-6269-4ca1-9668-0d934d2f3370                 

2021-04-29 13:58:25.387  INFO 19701 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.131 seconds (JVM running for 3.822)
2021-04-29 13:58:25.389  INFO 19701 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-renditions
2021-04-29 13:58:25.390  INFO 19701 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 13:58:25.391  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Listing versions for file node ID 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 13:58:25.703  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Node rendition: class Rendition {
    id: avatar
    content: class ContentInfo {
        mimeType: image/png
        mimeTypeName: PNG Image
        sizeInBytes: null
        encoding: null
    }
    status: NOT_CREATED
}
2021-04-29 13:58:25.703  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Node rendition: class Rendition {
    id: avatar32
    content: class ContentInfo {
        mimeType: image/png
        mimeTypeName: PNG Image
        sizeInBytes: null
        encoding: null
    }
    status: NOT_CREATED
}
2021-04-29 13:58:25.703  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Node rendition: class Rendition {
    id: doclib
    content: class ContentInfo {
        mimeType: image/png
        mimeTypeName: PNG Image
        sizeInBytes: 222
        encoding: UTF-8
    }
    status: CREATED
}
2021-04-29 13:58:25.703  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Node rendition: class Rendition {
    id: imgpreview
    content: class ContentInfo {
        mimeType: image/jpeg
        mimeTypeName: JPEG Image
        sizeInBytes: null
        encoding: null
    }
    status: NOT_CREATED
}
2021-04-29 13:58:25.703  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Node rendition: class Rendition {
    id: medium
    content: class ContentInfo {
        mimeType: image/jpeg
        mimeTypeName: JPEG Image
        sizeInBytes: null
        encoding: null
    }
    status: NOT_CREATED
}
2021-04-29 13:58:25.703  INFO 19701 --- [           main] o.a.tutorial.restapi.ListRenditionsCmd   : Node rendition: class Rendition {
    id: pdf
    content: class ContentInfo {
        mimeType: application/pdf
        mimeTypeName: Adobe PDF Document
        sizeInBytes: 8472
        encoding: UTF-8
    }
    status: CREATED
}

We can see the renditions id, such as pdf. Some renditions are for things you see in the UI, such as thumbnail and preview of document.

Get file rendition content

To get the content for a file rendition the RenditionsApi.getRenditionContent method, which will download the content bytes for the file rendition.

More info about this ReST API endpoint

import org.alfresco.core.handler.RenditionsApi;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.time.OffsetDateTime;

@Component
public class GetRenditionContentCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetRenditionContentCmd.class);

    @Autowired
    RenditionsApi renditionsApi;

    public void execute(String fileNodeId, String renditionId, String filePathOnDisk) throws IOException {
        Resource nodeContent = getRenditionContent(fileNodeId, renditionId);

        // Write rendition file to disk
        File targetFile = new File(filePathOnDisk);
        FileUtils.copyInputStreamToFile(nodeContent.getInputStream(), targetFile);
    }

    /**
     * Get rendition content info.
     *
     * @param nodeId        the id for the node that the rendition is for
     * @param renditionId   the id of the rendition that we want to fetch content for, such as doclib, pdf etc
     * @return Rendition content info object
     */
    private Resource getRenditionContent(String nodeId, String renditionId) throws IOException {
        // Relevant when using API call from web browser, true is the default
        Boolean attachment = true;
        // Only download if modified since this time, optional
        OffsetDateTime ifModifiedSince = null;
        // The Range header indicates the part of a rendition that the server should return.
        // Single part request supported, for example: bytes=1-10., optional
        String range = null;
        // If true and there is no rendition for this nodeId and renditionId, then the placeholder image for the
        // mimetype of this rendition is returned, rather than a 404 response
        Boolean placeholder = false;

        Resource result = renditionsApi.getRenditionContent(
                nodeId, renditionId, attachment, ifModifiedSince, range, placeholder).getBody();
        LOGGER.info("Got rendition {} size: {}", result.getFilename(), result.contentLength());

        return result;
    }
}

Executing this code and getting a pdf rendition for a text file looks like this:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar get-rendition-content 0492460b-6269-4ca1-9668-0d934d2f3370 pdf mytext.pdf 

2021-04-29 16:05:25.501  INFO 20077 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.324 seconds (JVM running for 3.867)
2021-04-29 16:05:25.504  INFO 20077 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: get-rendition-content
2021-04-29 16:05:25.506  INFO 20077 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 16:05:25.506  INFO 20077 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: pdf
2021-04-29 16:05:25.506  INFO 20077 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[3]: mytext.pdf
2021-04-29 16:05:25.657  INFO 20077 --- [           main] o.a.t.restapi.GetRenditionContentCmd     : Got rendition pdf size: 8472

Update metadata for a folder or file

To update metadata for a node, such as a file or folder, use the NodesApi.updateNode method.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyUpdate;
import org.alfresco.core.model.NodeEntry;
import org.alfresco.core.model.PermissionsBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class UpdateNodeMetadataCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(UpdateNodeMetadataCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String nodeId) throws IOException {
        Map<String, Object> properties = new HashMap<>();
        properties.put("cm:title", "UPDATED title");
        properties.put("cm:description", "UPDATED description");
        Node node = updateNode(nodeId, "newname.txt", properties, null, null);
    }

    /**
     * Update a node (such as file/folder).
     *
     * @param nodeId the id of the node that we want to update metadata for.
     * @param newName a new name for the node (sets cm:name)
     * @param properties the properties we want to update and their new values
     * @param aspectNames a list of aspect names to set the node, not that it needs to include all aspects as it will overwrite
     * @param permissionsBody permissions to set on the node
     * @return updated Node object
     */
    private Node updateNode(String nodeId,
                            String newName,
                            Map<String, Object> properties,
                            List<String> aspectNames,
                            PermissionsBody permissionsBody) {

        List<String> include = null;
        List<String> fields = null;

        NodeBodyUpdate nodeBodyUpdate = new NodeBodyUpdate();
        nodeBodyUpdate.setName(newName);
        nodeBodyUpdate.setProperties(properties);
        nodeBodyUpdate.setAspectNames(aspectNames);
        nodeBodyUpdate.setPermissions(permissionsBody);

        NodeEntry result = nodesApi.updateNode(nodeId, nodeBodyUpdate, include, fields).getBody();
        LOGGER.info("Updated node {}", result.getEntry());

        return result.getEntry();
    }
}

With the updateNode call we can update properties, aspects, and permissions for a node. Note that when updating aspects you need to include the complete list of aspects that should be set on the node as this call overwrites. You can fetch existing aspects with the getNodeMetadata call. If you are adding an aspect that has properties, then you can just add the properties and the aspect will be added automatically for you.

Executing this code result in this for a text file example:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar update-metadata 0492460b-6269-4ca1-9668-0d934d2f3370

2021-04-29 16:27:42.303  INFO 20246 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.185 seconds (JVM running for 3.683)
2021-04-29 16:27:42.306  INFO 20246 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: update-metadata
2021-04-29 16:27:42.308  INFO 20246 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-29 16:27:43.089  INFO 20246 --- [           main] o.a.t.restapi.UpdateNodeMetadataCmd      : Updated node class Node {
    id: 0492460b-6269-4ca1-9668-0d934d2f3370
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-29T15:27:42.528Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.143Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, cm:titled, cm:auditable, acme:securityClassified, cm:author, cm:thumbnailModification]
    properties: {cm:title=UPDATED title, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1619613896873, pdf:1619701086215], cm:description=UPDATED description}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

We can see the updated name and properties in the returned node object.

Add aspects to a folder or file

To add aspects to a node, use the NodesApi.updateNode method. The way you do this is described in the update metadata for a node section. Note that you only need to add aspects to a node when they are so called “marker” aspects without properties.

More info about this ReST API endpoint

Remove aspects from a folder or file

To remove aspects from a node, use the NodesApi.updateNode method. The way you do this is described in the update metadata for a node section.

Removing an aspect from a node is similar to how you add a “marker” aspect. You first get the list of aspects currently applied to the node. Then you remove the aspect from the list. And finally you use an update node call with the updated aspect list.

More info about this ReST API endpoint

Get and Set permissions for a folder or file

To manage permissions for a node, use the NodesApi.updateNode method.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

In the following example we show how a node can be updated with new permissions for a group and a user.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class SetNodePermissionsMetadataCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(SetNodePermissionsMetadataCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String nodeId) throws IOException {
        // First get current permissions
        PermissionsInfo currentPermissions = getNodePermissions(nodeId);

        // Update with permissions for a user and a group
        // Add current permissions first, it will overwrite so we need to add what's already set
        PermissionsBody permissionsBody = new PermissionsBody();
        permissionsBody.setIsInheritanceEnabled(true);
        permissionsBody.setLocallySet(currentPermissions.getLocallySet());
        PermissionElement engineeringGroupPermission = new PermissionElement();
        engineeringGroupPermission.setName("Collaborator");
        engineeringGroupPermission.setAuthorityId("GROUP_engineering");
        engineeringGroupPermission.setAccessStatus(PermissionElement.AccessStatusEnum.ALLOWED);
        permissionsBody.addLocallySetItem(engineeringGroupPermission);
        PermissionElement testUserPermission = new PermissionElement();
        testUserPermission.setName("Contributor");
        testUserPermission.setAuthorityId("tester");
        testUserPermission.setAccessStatus(PermissionElement.AccessStatusEnum.ALLOWED);
        permissionsBody.addLocallySetItem(testUserPermission);

        // Update permissions for node
        Node node = updateNodePermissions(nodeId, permissionsBody);
    }

    /**
     * Get node permissions.
     *
     * @param nodeId the id of the node that we want to get permissions for.
     * @return updated Node object
     */
    private PermissionsInfo getNodePermissions(String nodeId) {
        String relativePath = null;
        List<String> fields = null;
        List<String> include = new ArrayList<>();
        include.add("permissions");

        NodeEntry result = nodesApi.getNode(nodeId, include, relativePath, fields).getBody();
        LOGGER.info("Got node including permissions {}", result.getEntry());

        return result.getEntry().getPermissions();
    }

    /**
     * Update node permissions.
     *
     * @param nodeId the id of the node that we want to update permissions for.
     * @param permissionsBody permissions to set on the node
     * @return updated Node object
     */
    private Node updateNodePermissions(String nodeId,
                                       PermissionsBody permissionsBody) {

        List<String> include = new ArrayList<>();
        include.add("permissions");
        List<String> fields = null;

        NodeBodyUpdate nodeBodyUpdate = new NodeBodyUpdate();
        nodeBodyUpdate.setPermissions(permissionsBody);

        NodeEntry result = nodesApi.updateNode(nodeId, nodeBodyUpdate, include, fields).getBody();
        LOGGER.info("Updated node permissions {}", result.getEntry());

        return result.getEntry();
    }
}

Note that you have to first get the currently set permissions before you set the new ones. This is because the update call will overwrite permissions already set. So we use the getNodeMetadata call to get already set node permissions.

Executing this code result in this for a text file example:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar update-permissions 0492460b-6269-4ca1-9668-0d934d2f3370

2021-04-30 09:32:02.206  INFO 21515 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.4 seconds (JVM running for 3.957)
2021-04-30 09:32:02.208  INFO 21515 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: update-permissions
2021-04-30 09:32:02.210  INFO 21515 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-04-30 09:32:02.509  INFO 21515 --- [           main] o.a.t.r.SetNodePermissionsMetadataCmd    : Got node including permissions class Node {
    id: 0492460b-6269-4ca1-9668-0d934d2f3370
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-29T15:27:42.528Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.143Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, cm:titled, cm:auditable, acme:securityClassified, cm:author, cm:thumbnailModification]
    properties: {cm:title=UPDATED title, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1619613896873, pdf:1619701086215], cm:description=UPDATED description}
    allowableOperations: null
    path: null
    permissions: class PermissionsInfo {
        isInheritanceEnabled: true
        inherited: [class PermissionElement {
            authorityId: GROUP_EVERYONE
            name: Consumer
            accessStatus: ALLOWED
        }, class PermissionElement {
            authorityId: guest
            name: Consumer
            accessStatus: ALLOWED
        }]
        locallySet: null
        settable: [Contributor, Collaborator, Coordinator, Editor, Consumer]
    }
    definition: null
}
2021-04-30 09:32:02.708  INFO 21515 --- [           main] o.a.t.r.SetNodePermissionsMetadataCmd    : Updated node permissions class Node {
    id: 0492460b-6269-4ca1-9668-0d934d2f3370
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-30T08:32:02.635Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.143Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, cm:titled, cm:auditable, acme:securityClassified, cm:author, cm:thumbnailModification]
    properties: {cm:title=UPDATED title, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1619613896873, pdf:1619701086215], cm:description=UPDATED description}
    allowableOperations: null
    path: null
    permissions: class PermissionsInfo {
        isInheritanceEnabled: true
        inherited: [class PermissionElement {
            authorityId: guest
            name: Consumer
            accessStatus: ALLOWED
        }, class PermissionElement {
            authorityId: GROUP_EVERYONE
            name: Consumer
            accessStatus: ALLOWED
        }]
        locallySet: [class PermissionElement {
            authorityId: GROUP_engineering
            name: Collaborator
            accessStatus: ALLOWED
        }, class PermissionElement {
            authorityId: tester
            name: Contributor
            accessStatus: ALLOWED
        }]
        settable: [Contributor, Collaborator, Coordinator, Editor, Consumer]
    }
    definition: null
}

We can see that before the permission update there were no locally set permissions for the node, only inherited. After the update we see also the locallySet returned with the newly set permissions. Note that for the permission information to be returned with each call we have to add permissions to the include parameter.

Working with relationships between folders/files

To manage relationships (referred to as associations) between nodes use the NodesApi and the following methods:

Method Description
listNodeChildren List primary parent-child associations, see list folder content
createSecondaryChildAssociation Create secondary parent-child association
deleteSecondaryChildAssociation Delete secondary parent-child association
listSecondaryChildren List secondary parent-child associations
createAssociation Create peer-2-peer association
deleteAssociation Delete peer-2-peer association(s)
listSourceAssociations List source peer-2-peer associations
listTargetAssociations List target peer-2-peer associations

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

Assuming we have deployed the FDK content model with secondary parent-child and peer-2-peer association types, then the following code examples shows how to create those types of associations (it also shows how to upload files, create folder, create node of different type):

Note. this code assumes the following two files exists in current directory: somepicture.png and sometext.txt.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.*;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.*;

@Component
public class ManageAssociationsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ManageAssociationsCmd.class);

    @Autowired
    NodesApi nodesApi;

    private Boolean autoRename = true;
    private Boolean majorVersion = true;
    private Boolean versioningEnabled = true;
    private String updateComment = null;
    private String updatedName = null;
    private List<String> include = null;
    private List<String> fields = null;
    private List<String> orderBy = null;
    private Integer skipCount = 0;
    private Integer maxItems = 100;
    private String where = null;
    private Boolean includeSource = false;

    public void execute() throws IOException {
        // List all folders and files (primary parent-child associations) in /Company Home/Data Dictionary
        NodeChildAssociationPagingList primaryChildAssociations =
                listPrimaryChildAssociations("-root-", "/Data Dictionary");

        // Create gadget folder, gadget image, and gadget review
        Node gadgetFolderNode = createFolder("My Gadgets", "");
        Node gadgetPictureNode = uploadFile(gadgetFolderNode.getId(), "gadget-picture.png", "somepicture.png");
        Node gadgetReviewNode = uploadFile(gadgetFolderNode.getId(), "gadget-review.txt", "sometext.txt");

        // Create the Gadget company node
        Map<String, Object> properties = new HashMap<>();
        properties.put("fdk:email", "info@coolgadgets.com");
        properties.put("fdk:url","www.coolgadgets.com");
        properties.put("fdk:city","London");
        Node companyNode = createNode(gadgetFolderNode.getId(), "Cool Gadgets Inc","fdk:company", properties);

        // Create a gadget node with associations using the FDK content model
        List<ChildAssociationBody> secondaryParentChildAssociations = new ArrayList<>();
        ChildAssociationBody childAssoc = new ChildAssociationBody();
        childAssoc.assocType("fdk:images");
        childAssoc.setChildId(gadgetPictureNode.getId());
        secondaryParentChildAssociations.add(childAssoc);
        List<AssociationBody> peer2peerAssociations = new ArrayList<>();
        AssociationBody peer2peerAssoc = new AssociationBody();
        peer2peerAssoc.assocType("fdk:reviews");
        peer2peerAssoc.setTargetId(gadgetReviewNode.getId());
        peer2peerAssociations.add(peer2peerAssoc);
        AssociationBody peer2peerAssoc2 = new AssociationBody();
        peer2peerAssoc2.assocType("fdk:company");
        peer2peerAssoc2.setTargetId(companyNode.getId());
        peer2peerAssociations.add(peer2peerAssoc2);
        Node gadgetNode = createNodeWithAssociations(
                gadgetFolderNode.getId(),"My Gadget", "fdk:gadget",
                secondaryParentChildAssociations, peer2peerAssociations);

        // List secondary parent-child associations for a node
        NodeChildAssociationPagingList secondaryAssoc = listSecondaryChildAssociations(gadgetNode.getId());

        // List peer-2-peer associations for a node
        NodeAssociationPagingList targetAssoc = listPeer2PeerAssociations(gadgetNode.getId());
    }

    /**
     * List primary parent-child associations. Basically list folder contents.
     *
     * @param rootNodeId         the id of the folder node that is the root. If relativeFolderPath is null, then content in this folder will be listed. Besides node ID the aliases -my-, -root- and -shared- are also supported.
     * @param relativeFolderPath path relative rootNodeId, if this is not null, then the content of this folder will be listed
     * @return a list of child node objects contained in the folder, or null if not found
     */
    private NodeChildAssociationPagingList listPrimaryChildAssociations(String rootNodeId, String relativeFolderPath) {
        LOGGER.info("Listing primary child associations for folder {}{}", rootNodeId, relativeFolderPath);
        NodeChildAssociationPagingList result = nodesApi.listNodeChildren(rootNodeId, skipCount, maxItems, orderBy, where, include,
                relativeFolderPath, includeSource, fields).getBody().getList();
        for (NodeChildAssociationEntry childNodeAssoc: result.getEntries()) {
            LOGGER.info("Found primary child [name=" + childNodeAssoc.getEntry().getName() + "]");
        }

        return result;
    }

    /**
     * List secondary parent-child associations.
     *
     * @param nodeId         the node to list assoc for
     * @return a list of child node objects contained in the node, or null if not found
     */
    private NodeChildAssociationPagingList listSecondaryChildAssociations(String nodeId) {
        LOGGER.info("Listing secondary child associations for node {}", nodeId);
        NodeChildAssociationPagingList result = nodesApi.listSecondaryChildren(
                nodeId, where, include, skipCount, maxItems, includeSource, fields).getBody().getList();
        for (NodeChildAssociationEntry childNodeAssoc: result.getEntries()) {
            LOGGER.info("Found secondary child [name=" + childNodeAssoc.getEntry().getName() + "]");
        }

        return result;
    }

    /**
     * List peer-2-peer associations.
     *
     * @param nodeId         the node to list assoc for
     * @return a list of assoc objects associated with the node
     */
    private NodeAssociationPagingList listPeer2PeerAssociations(String nodeId) {
        LOGGER.info("Listing peer-2-peer associations for node {}", nodeId);
        NodeAssociationPagingList result = nodesApi.listTargetAssociations(
                nodeId, where, include, fields).getBody().getList();
        for (NodeAssociationEntry targetAssoc: result.getEntries()) {
            LOGGER.info("Found target [name=" + targetAssoc.getEntry().getName() + "]");
        }

        return result;
    }

    /**
     * Create a node with associations.
     *
     * @param parentNodeId the parent node id
     * @param nodeName     the name of the node
     * @param nodeType     the type of the node
     * @param secondaryParentChildAssociations a list of secondary parent-child associations that should be set up
     * @param peer2peerAssociations a list of peer-2-peer associations that should be set up
     * @return a node object for the newly created node, contains the ID,
     * such as e859588c-ae81-4c5e-a3b6-4c6109b6c905
     */
    private Node createNodeWithAssociations(
            String parentNodeId,
            String nodeName,
            String nodeType,
            List<ChildAssociationBody> secondaryParentChildAssociations,
            List<AssociationBody> peer2peerAssociations) {
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(nodeName);
        nodeBodyCreate.setNodeType(nodeType);
        nodeBodyCreate.setSecondaryChildren(secondaryParentChildAssociations);
        nodeBodyCreate.setTargets(peer2peerAssociations);
        Node node = nodesApi.createNode(parentNodeId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled,
                include, fields).getBody().getEntry();
        LOGGER.info("Created new node with associations: {}", node);

        return node;
    }

    /**
     * Make the remote call to create a folder in the repository, if it does not exist.
     *
     * @param folderName         the name of the folder
     * @param relativeFolderPath path relative to /Company Home
     * @return a node object for the newly created node, contains the ID,
     * such as e859588c-ae81-4c5e-a3b6-4c6109b6c905
     */
    private Node createFolder(String folderName,
                              String relativeFolderPath) {
        String nodeId = "-root-";
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(folderName);
        nodeBodyCreate.setNodeType("cm:folder");
        nodeBodyCreate.setRelativePath(relativeFolderPath);
        Node folderNode = nodesApi.createNode(nodeId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled,
                include, fields).getBody().getEntry();
        LOGGER.info("Created new folder: {}", folderNode);

        return folderNode;
    }

    /**
     * Create a node
     *
     * @param parentNodeId  the node id for parent folder
     * @param nodeName      the name of the node
     * @param nodeType      the type of the node
     * @return a node object for the newly created node, contains the ID,
     * such as e859588c-ae81-4c5e-a3b6-4c6109b6c905
     */
    private Node createNode(String parentNodeId,
                            String nodeName,
                            String nodeType,
                            Map<String, Object> properties) {
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(nodeName);
        nodeBodyCreate.setNodeType(nodeType);
        nodeBodyCreate.setProperties(properties);
        Node node = nodesApi.createNode(
                parentNodeId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled, include, fields).getBody().getEntry();
        LOGGER.info("Created new node: {}", node);

        return node;
    }

    /**
     * Upload a file from disk
     */
    private Node uploadFile(String folderId, String fileName, String filePath) {
        // Create the file node metadata
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(fileName);
        nodeBodyCreate.setNodeType("cm:content");
        Node fileNode = nodesApi.createNode(
                folderId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled, include, fields).getBody().getEntry();

        // Get the file bytes
        File someFile = new File(filePath);
        byte[] fileData = null;
        try {
            fileData = FileUtils.readFileToByteArray(someFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Add the file node content
        Node updatedFileNode = nodesApi.updateNodeContent(fileNode.getId(),
                fileData, majorVersion, updateComment, updatedName, include, fields).getBody().getEntry();

        LOGGER.info("Created file with content: {}", updatedFileNode.toString());

        return updatedFileNode;
    }

}

Executing the above code will result in logs such as follows:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar manage-associations                                    

2021-04-30 16:26:22.678  INFO 22647 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 2.93 seconds (JVM running for 3.402)
2021-04-30 16:26:22.680  INFO 22647 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: manage-associations
2021-04-30 16:26:22.681  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Listing primary child associations for folder -root-/Data Dictionary
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Email Templates]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Imap Configs]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Messages]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Models]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Node Templates]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Presentation Templates]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Rendering Actions Space]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Replication Actions Space]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=RSS Templates]
2021-04-30 16:26:23.071  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Saved Searches]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Scheduled Actions]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Scripts]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Smart Folder Downloads]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Smart Folder Templates]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Solr Facets Space]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Space Templates]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Transfers]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Web Client Extension]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Web Scripts]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Web Scripts Extensions]
2021-04-30 16:26:23.072  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found primary child [name=Workflow Definitions]
2021-04-30 16:26:23.184  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Created new folder: class Node {
    id: e6bacba5-0dba-40af-afa0-ff25e10a18bb
    name: My Gadgets
    nodeType: cm:folder
    isFolder: true
    isFile: false
    isLocked: false
    modifiedAt: 2021-04-30T15:26:23.129Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-30T15:26:23.129Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: e439190c-3fe0-48a1-8a9a-374fbc54b570
    isLink: null
    isFavorite: null
    content: null
    aspectNames: [cm:auditable]
    properties: null
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-30 16:26:23.482  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Created file with content: class Node {
    id: b9bf8f12-269f-46a3-97a8-16900644a7d6
    name: gadget-picture.png
    nodeType: cm:content
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-30T15:26:23.404Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-30T15:26:23.218Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: e6bacba5-0dba-40af-afa0-ff25e10a18bb
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: image/png
        mimeTypeName: PNG Image
        sizeInBytes: 14799
        encoding: UTF-8
    }
    aspectNames: [cm:versionable, cm:auditable]
    properties: {cm:versionLabel=2.0, cm:versionType=MAJOR}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-30 16:26:23.716  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Created file with content: class Node {
    id: bb35fdd6-f2f3-44e4-84c9-30e48efaf3d5
    name: gadget-review.txt
    nodeType: cm:content
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-30T15:26:23.644Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-30T15:26:23.507Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: e6bacba5-0dba-40af-afa0-ff25e10a18bb
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [cm:versionable, cm:auditable]
    properties: {cm:versionLabel=2.0, cm:versionType=MAJOR}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-30 16:26:23.918  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Created new node: class Node {
    id: 01c5e298-a6c2-4b5c-81e0-195172626e22
    name: Cool Gadgets Inc
    nodeType: fdk:company
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-30T15:26:23.772Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-30T15:26:23.772Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: e6bacba5-0dba-40af-afa0-ff25e10a18bb
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: application/octet-stream
        mimeTypeName: Binary File (Octet Stream)
        sizeInBytes: 0
        encoding: UTF-8
    }
    aspectNames: [cm:versionable, cm:auditable]
    properties: {fdk:email=info@coolgadgets.com, fdk:url=www.coolgadgets.com, cm:versionType=MAJOR, cm:versionLabel=1.0, fdk:city=London}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-30 16:26:24.133  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Created new node with associations: class Node {
    id: c5f329e8-7872-4e92-abe1-e7dd5f5f48ba
    name: My Gadget
    nodeType: fdk:gadget
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-04-30T15:26:23.950Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-30T15:26:23.950Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: e6bacba5-0dba-40af-afa0-ff25e10a18bb
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: application/octet-stream
        mimeTypeName: Binary File (Octet Stream)
        sizeInBytes: 0
        encoding: UTF-8
    }
    aspectNames: [cm:versionable, cm:auditable]
    properties: {cm:versionLabel=1.0, cm:versionType=MAJOR}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-04-30 16:26:24.134  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Listing secondary child associations for node c5f329e8-7872-4e92-abe1-e7dd5f5f48ba
2021-04-30 16:26:24.156  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found secondary child [name=gadget-picture.png]
2021-04-30 16:26:24.157  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Listing peer-2-peer associations for node c5f329e8-7872-4e92-abe1-e7dd5f5f48ba
2021-04-30 16:26:24.239  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found target [name=gadget-review.txt]
2021-04-30 16:26:24.239  INFO 22647 --- [           main] o.a.t.restapi.ManageAssociationsCmd      : Found target [name=Cool Gadgets Inc]

To create associations for existing nodes use the createSecondaryChildAssociation and createAssociation methods.

Manage comments for a folder or file

To manage comments for a node, use the CommentsApi and the following methods:

Method Description
createComment Create a comment for a node
deleteComment Delete a comment for a node
listComments List comments for a node
updateComment Update a comment for a node

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.CommentsApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class ManageCommentsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ManageCommentsCmd.class);

    private Integer skipCount = 0;
    private Integer maxItems = 100;
    private List<String> fields = new ArrayList<>();

    // Fetch only the fields we are interested in
    public ManageCommentsCmd() {
        fields.add("content,createdAt");
    }

    @Autowired
    CommentsApi commentsApi;

    public void execute(String nodeId) throws IOException {
        Comment firstComment = createComment(nodeId, "First comment");
        Comment secondComment = createComment(nodeId, "Second comment");

        LOGGER.info("Listing comments: ");
        CommentPagingList comments = commentsApi.listComments(nodeId, skipCount, maxItems, fields).getBody().getList();
        for (CommentEntry commentEntry: comments.getEntries()) {
            LOGGER.info("    {}", commentEntry.getEntry());
        }
    }

    private Comment createComment(String nodeId, String text) {
        CommentBody commentBody = new CommentBody();
        commentBody.setContent(text);
        Comment comment = commentsApi.createComment(nodeId, commentBody, fields).getBody().getEntry();
        LOGGER.info("{}", comment);
        return comment;
    }
}

Note the use of the fields parameter to limit the number of fields returned with each call, which saves bandwidth.

Executing the code gives a log like follows:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar manage-comments 0492460b-6269-4ca1-9668-0d934d2f3370

2021-05-03 18:54:48.085  INFO 26804 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.075 seconds (JVM running for 3.55)
2021-05-03 18:54:48.087  INFO 26804 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: manage-comments
2021-05-03 18:54:48.088  INFO 26804 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-05-03 18:54:48.373  INFO 26804 --- [           main] o.a.tutorial.restapi.ManageCommentsCmd   : class Comment {
    id: null
    title: null
    content: First comment
    createdBy: null
    createdAt: 2021-05-03T17:54:48.224Z
    edited: null
    modifiedBy: null
    modifiedAt: null
    canEdit: null
    canDelete: null
}
2021-05-03 18:54:48.492  INFO 26804 --- [           main] o.a.tutorial.restapi.ManageCommentsCmd   : class Comment {
    id: null
    title: null
    content: Second comment
    createdBy: null
    createdAt: 2021-05-03T17:54:48.417Z
    edited: null
    modifiedBy: null
    modifiedAt: null
    canEdit: null
    canDelete: null
}
2021-05-03 18:54:48.492  INFO 26804 --- [           main] o.a.tutorial.restapi.ManageCommentsCmd   : Listing comments: 
2021-05-03 18:54:48.545  INFO 26804 --- [           main] o.a.tutorial.restapi.ManageCommentsCmd   :     class Comment {
    id: null
    title: null
    content: Second comment
    createdBy: null
    createdAt: 2021-05-03T17:54:48.417Z
    edited: null
    modifiedBy: null
    modifiedAt: null
    canEdit: null
    canDelete: null
}
2021-05-03 18:54:48.545  INFO 26804 --- [           main] o.a.tutorial.restapi.ManageCommentsCmd   :     class Comment {
    id: null
    title: null
    content: First comment
    createdBy: null
    createdAt: 2021-05-03T17:54:48.224Z
    edited: null
    modifiedBy: null
    modifiedAt: null
    canEdit: null
    canDelete: null
}

Manage tags for a folder or file

To manage tags for a node, use the TagsApi and the following methods:

Method Description
createTagForNode Create a tag for a node
deleteTagFromNode Delete a tag from a node
getTag Get a tag
listTags List tags
listTagsForNode List tags for a node
updateTag Update a tag

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.TagsApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class ManageTagsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ManageTagsCmd.class);

    private Integer skipCount = 0;
    private Integer maxItems = 100;
    private List<String> fields = null;
    private List<String> include = new ArrayList<>();

    // Include an extra field with tag count
    public ManageTagsCmd() {
        include.add("count");
    }

    @Autowired
    TagsApi tagsApi;

    public void execute(String nodeId) throws IOException {
        Tag firstTag = createTag(nodeId, "tag-one");
        Tag secondTag = createTag(nodeId, "tag-two");

        LOGGER.info("Listing tags for the whole repository: ");
        TagPagingList repoTags = tagsApi.listTags(skipCount, maxItems, fields, include).getBody().getList();
        for (TagEntry repoTagEntry: repoTags.getEntries()) {
            LOGGER.info("    {} count: {}", repoTagEntry.getEntry().getTag(), repoTagEntry.getEntry().getCount());
        }

        LOGGER.info("Listing tags for node: {}", nodeId);
        TagPagingList nodeTags = tagsApi.listTagsForNode(nodeId, skipCount, maxItems, fields).getBody().getList();
        for (TagEntry nodeTagEntry: nodeTags.getEntries()) {
            LOGGER.info("    {}", nodeTagEntry.getEntry());
        }
    }

    private Tag createTag(String nodeId, String text) {
        TagBody tagBody = new TagBody();
        tagBody.setTag(text);
        Tag tag = tagsApi.createTagForNode(nodeId, tagBody, fields).getBody().getEntry();
        LOGGER.info("Created Tag {}", tag);
        return tag;
    }
}

Executing the code gives a log like follows:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar manage-tags 0492460b-6269-4ca1-9668-0d934d2f3370

2021-05-04 09:56:25.846  INFO 27655 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 2.884 seconds (JVM running for 3.333)
2021-05-04 09:56:25.848  INFO 27655 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: manage-tags
2021-05-04 09:56:25.849  INFO 27655 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-05-04 09:56:26.073  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       : Created Tag class Tag {
    id: a6da6c4d-cb6b-41b5-a010-7188459dd3cb
    tag: tag-one
    count: null
}
2021-05-04 09:56:26.175  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       : Created Tag class Tag {
    id: 9a9044c9-3787-44ca-bd92-c6797c9a82ae
    tag: tag-two
    count: null
}
2021-05-04 09:56:26.175  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       : Listing tags for the whole repository: 
2021-05-04 09:56:26.288  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     activiti count: 3
2021-05-04 09:56:26.288  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     aps count: 1
2021-05-04 09:56:26.288  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     tag-one count: null
2021-05-04 09:56:26.288  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     tag-two count: null
2021-05-04 09:56:26.288  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     white-paper count: 2
2021-05-04 09:56:26.288  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       : Listing tags for node: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-05-04 09:56:26.310  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     class Tag {
    id: a6da6c4d-cb6b-41b5-a010-7188459dd3cb
    tag: tag-one
    count: null
}
2021-05-04 09:56:26.310  INFO 27655 --- [           main] o.a.tutorial.restapi.ManageTagsCmd       :     class Tag {
    id: 9a9044c9-3787-44ca-bd92-c6797c9a82ae
    tag: tag-two
    count: null
}

Note that the tag count are not available directly after you have created the tag. It has to be indexed first.

Copy folders and files

To copy a node, such as a file or folder, use the copyNode method of the NodesApi.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyCopy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class CopyNodeCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(CopyNodeCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String nodeId, String parentFolderNodeId) throws IOException {
        List<String> include = new ArrayList<>();
        List<String> fields = null;

        include.add("path"); // add extra path property in response so we can see location of node

        Node node = nodesApi.getNode(nodeId, include, null, fields).getBody().getEntry();
        LOGGER.info("Got node we want to copy ID: {} Parent: {} Location: {}",
                node.getId(), node.getParentId(), node.getPath().getName());

        NodeBodyCopy nodeBodyCopy = new NodeBodyCopy();
        nodeBodyCopy.setTargetParentId(parentFolderNodeId);
        Node copiedNode = nodesApi.copyNode(nodeId, nodeBodyCopy, include, fields).getBody().getEntry();
        LOGGER.info("Copied node ID: {} Parent: {} Location: {}",
                copiedNode.getId(), copiedNode.getParentId(), copiedNode.getPath().getName());
    }
}

Executing this code would give the following result, passing in node to copy and target folder node:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar copy-node 0492460b-6269-4ca1-9668-0d934d2f3370 7f041db0-fdb6-4185-b921-2fb9ed381480

2021-05-04 10:52:16.741  INFO 28353 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.667 seconds (JVM running for 4.218)
2021-05-04 10:52:16.743  INFO 28353 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: copy-node
2021-05-04 10:52:16.745  INFO 28353 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-05-04 10:52:16.745  INFO 28353 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 7f041db0-fdb6-4185-b921-2fb9ed381480
2021-05-04 10:52:16.974  INFO 28353 --- [           main] o.alfresco.tutorial.restapi.CopyNodeCmd  : Got node we want to copy ID: 0492460b-6269-4ca1-9668-0d934d2f3370 Parent: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445 Location: /Company Home/Guest Home
2021-05-04 10:52:17.366  INFO 28353 --- [           main] o.alfresco.tutorial.restapi.CopyNodeCmd  : Copied node ID: fe955da0-c4e5-42d3-972f-697424b546b1 Parent: 7f041db0-fdb6-4185-b921-2fb9ed381480 Location: /Company Home/Imap Attachments

Note the new node ID for the copy in the response.

Note that we set the include parameter to path so the location of the node is returned. The following extra information is returned:

Location: class PathInfo {
    elements: [class PathElement {
        id: e439190c-3fe0-48a1-8a9a-374fbc54b570
        name: Company Home
        nodeType: cm:folder
        aspectNames: [cm:titled, cm:auditable, app:uifacets]
    }, class PathElement {
        id: 7f041db0-fdb6-4185-b921-2fb9ed381480
        name: Imap Attachments
        nodeType: cm:folder
        aspectNames: [cm:titled, cm:auditable, app:uifacets]
    }]
    name: /Company Home/Imap Attachments
    isComplete: true
}

Move folders and files

To copy a node, such as a file or folder, use the moveNode method of the NodesApi.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyMove;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class MoveNodeCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(MoveNodeCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String nodeId, String parentFolderNodeId) throws IOException {
        List<String> include = new ArrayList<>();
        List<String> fields = null;

        include.add("path"); // add extra path property in response so we can see location of node

        Node node = nodesApi.getNode(nodeId, include, null, fields).getBody().getEntry();
        LOGGER.info("Got node before move ID: {} Parent: {} Location: {}",
                node.getId(), node.getParentId(), node.getPath().getName());

        NodeBodyMove nodeBodyMove = new NodeBodyMove();
        nodeBodyMove.setTargetParentId(parentFolderNodeId);
        Node movedNode = nodesApi.moveNode(nodeId, nodeBodyMove, include, fields).getBody().getEntry();
        LOGGER.info("Moved node ID: {} Parent: {} Location: {}",
                movedNode.getId(), movedNode.getParentId(), movedNode.getPath().getName());
    }
}

Executing this code would give the following result, passing in node to copy and target folder node:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar move-node d11d6970-c5c4-4edd-9971-593a23b9344f 7f041db0-fdb6-4185-b921-2fb9ed381480

2021-05-04 10:45:47.080  INFO 28288 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.103 seconds (JVM running for 3.568)
2021-05-04 10:45:47.081  INFO 28288 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: move-node
2021-05-04 10:45:47.082  INFO 28288 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: d11d6970-c5c4-4edd-9971-593a23b9344f
2021-05-04 10:45:47.082  INFO 28288 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 7f041db0-fdb6-4185-b921-2fb9ed381480
2021-05-04 10:45:47.276  INFO 28288 --- [           main] o.alfresco.tutorial.restapi.MoveNodeCmd  : Got node before move ID: d11d6970-c5c4-4edd-9971-593a23b9344f Parent: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445 Location: /Company Home/Guest Home
2021-05-04 10:45:47.458  INFO 28288 --- [           main] o.alfresco.tutorial.restapi.MoveNodeCmd  : Moved node ID: d11d6970-c5c4-4edd-9971-593a23b9344f Parent: 7f041db0-fdb6-4185-b921-2fb9ed381480 Location: /Company Home/Imap Attachments

Note that the node ID in the response is the same as passed in node ID, it is just the location of the node that has changed when moving it.

Note that we set the include parameter to path so the location of the node is returned. The following extra information is returned:

Location: class PathInfo {
    elements: [class PathElement {
        id: e439190c-3fe0-48a1-8a9a-374fbc54b570
        name: Company Home
        nodeType: cm:folder
        aspectNames: [cm:titled, cm:auditable, app:uifacets]
    }, class PathElement {
        id: 7f041db0-fdb6-4185-b921-2fb9ed381480
        name: Imap Attachments
        nodeType: cm:folder
        aspectNames: [cm:titled, cm:auditable, app:uifacets]
    }]
    name: /Company Home/Imap Attachments
    isComplete: true
}

Lock a file for editing

To lock a file for editing, use the lockNode method of the NodesApi. Use the unlockNode method when you are finished editing the node.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class LockNodeCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(LockNodeCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String nodeId) throws IOException {
        List<String> include = null;
        List<String> fields = null;

        Node node = nodesApi.getNode(nodeId, include, null, fields).getBody().getEntry();
        LOGGER.info("Got node we want to lock ID: {} Is locked ?: {}", node.getId(), node.isIsLocked());

        if (!node.isIsLocked()) {
            // Lock the file with exclusive lock
            NodeBodyLock nodeBodyLock = new NodeBodyLock();
            Node lockedNode = nodesApi.lockNode(nodeId, nodeBodyLock, include, fields).getBody().getEntry();
            LOGGER.info("Locked node: {}", lockedNode);

            // Do the work on the file

            // Unlock the file
            Node unLockedNode = nodesApi.unlockNode(nodeId, include, fields).getBody().getEntry();
            LOGGER.info("Unlocked node: {}", unLockedNode);

        }
    }
}

Executing this code would give the following result, passing in node to lock:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar lock-node 0492460b-6269-4ca1-9668-0d934d2f3370                                 

2021-05-04 11:12:51.070  INFO 28630 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.722 seconds (JVM running for 4.523)
2021-05-04 11:12:51.072  INFO 28630 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: lock-node
2021-05-04 11:12:51.074  INFO 28630 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 0492460b-6269-4ca1-9668-0d934d2f3370
2021-05-04 11:12:51.285  INFO 28630 --- [           main] o.alfresco.tutorial.restapi.LockNodeCmd  : Got node we want to lock ID: 0492460b-6269-4ca1-9668-0d934d2f3370 Is locked ?: false
2021-05-04 11:12:51.671  INFO 28630 --- [           main] o.alfresco.tutorial.restapi.LockNodeCmd  : Locked node: class Node {
    id: 0492460b-6269-4ca1-9668-0d934d2f3370
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-05-04T08:56:26.135Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.143Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, acme:securityClassified, cm:taggable, cm:thumbnailModification, fm:discussable, cm:titled, cm:lockable, cm:auditable, fm:commentsRollup, cm:author]
    properties: {cm:lockType=WRITE_LOCK, cm:title=UPDATED title, cm:lockOwner={id=admin, displayName=Administrator}, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, cm:lockLifetime=PERSISTENT, fm:commentCount=2, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1619613896873, pdf:1619701086215], cm:description=UPDATED description, cm:taggable=[a6da6c4d-cb6b-41b5-a010-7188459dd3cb, 9a9044c9-3787-44ca-bd92-c6797c9a82ae]}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}
2021-05-04 11:12:51.743  INFO 28630 --- [           main] o.alfresco.tutorial.restapi.LockNodeCmd  : Unlocked node: class Node {
    id: 0492460b-6269-4ca1-9668-0d934d2f3370
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-05-04T08:56:26.135Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-28T12:02:33.143Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 8fa4e27d-35aa-411d-8bbe-831b6ed0c445
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, acme:securityClassified, cm:taggable, cm:thumbnailModification, fm:discussable, cm:titled, cm:auditable, fm:commentsRollup, cm:author]
    properties: {cm:title=UPDATED title, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=3.0, fm:commentCount=2, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1619613896873, pdf:1619701086215], cm:description=UPDATED description, cm:taggable=[a6da6c4d-cb6b-41b5-a010-7188459dd3cb, 9a9044c9-3787-44ca-bd92-c6797c9a82ae]}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

Note that the lockNode call response contains some extra parameters with lock information, such as cm:lockType=WRITE_LOCK and cm:lockOwner={id=admin, displayName=Administrator}.

To create a link to a file, use the createNode method of the NodesApi and create a node of the type app:filelink.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.model.Node;
import org.alfresco.core.model.NodeBodyCreate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;

@Component
public class LinkFileCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(LinkFileCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String parentFolderNodeId, String linkToNodeId) throws IOException {
        Map<String, String> linkProps = new HashMap<>();
        linkProps.put("cm:destination", linkToNodeId); // Link points to this file node

        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName("Link to a text file");
        nodeBodyCreate.setNodeType("app:filelink"); // Out-of-the-box content model type for a file link
        nodeBodyCreate.setProperties(linkProps);

        Boolean autoRename = true;
        List<String> include = new ArrayList<>();
        List<String> fields = null;
        Boolean majorVersion = true;
        Boolean versioningEnabled = true;

        // Include the isLink property in the response so we can see if a node is a link
        include.add("isLink");

        Node fileLinkNode = nodesApi.createNode(parentFolderNodeId, nodeBodyCreate, autoRename, majorVersion,
                versioningEnabled, include, fields).getBody().getEntry();
        LOGGER.info("File link: {}", fileLinkNode);
    }
}

Executing this code would give the following result, passing in parent folder and node to link:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar link-file 7f041db0-fdb6-4185-b921-2fb9ed381480 48413f7a-066d-4e38-b2e6-c84ede635493

2021-05-04 13:27:43.981  INFO 29404 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 4.237 seconds (JVM running for 4.904)
2021-05-04 13:27:43.983  INFO 29404 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: link-file
2021-05-04 13:27:43.985  INFO 29404 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 7f041db0-fdb6-4185-b921-2fb9ed381480
2021-05-04 13:27:43.985  INFO 29404 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 48413f7a-066d-4e38-b2e6-c84ede635493
2021-05-04 13:27:44.329  INFO 29404 --- [           main] o.alfresco.tutorial.restapi.LinkFileCmd  : File link: class Node {
    id: c4ab808f-f42b-42a8-b308-d5d82df29830
    name: Link to a text file
    nodeType: app:filelink
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-05-04T12:27:44.166Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-05-04T12:27:44.166Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 7f041db0-fdb6-4185-b921-2fb9ed381480
    isLink: true
    isFavorite: null
    content: null
    aspectNames: [cm:auditable]
    properties: {cm:destination=48413f7a-066d-4e38-b2e6-c84ede635493}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

Note that the include parameter has been populated with the isLink value, which means the response will contain a value for the isLink property.

Delete a folder or file

To delete a folder or a file node, use the deleteNode method of the NodesApi.

More info about this ReST API endpoint

import org.alfresco.core.handler.NodesApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class DeleteNodeCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(DeleteNodeCmd.class);

    @Autowired
    NodesApi nodesApi;

    public void execute(String nodeId) throws IOException {
        // If true, then the node is deleted permanently, without moving to the trashcan.
        // Only the owner of the node or an admin can permanently delete the node.
        // default value = false
        Boolean permanent = false;

        ResponseEntity<Void> deletedNodeResponse = nodesApi.deleteNode(nodeId, permanent);
        LOGGER.info("Deleted node response: {}", deletedNodeResponse);
    }
}

Executing this code would give the following result, passing in the node to delete:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar delete-node fe955da0-c4e5-42d3-972f-697424b546b1                                   

2021-05-04 13:47:10.376  INFO 29542 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.639 seconds (JVM running for 4.406)
2021-05-04 13:47:10.377  INFO 29542 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: delete-node
2021-05-04 13:47:10.379  INFO 29542 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: fe955da0-c4e5-42d3-972f-697424b546b1
2021-05-04 13:47:10.787  INFO 29542 --- [           main] o.a.tutorial.restapi.DeleteNodeCmd       : Deleted node response: 
  <204 NO_CONTENT No Content,[cache-control:"no-cache", connection:"keep-alive", content-type:"application/json;charset=UTF-8", date:"Tue, 04 May 2021 12:47:10 GMT", expires:"Thu, 01 Jan 1970 00:00:00 GMT", pragma:"no-cache", server:"nginx/1.18.0", x-frame-options:"SAMEORIGIN"]>

List deleted folders and files (Trashcan)

To list deleted nodes, use the listDeletedNodes method of the TrashcanApi.

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.TrashcanApi;
import org.alfresco.core.model.DeletedNodeEntry;
import org.alfresco.core.model.DeletedNodesPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class ListDeletedNodesCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListDeletedNodesCmd.class);

    @Autowired
    TrashcanApi trashcanApi;

    public void execute() throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> include = new ArrayList<>();
        include.add("path");

        LOGGER.info("Listing soft deleted nodes in the trashcan:");
        DeletedNodesPaging deletedNodes = trashcanApi.listDeletedNodes(skipCount, maxItems, include).getBody();
        for (DeletedNodeEntry deletedNodeEntry: deletedNodes.getList().getEntries()) {
            LOGGER.info("    Deleted node: {}", deletedNodeEntry.getEntry());
        }
    }
}

Executing this code would list the soft deleted nodes that exist in the so called “Trashcan”:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-deleted-nodes                                       

2021-05-05 09:38:54.983  INFO 14986 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 4.404 seconds (JVM running for 4.861)
2021-05-05 09:38:54.985  INFO 14986 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-deleted-nodes
2021-05-05 09:38:54.986  INFO 14986 --- [           main] o.a.t.restapi.ListDeletedNodesCmd        : Listing soft deleted nodes in the trashcan:
2021-05-05 09:38:55.333  INFO 14986 --- [           main] o.a.t.restapi.ListDeletedNodesCmd        :     Deleted node: class DeletedNode {
    id: d32e1b4b-2ae0-48c2-9ee7-6323f8f4e96b
    name: My Gadgets
    nodeType: cm:folder
    isFolder: true
    isFile: false
    isLocked: false
    modifiedAt: 2021-04-30T15:46:17.334Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-04-30T15:46:16.332Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: null
    isLink: null
    isFavorite: null
    content: null
    aspectNames: null
    properties: null
    allowableOperations: null
    path: class PathInfo {
        elements: [class PathElement {
            id: e439190c-3fe0-48a1-8a9a-374fbc54b570
            name: Company Home
            nodeType: cm:folder
            aspectNames: [cm:titled, cm:auditable, app:uifacets]
        }]
        name: /Company Home
        isComplete: true
    }
    permissions: null
    definition: null
    archivedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    archivedAt: 2021-05-05T08:36:11.141Z
}
2021-05-05 09:38:55.333  INFO 14986 --- [           main] o.a.t.restapi.ListDeletedNodesCmd        :     Deleted node: class DeletedNode {
    id: fe955da0-c4e5-42d3-972f-697424b546b1
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-05-04T09:52:17.053Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-05-04T09:52:17.053Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: null
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: null
    properties: null
    allowableOperations: null
    path: class PathInfo {
        elements: [class PathElement {
            id: e439190c-3fe0-48a1-8a9a-374fbc54b570
            name: Company Home
            nodeType: cm:folder
            aspectNames: [cm:titled, cm:auditable, app:uifacets]
        }, class PathElement {
            id: 7f041db0-fdb6-4185-b921-2fb9ed381480
            name: Imap Attachments
            nodeType: cm:folder
            aspectNames: [cm:titled, cm:auditable, app:uifacets]
        }]
        name: /Company Home/Imap Attachments
        isComplete: true
    }
    permissions: null
    definition: null
    archivedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    archivedAt: 2021-05-04T12:47:10.524Z
}

Note the extra properties at the end that tells you when the node was soft deleted and by who (i.e. archivedAt and archivedByUser). Also, by setting the include parameter to path we get information about where the node was located before it was deleted (i.e. path.name)

Restore deleted folders and files (Trashcan)

To list deleted nodes, use the restoreDeletedNode method of the TrashcanApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.TrashcanApi;
import org.alfresco.core.model.DeletedNodeBodyRestore;
import org.alfresco.core.model.NodeEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class RestoreDeletedNodeCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(RestoreDeletedNodeCmd.class);

    @Autowired
    TrashcanApi trashcanApi;

    public void execute(String nodeId, String restoreFolderId) throws IOException {
        List<String> fields = null;

        // POST body need to ne supplied with target folder ID
        DeletedNodeBodyRestore deletedNodeBodyRestore = new DeletedNodeBodyRestore();
        deletedNodeBodyRestore.setTargetParentId(restoreFolderId);
        deletedNodeBodyRestore.setAssocType("cm:contains");
        NodeEntry restoredNode = trashcanApi.restoreDeletedNode(nodeId, fields, deletedNodeBodyRestore).getBody();
        LOGGER.info("Restored node: {}", restoredNode.getEntry());
    }
}

Executing this code would restore a node with passed in ID. The location folder ID is also passed in:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar restore-deleted-node b717304b-1c07-400b-b8a8-3268ea79c49f 7f041db0-fdb6-4185-b921-2fb9ed381480

2021-05-05 09:46:19.805  INFO 15098 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.033 seconds (JVM running for 3.498)
2021-05-05 09:46:19.807  INFO 15098 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: restore-deleted-node
2021-05-05 09:46:19.808  INFO 15098 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: b717304b-1c07-400b-b8a8-3268ea79c49f
2021-05-05 09:46:19.808  INFO 15098 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 7f041db0-fdb6-4185-b921-2fb9ed381480
2021-05-05 09:46:20.436  INFO 15098 --- [           main] o.a.t.restapi.RestoreDeletedNodeCmd      : Restored node: class Node {
    id: b717304b-1c07-400b-b8a8-3268ea79c49f
    name: newname.txt
    nodeType: acme:document
    isFolder: false
    isFile: true
    isLocked: false
    modifiedAt: 2021-05-04T09:19:49.903Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-05-04T09:19:49.903Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 7f041db0-fdb6-4185-b921-2fb9ed381480
    isLink: null
    isFavorite: null
    content: class ContentInfo {
        mimeType: text/plain
        mimeTypeName: Plain Text
        sizeInBytes: 30
        encoding: ISO-8859-1
    }
    aspectNames: [rn:renditioned, cm:versionable, cm:titled, cm:auditable, acme:securityClassified, cm:taggable, cm:author, cm:thumbnailModification]
    properties: {cm:title=UPDATED title, cm:versionType=MAJOR, acme:documentId=DOC-001, cm:versionLabel=1.0, acme:securityClassification=Company Confidential, cm:lastThumbnailModification=[doclib:1620120715749], cm:description=UPDATED description, cm:taggable=[a6da6c4d-cb6b-41b5-a010-7188459dd3cb, 9a9044c9-3787-44ca-bd92-c6797c9a82ae]}
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

=== Managing Sites ===

The following sections walk through how to use the Java ReST API wrapper services when managing Alfresco Share sites.

Create a site

To create an Alfresco Share site in the repository use the createSite method of the SitesApi, which is the main API used to create and manage sites.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.SitesApi;
import org.alfresco.core.model.Site;
import org.alfresco.core.model.SiteBodyCreate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class CreateSite {
    static final Logger LOGGER = LoggerFactory.getLogger(CreateSite.class);

    private List<String> fields = null;

    // Flag to indicate whether the Share-specific (surf) configuration files for the site should not be created
    // Default = false
    Boolean skipConfiguration = null;
    // Flag to indicate whether the site should not be added to the user's site favorites
    // Default = false
    Boolean skipAddToFavorites = null;

    @Autowired
    SitesApi sitesApi;

    public void execute(String siteId) throws IOException {
        SiteBodyCreate siteBodyCreate = new SiteBodyCreate();
        siteBodyCreate.setId(siteId);
        siteBodyCreate.setTitle("title-" + siteId);
        siteBodyCreate.setDescription("description-" + siteId);
        siteBodyCreate.setVisibility(SiteBodyCreate.VisibilityEnum.PUBLIC);

        Site site = sitesApi.createSite(siteBodyCreate, skipConfiguration, skipAddToFavorites, fields).getBody().getEntry();

        LOGGER.info("Created site: {}", site);
    }
}

List site containers

For more information about how to list site containers, such as Document Library, for a site see add content to site.

Add content to a site

Adding content to a site uses the same API calls as are used to create folders and upload files elsewhere in the Repository. See create folder and upload file for more information.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

The tricky bit is to figure out how to add content to the so called “Document Library” of a site. We can figure out the Node ID for the Document Library by using the listSiteContainers method of the SitesApi:

import org.alfresco.core.handler.NodesApi;
import org.alfresco.core.handler.SitesApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class AddSiteContentCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(AddSiteContentCmd.class);

    Integer skipCount = 0;
    Integer maxItems = 100;
    private List<String> fields = null;
    private List<String> include = null;
    private Boolean autoRename = true;
    private Boolean majorVersion = true;
    private Boolean versioningEnabled = true;

    @Autowired
    SitesApi sitesApi;

    @Autowired
    NodesApi nodesApi;

    public void execute(String siteId) throws IOException {
        // First get the Node ID for the Document Library
        String docLibNodeId = null;
        SiteContainerPaging siteContainerPaging = sitesApi.listSiteContainers(siteId, skipCount, maxItems, fields).getBody();
        LOGGER.info("Listing site containers [{}]: ", siteId);
        for (SiteContainerEntry siteContainerEntry: siteContainerPaging.getList().getEntries()) {
            SiteContainer siteContainer = siteContainerEntry.getEntry();
            LOGGER.info("  Site container: {}", siteContainer);
            if (siteContainer.getFolderId().equalsIgnoreCase("DocumentLibrary")) {
                docLibNodeId = siteContainer.getId();
            }
        }

        if (docLibNodeId != null) {
            // Create a folder in the document library
            createFolder(docLibNodeId, "White papers");
        } else {
            LOGGER.info("Document library not found in site {}", siteId);
        }
    }

    /**
     * Make the remote call to create a folder in the repository, if it does not exist.
     *
     * @param parentFolderId the node ID for the site container
     * @param folderName         the name of the folder
     * @return a node object for the newly created node, contains the ID,
     * such as e859588c-ae81-4c5e-a3b6-4c6109b6c905
     */
    private Node createFolder(String parentFolderId,
                              String folderName) {
        NodeBodyCreate nodeBodyCreate = new NodeBodyCreate();
        nodeBodyCreate.setName(folderName);
        nodeBodyCreate.setNodeType("cm:folder");
        Node folderNode = nodesApi.createNode(parentFolderId, nodeBodyCreate, autoRename, majorVersion, versioningEnabled,
                include, fields).getBody().getEntry();
        LOGGER.info("Created new folder in DocLib: {}", folderNode);

        return folderNode;
    }
}

Executing this code will create a folder in the passed in site’s document library:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar add-site-content test

2021-05-05 10:43:34.208  INFO 16095 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.019 seconds (JVM running for 3.46)
2021-05-05 10:43:34.210  INFO 16095 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: add-site-content
2021-05-05 10:43:34.211  INFO 16095 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: test
2021-05-05 10:43:34.390  INFO 16095 --- [           main] o.a.tutorial.restapi.AddSiteContentCmd   : Listing site containers [test]: 
2021-05-05 10:43:34.391  INFO 16095 --- [           main] o.a.tutorial.restapi.AddSiteContentCmd   :   Site container: class SiteContainer {
    id: 605e085c-92ae-4a53-b902-99c7d215f475
    folderId: documentLibrary
}
2021-05-05 10:43:34.833  INFO 16095 --- [           main] o.a.tutorial.restapi.AddSiteContentCmd   : Created new folder in DocLib: class Node {
    id: 6e157336-068a-4384-bc29-e4e1ca09cc6c
    name: White papers
    nodeType: cm:folder
    isFolder: true
    isFile: false
    isLocked: false
    modifiedAt: 2021-05-05T09:43:34.660Z
    modifiedByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    createdAt: 2021-05-05T09:43:34.660Z
    createdByUser: class UserInfo {
        displayName: Administrator
        id: admin
    }
    parentId: 605e085c-92ae-4a53-b902-99c7d215f475
    isLink: null
    isFavorite: null
    content: null
    aspectNames: [cm:auditable]
    properties: null
    allowableOperations: null
    path: null
    permissions: null
    definition: null
}

Adding members to a site

Adding members to a site uses the createSiteMembership method of the SitesApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.SitesApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

import static org.alfresco.core.model.SiteMembershipBodyCreate.RoleEnum.SITECOLLABORATOR;

@Component
public class AddSiteMembersCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(AddSiteMembersCmd.class);

    private List<String> fields = null;

    @Autowired
    SitesApi sitesApi;

    public void execute(String siteId, String personId) throws IOException {
        SiteMembershipBodyCreate siteMembershipBodyCreate = new SiteMembershipBodyCreate();
        siteMembershipBodyCreate.setId(personId);
        siteMembershipBodyCreate.setRole(SITECOLLABORATOR);
        SiteMemberEntry siteMemberEntry = sitesApi.createSiteMembership(siteId, siteMembershipBodyCreate, fields).getBody();
        LOGGER.info("Created site membership {}", siteMemberEntry);
    }
}

Executing this code will add a user with passed in ID with role Site Collaborator to site with passed in ID:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar add-site-member test test

2021-05-05 13:21:31.290  INFO 17933 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 2.923 seconds (JVM running for 3.377)
2021-05-05 13:21:31.291  INFO 17933 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: add-site-member
2021-05-05 13:21:31.292  INFO 17933 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: test
2021-05-05 13:21:31.293  INFO 17933 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: test
2021-05-05 13:21:31.697  INFO 17933 --- [           main] o.a.tutorial.restapi.AddSiteMembersCmd   : Created site membership class SiteMemberEntry {
    entry: class SiteMember {
        id: test
        person: class Person {
            id: test
            firstName: Test
            lastName: User
            displayName: Test User
            description: null
            avatarId: null
            email: test@example.com
            skypeId: null
            googleId: null
            instantMessageId: null
            jobTitle: null
            location: null
            company: class Company {
                organization: null
                address1: null
                address2: null
                address3: null
                postcode: null
                telephone: null
                fax: null
                email: null
            }
            mobile: null
            telephone: null
            statusUpdatedAt: null
            userStatus: null
            enabled: true
            emailNotificationsEnabled: true
            aspectNames: null
            properties: null
            capabilities: class Capabilities {
                isAdmin: false
                isGuest: false
                isMutable: true
            }
        }
        role: SiteCollaborator
        isMemberOfGroup: false
    }
}

=== Managing People and Groups ===

The following sections walk through how to use the Java ReST API wrapper services when managing users and groups. Note that these are usually managed via a directory server (LDAP/Active Directory).

List people (users)

Listing people uses the listPeople method of the PeopleApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.PeopleApi;
import org.alfresco.core.model.PersonEntry;
import org.alfresco.core.model.PersonPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListPeopleCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListPeopleCmd.class);

    @Autowired
    PeopleApi peopleApi;

    public void execute() throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> orderBy = null;
        List<String> include = null;
        List<String> fields = null;

        LOGGER.info("Listing people in the repository");
        PersonPaging people = peopleApi.listPeople(skipCount, maxItems, orderBy, include, fields).getBody();
        for (PersonEntry personEntry: people.getList().getEntries()) {
            LOGGER.info("  {} ({})", personEntry.getEntry().getDisplayName(), personEntry.getEntry().getId());
        }
    }
}

Executing this code will list all users in the repository (note, if connected to LDAP this could be a lot of users…):

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-people

2021-05-05 13:42:15.547  INFO 18327 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.426 seconds (JVM running for 3.96)
2021-05-05 13:42:15.549  INFO 18327 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-people
2021-05-05 13:42:15.550  INFO 18327 --- [           main] o.a.tutorial.restapi.ListPeopleCmd       : Listing people in the repository
2021-05-05 13:42:15.879  INFO 18327 --- [           main] o.a.tutorial.restapi.ListPeopleCmd       :   Alice Beecher (abeecher)
2021-05-05 13:42:15.880  INFO 18327 --- [           main] o.a.tutorial.restapi.ListPeopleCmd       :   Administrator (admin)
2021-05-05 13:42:15.880  INFO 18327 --- [           main] o.a.tutorial.restapi.ListPeopleCmd       :   Guest (guest)
2021-05-05 13:42:15.880  INFO 18327 --- [           main] o.a.tutorial.restapi.ListPeopleCmd       :   Mike Jackson (mjackson)
2021-05-05 13:42:15.880  INFO 18327 --- [           main] o.a.tutorial.restapi.ListPeopleCmd       :   Test User (test)

Create a person

Creating a person uses the createPerson method of the PeopleApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.PeopleApi;
import org.alfresco.core.model.PersonBodyCreate;
import org.alfresco.core.model.PersonEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class CreatePersonCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(CreatePersonCmd.class);

    @Autowired
    PeopleApi peopleApi;

    public void execute(String username, String pwd, String firstname, String lastname, String email) throws IOException {
        List<String> fields = null;

        PersonBodyCreate personBodyCreate = new PersonBodyCreate();
        personBodyCreate.setId(username);
        personBodyCreate.setPassword(pwd);
        personBodyCreate.setFirstName(firstname);
        personBodyCreate.setLastName(lastname);
        personBodyCreate.setEmail(email);
        PersonEntry person = peopleApi.createPerson(personBodyCreate, fields).getBody();
        LOGGER.info("Created person  {}", person);
    }
}

Executing this code will add a user passed in username, pwd, first name, last name and email:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-person martin 1234 Martin Bergljung martin@example.com

2021-05-05 15:49:25.198  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.928 seconds (JVM running for 4.427)
2021-05-05 15:49:25.200  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: create-person
2021-05-05 15:49:25.201  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: martin
2021-05-05 15:49:25.201  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 1234
2021-05-05 15:49:25.201  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[3]: Martin
2021-05-05 15:49:25.201  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[4]: Bergljung
2021-05-05 15:49:25.201  INFO 22389 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[5]: martin@example.com
2021-05-05 15:49:25.830  INFO 22389 --- [           main] o.a.tutorial.restapi.CreatePersonCmd     : Created person  class PersonEntry {
    entry: class Person {
        id: martin
        firstName: Martin
        lastName: Bergljung
        displayName: Martin Bergljung
        description: null
        avatarId: null
        email: martin@example.com
        skypeId: null
        googleId: null
        instantMessageId: null
        jobTitle: null
        location: null
        company: class Company {
            organization: null
            address1: null
            address2: null
            address3: null
            postcode: null
            telephone: null
            fax: null
            email: null
        }
        mobile: null
        telephone: null
        statusUpdatedAt: null
        userStatus: null
        enabled: true
        emailNotificationsEnabled: true
        aspectNames: null
        properties: null
        capabilities: class Capabilities {
            isAdmin: false
            isGuest: false
            isMutable: true
        }
    }
}

Get person metadata

Getting metadata for a person involves a number of API calls:

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.PeopleApi;
import org.alfresco.core.handler.PreferencesApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class GetPersonMetadataCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetPersonMetadataCmd.class);

    @Autowired
    PeopleApi peopleApi;

    @Autowired
    PreferencesApi preferencesApi;

    public void execute(String personId) throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> fields = null;

        PersonEntry person = peopleApi.getPerson(personId, fields).getBody();
        LOGGER.info("Got person metadata {}", person);
        PreferencePaging preferencePaging = preferencesApi.listPreferences(personId, skipCount, maxItems, fields).getBody();
        LOGGER.info("Got person preferences:");
        for (PreferenceEntry preferenceEntry: preferencePaging.getList().getEntries()) {
            LOGGER.info("  preference: {}", preferenceEntry.getEntry());
        }
    }
}

Executing this code will list the metadata for the user including any preferences. In the following example we list metadata for the out-of-the-box user abeecher:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar get-person-metadata abeecher

2021-05-05 16:06:50.550  INFO 22610 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.396 seconds (JVM running for 3.893)
2021-05-05 16:06:50.552  INFO 22610 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: get-person-metadata
2021-05-05 16:06:50.553  INFO 22610 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: abeecher
2021-05-05 16:06:50.787  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       : Got person metadata class PersonEntry {
    entry: class Person {
        id: abeecher
        firstName: Alice
        lastName: Beecher
        displayName: Alice Beecher
        description: Alice is a demo user for the sample Alfresco Team site.
        avatarId: 198500fc-1e99-4f5f-8926-248cea433366
        email: abeecher@example.com
        skypeId: abeecher
        googleId: null
        instantMessageId: null
        jobTitle: Graphic Designer
        location: Tilbury, UK
        company: class Company {
            organization: Moresby, Garland and Wedge
            address1: 200 Butterwick Street
            address2: Tilbury
            address3: UK
            postcode: ALF1 SAM1
            telephone: null
            fax: null
            email: null
        }
        mobile: 0112211001100
        telephone: 0112211001100
        statusUpdatedAt: 2011-02-15T20:20:13.432Z
        userStatus: Helping to design the look and feel of the new web site
        enabled: false
        emailNotificationsEnabled: true
        aspectNames: null
        properties: null
        capabilities: class Capabilities {
            isAdmin: false
            isGuest: false
            isMutable: true
        }
    }
}
2021-05-05 16:06:50.849  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       : Got person preferences:
2021-05-05 16:06:50.849  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.documentList.showFolders
    value: true
}
2021-05-05 16:06:50.849  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.documentList.simpleView
    value: false
}
2021-05-05 16:06:50.849  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.documentList.sortField
    value: cm:name
}
2021-05-05 16:06:50.849  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.documents.favourites
    value: workspace://SpacesStore/7c7bca1d-b65d-4444-9378-805b459fb74d,workspace://SpacesStore/b2f21ddd-0b0e-449f-bea9-a0be73e7d67b,workspace://SpacesStore/2cf35860-6705-42c3-b123-c4d6b39997b4,workspace://SpacesStore/7d90c94c-fcf7-4f79-9273-bd1352bbb612,workspace://SpacesStore/05dedd34-9d9d-48d9-9af6-c81b555541c9
}
2021-05-05 16:06:50.850  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.sites.favourites.test
    value: true
}
2021-05-05 16:06:50.850  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.sites.recent._0
    value: swsdp
}
2021-05-05 16:06:50.850  INFO 22610 --- [           main] o.a.t.restapi.GetPersonMetadataCmd       :   preference: class Preference {
    id: org.alfresco.share.twisters.collapsed
    value: DocumentPermissions,DocumentWorkflows,DocumentLinks,DocumentActions
}

Update a person

Updating metadata for a person involves these two API calls:

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.PeopleApi;
import org.alfresco.core.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class UpdatePersonMetadataCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(UpdatePersonMetadataCmd.class);

    @Autowired
    PeopleApi peopleApi;

    public void execute(String personId) throws IOException {
        List<String> fields = null;

        PersonBodyUpdate personBodyUpdate = new PersonBodyUpdate();
        // Mandatory fields during an update
        personBodyUpdate.setFirstName("Martin");
        personBodyUpdate.setLastName("Bergljung");
        personBodyUpdate.setEmail("martin@example.com");
        personBodyUpdate.setEmailNotificationsEnabled(true);
        personBodyUpdate.setOldPassword("1234");
        personBodyUpdate.setPassword("1234");
        personBodyUpdate.setEnabled(true);

        // Other fields
        personBodyUpdate.setJobTitle("Techie");
        Company company = new Company();
        company.setAddress1("Alfresco way 1");
        company.setOrganization("Alfresco Org");
        company.setTelephone("12345678");
        personBodyUpdate.setCompany(company);

        PersonEntry person = peopleApi.updatePerson(personId, personBodyUpdate, fields).getBody();
        LOGGER.info("Updated person metadata {}", person);
    }
}

Executing this code will update the user martin with some new company information, there are a number of fields that are mandatory that you need to set, so might be best to read them first and then set them:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar update-person-metadata martin

2021-05-06 09:16:41.833  INFO 24158 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.151 seconds (JVM running for 3.596)
2021-05-06 09:16:41.835  INFO 24158 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: update-person-metadata
2021-05-06 09:16:41.837  INFO 24158 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: martin
2021-05-06 09:16:42.111  INFO 24158 --- [           main] o.a.t.restapi.UpdatePersonMetadataCmd    : Updated person metadata class PersonEntry {
    entry: class Person {
        id: martin
        firstName: Martin
        lastName: Bergljung
        displayName: Martin Bergljung
        description: null
        avatarId: null
        email: martin@example.com
        skypeId: null
        googleId: null
        instantMessageId: null
        jobTitle: Techie
        location: null
        company: class Company {
            organization: Alfresco Org
            address1: Alfresco way 1
            address2: null
            address3: null
            postcode: null
            telephone: 12345678
            fax: null
            email: null
        }
        mobile: null
        telephone: null
        statusUpdatedAt: null
        userStatus: null
        enabled: true
        emailNotificationsEnabled: true
        aspectNames: null
        properties: null
        capabilities: class Capabilities {
            isAdmin: false
            isGuest: false
            isMutable: true
        }
    }
}

Request password reset for a person

Requesting a password reset for a person (user) in the repository involves these two API calls:

More info about this ReST API endpoint

import org.alfresco.core.handler.PeopleApi;
import org.alfresco.core.model.ClientBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class RequestPwdResetCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(RequestPwdResetCmd.class);

    @Autowired
    PeopleApi peopleApi;

    public void execute(String personId) throws IOException {
        ClientBody clientBody = new ClientBody();
        clientBody.setClient("share"); // Alfresco Share UI client

        HttpEntity<Void> result = peopleApi.requestPasswordReset(personId, clientBody);
        LOGGER.info("Password reset request sent for {} result {}", personId, result);
    }
}

Executing this code will request a password request for user martin, an email will be sent to the user assuming the password request should be via the Alfresco Share UI:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar request-pwd-reset martin     

2021-05-06 09:30:38.440  INFO 24356 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.438 seconds (JVM running for 3.939)
2021-05-06 09:30:38.442  INFO 24356 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: request-pwd-reset
2021-05-06 09:30:38.443  INFO 24356 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: martin
2021-05-06 09:30:39.726  INFO 24356 --- [           main] o.a.tutorial.restapi.RequestPwdResetCmd  : Password reset request sent for martin result <202 ACCEPTED Accepted,[cache-control:"no-cache", connection:"keep-alive", content-length:"0", content-type:"application/json;charset=UTF-8", date:"Thu, 06 May 2021 08:30:39 GMT", expires:"Thu, 01 Jan 1970 00:00:00 GMT", pragma:"no-cache", server:"nginx/1.18.0", x-frame-options:"SAMEORIGIN"]>

List groups a person is a member of

Listing groups that a person is a member of uses the listGroupMembershipsForPerson method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupEntry;
import org.alfresco.core.model.GroupPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListPersonGroupMembershipsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListPersonGroupMembershipsCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String personId) throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        String where = null;
        List<String> orderBy = null;
        List<String> include = null;
        List<String> fields = null;

        LOGGER.info("Listing group memberships for person {}", personId);
        GroupPaging groups = groupsApi.listGroupMembershipsForPerson(
                personId, skipCount, maxItems, orderBy, include, where, fields).getBody();
        for (GroupEntry groupEntry: groups.getList().getEntries()) {
            LOGGER.info("  {}", groupEntry.getEntry().getDisplayName());
        }
    }
}

Executing this code will list the group memberships for passed in username. In this example we list group memberships for two of the out-of-the-box users abeecher and admin:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-person-group-memberships abeecher

2021-05-06 09:42:50.643  INFO 24597 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.63 seconds (JVM running for 4.106)
2021-05-06 09:42:50.645  INFO 24597 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-person-group-memberships
2021-05-06 09:42:50.647  INFO 24597 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: abeecher
2021-05-06 09:42:50.647  INFO 24597 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    : Listing group memberships for person abeecher
2021-05-06 09:42:50.821  INFO 24597 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   null
2021-05-06 09:42:50.821  INFO 24597 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   site_swsdp
2021-05-06 09:42:50.821  INFO 24597 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   site_swsdp_SiteCollaborator

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-person-group-memberships admin   

2021-05-06 09:43:06.433  INFO 24599 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: admin
2021-05-06 09:43:06.433  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    : Listing group memberships for person admin
2021-05-06 09:43:06.631  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   ALFRESCO_ADMINISTRATORS
2021-05-06 09:43:06.631  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   ALFRESCO_MODEL_ADMINISTRATORS
2021-05-06 09:43:06.631  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   ALFRESCO_SEARCH_ADMINISTRATORS
2021-05-06 09:43:06.631  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   EMAIL_CONTRIBUTORS
2021-05-06 09:43:06.631  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   null
2021-05-06 09:43:06.631  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   SITE_ADMINISTRATORS
2021-05-06 09:43:06.632  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   site_swsdp
2021-05-06 09:43:06.632  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   site_swsdp_SiteManager
2021-05-06 09:43:06.632  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   site_test
2021-05-06 09:43:06.632  INFO 24599 --- [           main] o.a.t.r.ListPersonGroupMembershipsCmd    :   site_test_SiteManager```

List groups

Listing the groups available in the repository uses the listGroups method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupEntry;
import org.alfresco.core.model.GroupPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListGroupsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListGroupsCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute() throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        String where = null;
        List<String> orderBy = null;
        List<String> include = null;
        List<String> fields = null;

        LOGGER.info("Listing group in the repo:");
        GroupPaging groups = groupsApi.listGroups(skipCount, maxItems, orderBy, include, where, fields).getBody();
        for (GroupEntry groupEntry: groups.getList().getEntries()) {
            LOGGER.info("  {}", groupEntry.getEntry().getDisplayName());
        }
    }
}

Executing this code will list the groups available in the repository, note that this can be a lot of groups if there are loads of Share sites and the system is connected to a directory server:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-groups                        

2021-05-06 09:50:39.416  INFO 24665 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.637 seconds (JVM running for 4.131)
2021-05-06 09:50:39.419  INFO 24665 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-groups
2021-05-06 09:50:39.420  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       : Listing group in the repo:
2021-05-06 09:50:39.807  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   Engineering
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   ALFRESCO_ADMINISTRATORS
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   ALFRESCO_MODEL_ADMINISTRATORS
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   ALFRESCO_SEARCH_ADMINISTRATORS
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   EMAIL_CONTRIBUTORS
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   SITE_ADMINISTRATORS
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_swsdp
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_swsdp_SiteCollaborator
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_swsdp_SiteConsumer
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_swsdp_SiteContributor
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_swsdp_SiteManager
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_test
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_test_SiteCollaborator
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_test_SiteConsumer
2021-05-06 09:50:39.808  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_test_SiteContributor
2021-05-06 09:50:39.809  INFO 24665 --- [           main] o.a.tutorial.restapi.ListGroupsCmd       :   site_test_SiteManager

Create a group

Creating a group uses the createGroup method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupBodyCreate;
import org.alfresco.core.model.GroupEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class CreateGroupCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(CreateGroupCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String groupId, String name) throws IOException {
        List<String> fields = null;
        List<String> include = null;

        GroupBodyCreate groupBodyCreate = new GroupBodyCreate();
        groupBodyCreate.setId(groupId);
        groupBodyCreate.setDisplayName(name);
        GroupEntry groupEntry = groupsApi.createGroup(groupBodyCreate, include, fields).getBody();
        LOGGER.info("Created group  {}", groupEntry.getEntry());
    }
}

Executing this code will create a group, in this case we are creating an HR group:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-group hr "Human Resources"

2021-05-06 10:25:51.906  INFO 25139 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.477 seconds (JVM running for 3.956)
2021-05-06 10:25:51.908  INFO 25139 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: create-group
2021-05-06 10:25:51.909  INFO 25139 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: hr
2021-05-06 10:25:51.909  INFO 25139 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: Human Resources
2021-05-06 10:25:52.165  INFO 25139 --- [           main] o.a.tutorial.restapi.CreateGroupCmd      : Created group  class Group {
    id: GROUP_hr
    displayName: Human Resources
    isRoot: true
    parentIds: null
    zones: null
}

Get group metadata

Getting metadata for a group uses the getGroup method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class GetGroupCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetGroupCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String groupId) throws IOException {
        List<String> fields = null;
        List<String> include = null;

        GroupEntry groupEntry = groupsApi.getGroup(groupId, include, fields).getBody();
        LOGGER.info("Got group metadata  {}", groupEntry.getEntry());
    }
}

Executing this code will get metadata for a group, in this case we are getting metadata for a group with id hr, note that you have to prefix group ids with GROUP_:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar get-group GROUP_hr

2021-05-06 10:31:37.864  INFO 25363 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.763 seconds (JVM running for 4.242)
2021-05-06 10:31:37.866  INFO 25363 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: get-group
2021-05-06 10:31:37.868  INFO 25363 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_hr
2021-05-06 10:31:38.025  INFO 25363 --- [           main] o.alfresco.tutorial.restapi.GetGroupCmd  : Got group metadata  class Group {
    id: GROUP_hr
    displayName: Human Resources
    isRoot: true
    parentIds: null
    zones: null
}

Update a group

Updating a group name uses the updateGroup method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupBodyUpdate;
import org.alfresco.core.model.GroupEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class UpdateGroupCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(UpdateGroupCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String groupId, String newName) throws IOException {
        List<String> fields = null;
        List<String> include = null;

        GroupBodyUpdate groupBodyUpdate = new GroupBodyUpdate();
        groupBodyUpdate.setDisplayName(newName);

        GroupEntry group = groupsApi.updateGroup(groupId, groupBodyUpdate, include, fields).getBody();
        LOGGER.info("Updated group {}", group);
    }
}

Executing this code will update the name of the group with passed in id, in this case we are updating the name for a group with id hr, note that you have to prefix group ids with GROUP_:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar update-group GROUP_hr "Human Resources updated"

2021-05-06 12:42:41.475  INFO 26302 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.285 seconds (JVM running for 3.742)
2021-05-06 12:42:41.477  INFO 26302 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: update-group
2021-05-06 12:42:41.478  INFO 26302 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_hr
2021-05-06 12:42:41.478  INFO 26302 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: Human Resources updated
2021-05-06 12:42:41.673  INFO 26302 --- [           main] o.a.tutorial.restapi.UpdateGroupCmd      : Updated group class GroupEntry {
    entry: class Group {
        id: GROUP_hr
        displayName: Human Resources updated
        isRoot: true
        parentIds: null
        zones: null
    }
}

List all people and groups in a group

Listing all the members of a group (i.e. people and groups) uses the listGroupMemberships method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupMemberEntry;
import org.alfresco.core.model.GroupMemberPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListGroupMembersCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListGroupMembersCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String groupId) throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        String where = null;
        List<String> orderBy = null;
        List<String> fields = null;

        LOGGER.info("Listing members of group {}:", groupId);
        GroupMemberPaging groupMembers = groupsApi.listGroupMemberships(
                groupId, skipCount, maxItems, orderBy, where, fields).getBody();
        for (GroupMemberEntry groupMemberEntry: groupMembers.getList().getEntries()) {
            LOGGER.info("  {} ({})", groupMemberEntry.getEntry().getDisplayName(), 
                    groupMemberEntry.getEntry().getMemberType());
        }
    }
}

Executing this code will list the members of passed in group id, note that you have to prefix group ids with GROUP_:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-group-members GROUP_engineering

2021-05-06 12:55:43.231  INFO 26500 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.59 seconds (JVM running for 4.024)
2021-05-06 12:55:43.233  INFO 26500 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-group-members
2021-05-06 12:55:43.234  INFO 26500 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_engineering
2021-05-06 12:55:43.234  INFO 26500 --- [           main] o.a.t.restapi.ListGroupMembersCmd        : Listing members of group GROUP_engineering:
2021-05-06 12:55:43.404  INFO 26500 --- [           main] o.a.t.restapi.ListGroupMembersCmd        :   martin (PERSON)
2021-05-06 12:55:43.404  INFO 26500 --- [           main] o.a.t.restapi.ListGroupMembersCmd        :   System Architects (GROUP)

Adding people and groups to a group

Adding members to a group uses the createGroupMembership method of the GroupsApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.GroupsApi;
import org.alfresco.core.model.GroupMemberEntry;
import org.alfresco.core.model.GroupMembershipBodyCreate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class CreateGroupMembershipCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(CreateGroupMembershipCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String groupId, String memberId, String type) throws IOException {
        List<String> fields = null;

        GroupMembershipBodyCreate groupMembershipBodyCreate = new GroupMembershipBodyCreate();
        groupMembershipBodyCreate.setId(memberId);
        groupMembershipBodyCreate.setMemberType(GroupMembershipBodyCreate.MemberTypeEnum.fromValue(type));

        GroupMemberEntry groupMember = groupsApi.createGroupMembership(groupId, groupMembershipBodyCreate, fields).getBody();
        LOGGER.info("Added member to group {} {}", groupId, groupMember.getEntry());
    }
}

Executing this code will add a person, or a group, to passed in group id, note that you have to prefix group ids with GROUP_:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-group-membership GROUP_hr martin PERSON

2021-05-06 13:54:39.062  INFO 27295 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.83 seconds (JVM running for 4.364)
2021-05-06 13:54:39.064  INFO 27295 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: create-group-membership
2021-05-06 13:54:39.066  INFO 27295 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_hr
2021-05-06 13:54:39.066  INFO 27295 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: martin
2021-05-06 13:54:39.066  INFO 27295 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[3]: PERSON
2021-05-06 13:54:39.311  INFO 27295 --- [           main] o.a.t.restapi.CreateGroupMembershipCmd   : Added member to group GROUP_hr class GroupMember {
    id: martin
    displayName: martin
    memberType: PERSON
}
% java -jar target/rest-api-0.0.1-SNAPSHOT.jar create-group-membership GROUP_hr GROUP_engineering GROUP

2021-05-06 13:55:23.094  INFO 27297 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.855 seconds (JVM running for 4.351)
2021-05-06 13:55:23.096  INFO 27297 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: create-group-membership
2021-05-06 13:55:23.097  INFO 27297 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_hr
2021-05-06 13:55:23.097  INFO 27297 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: GROUP_engineering
2021-05-06 13:55:23.097  INFO 27297 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[3]: GROUP
2021-05-06 13:55:23.327  INFO 27297 --- [           main] o.a.t.restapi.CreateGroupMembershipCmd   : Added member to group GROUP_hr class GroupMember {
    id: GROUP_engineering
    displayName: Engineering
    memberType: GROUP
}

Delete a person or group from a group

Deleting members of a group uses the deleteGroupMembership method of the GroupsApi.

More info about this ReST API endpoint

import org.alfresco.core.handler.GroupsApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class DeleteGroupMembershipCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(DeleteGroupMembershipCmd.class);

    @Autowired
    GroupsApi groupsApi;

    public void execute(String groupId, String groupMemberId) throws IOException {
        HttpEntity<Void> result = groupsApi.deleteGroupMembership(groupId, groupMemberId);
        LOGGER.info("Deleted group membership for group {} member {} result {}", groupId, groupMemberId, result);
    }
}

Executing this code will delete a person, or a group, from passed in group id, note that you have to prefix group ids with GROUP_:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar delete-group-membership GROUP_hr martin           

2021-05-06 14:09:25.825  INFO 27471 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.959 seconds (JVM running for 4.516)
2021-05-06 14:09:25.828  INFO 27471 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: delete-group-membership
2021-05-06 14:09:25.829  INFO 27471 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_hr
2021-05-06 14:09:25.829  INFO 27471 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: martin
2021-05-06 14:09:26.112  INFO 27471 --- [           main] o.a.t.restapi.DeleteGroupMembershipCmd   : Deleted group membership for group GROUP_hr member martin result <204 NO_CONTENT No Content,[cache-control:"no-cache", connection:"keep-alive", content-type:"application/json;charset=UTF-8", date:"Thu, 06 May 2021 13:09:26 GMT", expires:"Thu, 01 Jan 1970 00:00:00 GMT", pragma:"no-cache", server:"nginx/1.18.0", x-frame-options:"SAMEORIGIN"]>

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar delete-group-membership GROUP_hr GROUP_engineering

2021-05-06 14:09:51.048  INFO 27472 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.964 seconds (JVM running for 4.493)
2021-05-06 14:09:51.050  INFO 27472 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: delete-group-membership
2021-05-06 14:09:51.051  INFO 27472 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: GROUP_hr
2021-05-06 14:09:51.051  INFO 27472 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: GROUP_engineering
2021-05-06 14:09:51.152  INFO 27472 --- [           main] o.a.t.restapi.DeleteGroupMembershipCmd   : Deleted group membership for group GROUP_hr member GROUP_engineering result <204 NO_CONTENT No Content,[cache-control:"no-cache", connection:"keep-alive", content-type:"application/json;charset=UTF-8", date:"Thu, 06 May 2021 13:09:51 GMT", expires:"Thu, 01 Jan 1970 00:00:00 GMT", pragma:"no-cache", server:"nginx/1.18.0", x-frame-options:"SAMEORIGIN"]>

Setting permissions for a group

Setting permissions for a group uses the updateNode method of the NodesApi. For more info see set permissions for a node.

More info about this ReST API endpoint

=== Managing Audit Applications and Logs ===

The following sections walk through how to use the Java ReST API wrapper services when managing audit applications and their logs.

Enable auditing and Alfresco Access audit application

See this section

List audit applications

Listing all the audit applications uses the listAuditApps method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.AuditApi;
import org.alfresco.core.model.AuditAppEntry;
import org.alfresco.core.model.AuditAppPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class ListAuditAppsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListAuditAppsCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute() throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        List<String> fields = null;

        LOGGER.info("Listing active audit applications in the repository:");
        AuditAppPaging auditApps = auditApi.listAuditApps(skipCount, maxItems, fields).getBody();
        for (AuditAppEntry auditAppEntry: auditApps.getList().getEntries()) {
            LOGGER.info("  {}", auditAppEntry);
        }
    }
}

Executing this code will list the audit applications that have been activated, if you have enabled auditing and activated the alfresco-access audit application, then you will see the following listing of audit apps:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-audit-apps

2021-05-07 12:48:12.434  INFO 36995 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.525 seconds (JVM running for 4.089)
2021-05-07 12:48:12.436  INFO 36995 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-audit-apps
2021-05-07 12:48:12.437  INFO 36995 --- [           main] o.a.tutorial.restapi.ListAuditAppsCmd    : Listing active audit applications in the repository:
2021-05-07 12:48:12.912  INFO 36995 --- [           main] o.a.tutorial.restapi.ListAuditAppsCmd    :   class AuditAppEntry {
    entry: class AuditApp {
        id: tagging
        name: Alfresco Tagging Service
        isEnabled: true
        maxEntryId: null
        minEntryId: null
    }
}
2021-05-07 12:48:12.913  INFO 36995 --- [           main] o.a.tutorial.restapi.ListAuditAppsCmd    :   class AuditAppEntry {
    entry: class AuditApp {
        id: alfresco-access
        name: alfresco-access
        isEnabled: true
        maxEntryId: null
        minEntryId: null
    }
}

Get audit application metadata

Getting the audit application metadata uses the getAuditApp method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.AuditApi;
import org.alfresco.core.model.AuditApp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class GetAuditAppCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetAuditAppCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String auditAppId) throws IOException {
        List<String> fields = null;
        List<String> include = null;

        AuditApp auditApp = auditApi.getAuditApp(auditAppId, fields, include).getBody();
        LOGGER.info("Got audit app metadata  {}", auditApp);
    }
}

Enable/Disable an audit application

Enable and disable an audit application uses the updateAuditApp method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.AuditApi;
import org.alfresco.core.model.AuditApp;
import org.alfresco.core.model.AuditBodyUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class EnableDisableAuditAppCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(EnableDisableAuditAppCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String auditAppId) throws IOException {
        List<String> fields = null;

        AuditBodyUpdate auditBodyUpdate = new AuditBodyUpdate();
        auditBodyUpdate.setIsEnabled(true);

        AuditApp auditApp = auditApi.updateAuditApp(auditAppId, auditBodyUpdate, fields).getBody();
        LOGGER.info("Enabled audit app  {}", auditApp);
    }
}

List audit entries (logs) for an audit application

Listing all the audit logs for an audit application uses the listAuditEntriesForAuditApp method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.AuditApi;
import org.alfresco.core.model.AuditEntryEntry;
import org.alfresco.core.model.AuditEntryPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class ListAuditLogsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListAuditLogsCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String auditAppId) throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        String where = null;
        List<String> fields = null;
        List<String> include = new ArrayList<>();
        List<String> orderBy = null;

        // Include the log values
        include.add("values");

        // Controls if the response provides the total numbers of items in the collection.
        // If not supplied then the default value is false.
        Boolean omitTotalItems = true;

        LOGGER.info("Listing logs for audit application {}:", auditAppId);
        AuditEntryPaging auditLogs = auditApi.listAuditEntriesForAuditApp(
                auditAppId, skipCount, omitTotalItems, orderBy, maxItems, where, include, fields).getBody();
        for (AuditEntryEntry auditAppEntry: auditLogs.getList().getEntries()) {
            String username = "N/A";
            if (auditAppEntry.getEntry().getCreatedByUser() != null) {
                username = auditAppEntry.getEntry().getCreatedByUser().getId();
            }
            String log = null;
            if (auditAppEntry.getEntry().getValues().toString().length() > 60) {
                log = auditAppEntry.getEntry().getValues().toString().substring(0, 60);
            } else {
                log = auditAppEntry.getEntry().getValues().toString();
            }
            LOGGER.info("  {} {} {}", auditAppEntry.getEntry().getCreatedAt(), username, log);
        }
    }
}

Note that you have to add values to the include parameter for the logs to include all the data. Also, the audit log value have been truncated for readability.

Executing this code will list the audit logs for passed in audit app id. The audit log values have been trimmed:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-audit-logs alfresco-access

2021-05-07 14:46:29.977  INFO 40544 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.493 seconds (JVM running for 3.948)
2021-05-07 14:46:29.980  INFO 40544 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-audit-logs
2021-05-07 14:46:29.981  INFO 40544 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: alfresco-access
2021-05-07 14:46:29.981  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    : Listing logs for audit application alfresco-access:
2021-05-07 14:46:30.376  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T11:48:12.758Z admin {/alfresco-access/login/user=admin}
2021-05-07 14:46:30.376  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:30:46.256Z N/A {/alfresco-access/loginFailure/user=martin}
2021-05-07 14:46:30.377  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:30:58.768Z N/A {/alfresco-access/loginFailure/user=test}
2021-05-07 14:46:30.377  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:31:07.650Z admin {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.379  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:31:08.087Z admin {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.379  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:31:08.141Z admin {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.379  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:31:08.800Z admin {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-07 14:46:30.379  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:31:08.853Z admin {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-07 14:46:30.380  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:31:08.961Z admin {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-07 14:46:30.380  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:04.579Z admin {/alfresco-access/logout/user=admin}
2021-05-07 14:46:30.381  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:10.404Z test {/alfresco-access/login/user=test}
2021-05-07 14:46:30.381  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:11.261Z test {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.381  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:11.316Z test {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.381  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:11.371Z test {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.381  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:11.436Z test {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.382  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:12.075Z test {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-07 14:46:30.382  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:12.101Z test {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-07 14:46:30.382  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:12.124Z test {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-07 14:46:30.383  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:24.764Z test {/alfresco-access/logout/user=test}
2021-05-07 14:46:30.383  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:26.423Z admin {/alfresco-access/login/user=admin}
2021-05-07 14:46:30.384  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:37.917Z admin {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.384  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:47.013Z admin {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:51.284Z admin {/alfresco-access/transaction/sub-actions=addNodeAspect crea
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:51.363Z admin {/alfresco-access/transaction/sub-actions=readContent addNod
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:51.458Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:51.511Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:54.271Z admin {/alfresco-access/transaction/sub-actions=addNodeAspect crea
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:54.343Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:54.443Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:32:54.468Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:03.656Z admin {/alfresco-access/transaction/sub-actions=createNode updateN
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:03.784Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-07 14:46:30.385  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.218Z admin {/alfresco-access/transaction/sub-actions=addNodeAspect, /al
2021-05-07 14:46:30.386  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.264Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-07 14:46:30.386  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.347Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-07 14:46:30.386  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.754Z admin {/alfresco-access/transaction/sub-actions=addNodeAspect crea
2021-05-07 14:46:30.386  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.806Z admin {/alfresco-access/transaction/sub-actions=addNodeAspect upda
2021-05-07 14:46:30.386  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.878Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-07 14:46:30.386  INFO 40544 --- [           main] o.a.tutorial.restapi.ListAuditLogsCmd    :   2021-05-07T13:33:04.899Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr

List audit entries (logs) for a node

Listing all the audit logs for a node uses the listAuditEntriesForNode method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.AuditApi;
import org.alfresco.core.model.AuditEntryEntry;
import org.alfresco.core.model.AuditEntryPaging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
public class ListNodeAuditLogsCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(ListNodeAuditLogsCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String nodeId) throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;
        String where = null;
        List<String> fields = null;
        List<String> include = new ArrayList<>();
        List<String> orderBy = null;

        // Include the log values
        include.add("values");

        LOGGER.info("Listing logs for node ID {}:", nodeId);
        AuditEntryPaging auditLogs = auditApi.listAuditEntriesForNode(
                nodeId, skipCount, orderBy, maxItems, where, include, fields).getBody();
        for (AuditEntryEntry auditAppEntry: auditLogs.getList().getEntries()) {
            String username = "N/A";
            if (auditAppEntry.getEntry().getCreatedByUser() != null) {
                username = auditAppEntry.getEntry().getCreatedByUser().getId();
            }
            String log = null;
            if (auditAppEntry.getEntry().getValues().toString().length() > 60) {
                log = auditAppEntry.getEntry().getValues().toString().substring(0, 60);
            } else {
                log = auditAppEntry.getEntry().getValues().toString();
            }
            LOGGER.info("  {} {} {} {}", auditAppEntry.getEntry().getId(), auditAppEntry.getEntry().getCreatedAt(),
                    username, log);
        }
    }
}

Note that you have to add values to the include parameter for the logs to include all the data. Also, the audit log value have been truncated for readability.

Executing this code will list the audit logs for passed in audit app id and audit entry id:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar list-audit-logs-node 37eedde2-3c78-4d25-bade-5360e22579f4

2021-05-10 09:27:33.714  INFO 51172 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 4.069 seconds (JVM running for 4.724)
2021-05-10 09:27:33.716  INFO 51172 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: list-audit-logs-node
2021-05-10 09:27:33.717  INFO 51172 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: 37eedde2-3c78-4d25-bade-5360e22579f4
2021-05-10 09:27:33.717  INFO 51172 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       : Listing logs for node ID 37eedde2-3c78-4d25-bade-5360e22579f4:
2021-05-10 09:41:55.396  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   76 2021-05-10T08:26:58.965Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-10 09:41:55.396  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   77 2021-05-10T08:27:06.541Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-10 09:41:55.396  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   78 2021-05-10T08:27:11.353Z admin {/alfresco-access/transaction/sub-actions=updateContent upda
2021-05-10 09:41:55.396  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   80 2021-05-10T08:27:12.270Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-10 09:41:55.397  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   81 2021-05-10T08:27:12.429Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr
2021-05-10 09:41:55.397  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   83 2021-05-10T08:27:13.524Z admin {/alfresco-access/transaction/sub-actions=updateNodeProperti
2021-05-10 09:41:55.397  INFO 51505 --- [           main] o.a.t.restapi.ListNodeAuditLogsCmd       :   84 2021-05-10T08:27:13.609Z admin {/alfresco-access/transaction/sub-actions=readContent, /alfr

Get an audit entry (log)

Getting an audit log uses the getAuditEntry method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.AuditApi;
import org.alfresco.core.model.AuditEntryEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class GetAuditLogCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(GetAuditLogCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String auditAppId, String auditLogId) throws IOException {
        List<String> fields = null;

        AuditEntryEntry auditLog = auditApi.getAuditEntry(auditAppId, auditLogId, fields).getBody();
        LOGGER.info("Got audit log metadata  {}", auditLog);
    }
}

Executing this code will list the audit logs for passed in audit app id. The audit log values have been trimmed:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar get-audit-log alfresco-access 80                     

2021-05-10 09:49:57.492  INFO 51645 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.49 seconds (JVM running for 3.992)
2021-05-10 09:49:57.494  INFO 51645 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: get-audit-log
2021-05-10 09:49:57.496  INFO 51645 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: alfresco-access
2021-05-10 09:49:57.496  INFO 51645 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 80
2021-05-10 09:49:57.676  INFO 51645 --- [           main] o.a.tutorial.restapi.GetAuditLogCmd      : Got audit log metadata  class AuditEntryEntry {
    entry: class AuditEntry {
        id: 80
        auditApplicationId: alfresco-access
        createdByUser: class UserInfo {
            displayName: Administrator
            id: admin
        }
        createdAt: 2021-05-10T08:27:12.270Z
        values: {
          /alfresco-access/transaction/sub-actions=updateNodeProperties, 
          /alfresco-access/transaction/properties/from={cm:lastThumbnailModification=[pdf:1620394371183, doclib:1620394374161], {http://www.alfresco.org/model/content/1.0}modified=2021-05-10T08:27:10.986+0000}, 
          /alfresco-access/transaction/properties/to={cm:lastThumbnailModification=[doclib:1620394374161, pdf:1620635231931], {http://www.alfresco.org/model/content/1.0}modified=2021-05-10T08:27:11.935+0000}, 
          /alfresco-access/transaction/path=/app:company_home/app:guest_home/cm:somefileudpated.txt, 
          /alfresco-access/transaction/action=updateNodeProperties, 
          /alfresco-access/transaction/type=cm:content, 
          /alfresco-access/transaction/user=admin}
    }
}

Delete audit entries (logs) for an audit application

Deleting audit logs for an audit app uses the deleteAuditEntriesForAuditApp method of the AuditApi.

More info about this ReST API endpoint

For a description of the common parameters, such as where, see this section.

import org.alfresco.core.handler.AuditApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class DeleteAuditLogsForAppCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(DeleteAuditLogsForAppCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String auditAppId) throws IOException {
        // Delete all logs with ids between 1 and 79
        String where = "(id BETWEEN ('1', '79'))";

        HttpEntity<Void> response = auditApi.deleteAuditEntriesForAuditApp(auditAppId, where);
        LOGGER.info("Deleted audit logs for app {} where {} response {}", auditAppId, where, response);
    }
}

Note that you have to supply a where clause to be able to delete any audit logs.

Executing this code will delete audit logs for passed in audit app id and where clause:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar delete-audit-logs-for-app alfresco-access

2021-05-10 10:05:22.248  INFO 51942 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 4.116 seconds (JVM running for 4.615)
2021-05-10 10:05:22.250  INFO 51942 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: delete-audit-logs-for-app
2021-05-10 10:05:22.251  INFO 51942 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: alfresco-access
2021-05-10 10:05:22.357  INFO 51942 --- [           main] o.a.t.restapi.DeleteAuditLogsForAppCmd   : Deleted audit logs for app alfresco-access where (id BETWEEN ('1', '79')) response <204 NO_CONTENT No Content,[cache-control:"no-cache", connection:"keep-alive", content-type:"application/json;charset=UTF-8", date:"Mon, 10 May 2021 09:05:22 GMT", expires:"Thu, 01 Jan 1970 00:00:00 GMT", pragma:"no-cache", server:"nginx/1.18.0", x-frame-options:"SAMEORIGIN"]

Delete an audit entry (log) for an audit application

Deleting a single audit entry for an audit app uses the deleteAuditEntry method of the AuditApi.

More info about this ReST API endpoint

import org.alfresco.core.handler.AuditApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class DeleteAuditLogCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(DeleteAuditLogCmd.class);

    @Autowired
    AuditApi auditApi;

    public void execute(String auditAppId, String auditLogId) throws IOException {
        HttpEntity<Void> response = auditApi.deleteAuditEntry(auditAppId, auditLogId);
        LOGGER.info("Deleted audit log: app {} log id {} response {}", auditAppId, auditLogId, response);
    }
}

Executing this code will delete an audit log with passed in id for audit app with passed in id:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar delete-audit-log alfresco-access 80                      

2021-05-10 10:14:54.941  INFO 52102 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.736 seconds (JVM running for 4.24)
2021-05-10 10:14:54.943  INFO 52102 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: delete-audit-log
2021-05-10 10:14:54.944  INFO 52102 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[1]: alfresco-access
2021-05-10 10:14:54.944  INFO 52102 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[2]: 80
2021-05-10 10:14:55.050  INFO 52102 --- [           main] o.a.tutorial.restapi.DeleteAuditLogCmd   : Deleted audit log: app alfresco-access log id 80 response <204 NO_CONTENT No Content,[cache-control:"no-cache", connection:"keep-alive", content-type:"application/json;charset=UTF-8", date:"Mon, 10 May 2021 09:14:55 GMT", expires:"Thu, 01 Jan 1970 00:00:00 GMT", pragma:"no-cache", server:"nginx/1.18.0", x-frame-options:"SAMEORIGIN"]>

=== Searching for content ===

The following sections walk through how to use the Java ReST API wrapper services when managing audit applications and their logs.

Finding folders and files by a term

To find a node, such as a folder or file, by a term use the findNodes method of the QueriesApi, which is a search API you can use when doing simple search on a term. For more complex search, such as Alfresco Full Text Search (AFTS), use the Search API;

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.core.handler.QueriesApi;
import org.alfresco.core.model.NodeEntry;
import org.alfresco.core.model.NodePagingList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class FindNodesCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(FindNodesCmd.class);

    @Autowired
    QueriesApi queriesApi;

    public void execute() throws IOException {
        String rootNodeId = "-root-"; // The id of the node to start the search from. Supports the aliases -my-, -root- and -shared-.
        Integer skipCount = 0;
        Integer maxItems = 100;

        // Restrict the returned results to only those of the given node type and its sub-types
        String nodeType = null;

        List<String> include = null;
        List<String> orderBy = null;
        List<String> fields = null;

        String term = "Dict*";
        LOGGER.info("Searching for nodes by term: {}", term);
        NodePagingList result = queriesApi.findNodes(
                term, rootNodeId, skipCount, maxItems, nodeType, include, orderBy, fields).getBody().getList();
        for (NodeEntry node: result.getEntries()) {
            LOGGER.info("Found node [name={}][id={}]", node.getEntry().getName(), node.getEntry().getId());
        }
    }
}

Executing this code gives the following type of result:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar find-nodes

2021-05-10 13:40:47.999  INFO 54955 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.429 seconds (JVM running for 3.909)
2021-05-10 13:40:48.001  INFO 54955 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: find-nodes
2021-05-10 13:40:48.003  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Searching for nodes by term: Dict*
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Dictionary][id=b1264564-9b33-4003-bff9-58f2591cea54]
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Dictionary-stuff.txt][id=6f7689af-b31e-493a-ad3a-298abcf03664]
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=InviteHelper.txt][id=4875faf1-6366-477a-a97b-b30d15f33808]
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Data Dictionary][id=392f377c-4a0b-4ab1-8327-3034269030a5]
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=readme.html][id=d38b8cb0-0973-4bfd-84c5-9db4959d4715]
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Project Contract.pdf][id=1a0b110f-1e09-4ca2-b367-fe25e4964a4e]
2021-05-10 13:40:49.143  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Meeting Notes 2011-02-10.doc][id=a8290263-4178-48f5-a0b0-be155a424828]
2021-05-10 13:40:49.144  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Meeting Notes 2011-02-03.doc][id=150398b3-7f82-4cf6-af63-c450ef6c5eb8]
2021-05-10 13:40:49.144  INFO 54955 --- [           main] o.a.tutorial.restapi.FindNodesCmd        : Found node [name=Meeting Notes 2011-01-27.doc][id=f3bb5d08-9fd1-46da-a94a-97f20f1ef208]

Finding sites by a term

To find sites by term use the findSites method of the QueriesApi, which is a search API you can use when doing simple search on a term. For more complex search, such as Alfresco Full Text Search (AFTS), use the Search API;

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.QueriesApi;
import org.alfresco.core.model.SiteEntry;
import org.alfresco.core.model.SitePagingList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class FindSitesCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(FindSitesCmd.class);

    @Autowired
    QueriesApi queriesApi;

    public void execute() throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;

        List<String> orderBy = null;
        List<String> fields = null;

        String term = "Soft*";
        LOGGER.info("Searching for sites by term: {}", term);
        SitePagingList result = queriesApi.findSites(term, skipCount, maxItems, orderBy, fields).getBody().getList();
        for (SiteEntry node: result.getEntries()) {
            LOGGER.info("Found site [id={}][name={}]", node.getEntry().getId(), node.getEntry().getTitle());
        }
    }
}

Executing this code gives the following type of result:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar find-sites

2021-05-10 13:52:04.833  INFO 55062 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 4.107 seconds (JVM running for 4.762)
2021-05-10 13:52:04.835  INFO 55062 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: find-sites
2021-05-10 13:52:04.845  INFO 55062 --- [           main] o.a.tutorial.restapi.FindSitesCmd        : Searching for sites by term: Soft*
2021-05-10 13:52:05.036  INFO 55062 --- [           main] o.a.tutorial.restapi.FindSitesCmd        : Found site [id=downloadable-software][name=Downloadable Software]
2021-05-10 13:52:05.036  INFO 55062 --- [           main] o.a.tutorial.restapi.FindSitesCmd        : Found site [id=software-design][name=Software Design]

Finding people by a term

To find sites by term use the findPeople method of the QueriesApi, which is a search API you can use when doing simple search on a term. For more complex search, such as Alfresco Full Text Search (AFTS), use the Search API;

More info about this ReST API endpoint

For a description of the common parameters, such as fields, see this section.

import org.alfresco.core.handler.QueriesApi;
import org.alfresco.core.model.PersonEntry;
import org.alfresco.core.model.PersonPagingList;
import org.alfresco.core.model.SiteEntry;
import org.alfresco.core.model.SitePagingList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class FindPeopleCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(FindPeopleCmd.class);

    @Autowired
    QueriesApi queriesApi;

    public void execute() throws IOException {
        Integer skipCount = 0;
        Integer maxItems = 100;

        List<String> orderBy = null;
        List<String> fields = null;

        String term = "*mi*";
        LOGGER.info("Searching for people by term: {}", term);
        PersonPagingList result = queriesApi.findPeople(term, skipCount, maxItems, fields, orderBy).getBody().getList();
        for (PersonEntry person: result.getEntries()) {
            LOGGER.info("Found person [id={}][name={}]", person.getEntry().getId(), person.getEntry().getDisplayName());
        }
    }
}

Executing this code gives the following type of result:

% java -jar target/rest-api-0.0.1-SNAPSHOT.jar find-people

2021-05-10 14:00:38.019  INFO 55327 --- [           main] o.a.tutorial.restapi.RestApiApplication  : Started RestApiApplication in 3.568 seconds (JVM running for 4.045)
2021-05-10 14:00:38.021  INFO 55327 --- [           main] o.a.tutorial.restapi.RestApiApplication  : args[0]: find-people
2021-05-10 14:00:38.022  INFO 55327 --- [           main] o.a.tutorial.restapi.FindPeopleCmd       : Searching for people by term: *mi*
2021-05-10 14:00:38.839  INFO 55327 --- [           main] o.a.tutorial.restapi.FindPeopleCmd       : Found person [id=admin][name=Administrator]
2021-05-10 14:00:38.839  INFO 55327 --- [           main] o.a.tutorial.restapi.FindPeopleCmd       : Found person [id=mjackson][name=Mike Jackson]

Finding content by a search query

To find content based on more complex search queries, such as using Alfresco Full Text Search (AFTS), use the SearchApi,;

More info about this ReST API endpoint

For a description of the common parameters, such as include, see this section.

import org.alfresco.search.handler.SearchApi;
import org.alfresco.search.model.RequestQuery;
import org.alfresco.search.model.ResultSetPaging;
import org.alfresco.search.model.ResultSetRowEntry;
import org.alfresco.search.model.SearchRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;

@Component
public class SearchCmd {
    static final Logger LOGGER = LoggerFactory.getLogger(SearchCmd.class);

    @Autowired
    SearchApi searchApi;

    /**
     * Search in a site
     *
     * @param siteId the site id
     * @param term the term to search for in the site
     */
    public void execute(String siteId, String term) throws IOException {
        List<ResultSetRowEntry> result = search("(SITE:\"" + siteId + "\" AND TEXT:\"" + term + "\" )");

        LOGGER.info("Search result: {}", result);
    }

    /**
     * Search the repository for content using an Alfresco Full Text Search (AFTS) query
     *
     * @param aftsQuery the query statement
     * @return a list of search results
     */
    List<ResultSetRowEntry> search(String aftsQuery) {
        ResponseEntity<ResultSetPaging> result = searchApi.search(new SearchRequest()
                .query(new RequestQuery()
                        .language(RequestQuery.LanguageEnum.AFTS)
                        .query(aftsQuery)));

        return result.getBody().getList().getEntries();
    }
}

Edit this page

Suggest an edit on GitHub
This website uses cookies in order to offer you the most relevant information. Please accept cookies for optimal performance. This documentation is subject to the Documentation Notice.