Skip navigation
All Places > Alfresco Content Services (ECM) > Blog > Author: gavincornwell
1 2 Previous Next

Alfresco Content Services (ECM)

18 Posts authored by: gavincornwell Employee

At the end of my last post I alluded to an improved ingestion pipeline using Step Functions, this post looks at how we can use the recently announced AWS service Comprehend to analyse text files.

 

The updated architecture is shown below (click to enlarge).

 

Demo Architecture

 

The Lambda function that fetches the content sets a flag to indicate whether the content is an image, this is used by the Step Function definition to decide whether to call the ProcessImage or ProcessText Lambda function as shown in the Step Function definition below:

 

Step Function Definition

 

We covered the behaviour of the ProcessImage in the last post, the new ProcessText Lambda function takes the text and sends it Comprehend to detect entities and to perform semantic analysis. The function then looks for Person, Location and Date entities in the text and compares the positive and negative values from the sentiment analysis to determine the values for the properties on the acme:insuranceClaimReport custom type.

 

Everything required to deploy and run the demo is available in this GitHub repository. Clone the repository to your local machine using the command below and follow the deployment instructions.

 

git clone https://github.com/gavincornwell/firehose-step-functions-demo.git .

 

Once the repository is up and running follow the detailed demo steps to upload images and text files to the system and see the metadata in Alfresco get updated automatically.

 

I'll be a doing a live demo of this at the forthcoming DevCon in Lisbon, Portugal, hope to see you there!

gavincornwell

Steps to Rekognition

Posted by gavincornwell Employee Jan 4, 2018

In my last post we looked at a potential out-of-process extension that analysed images using AWS Rekognition. The solution used a single large Lambda function, in this post we're going to examine an improved approach using Step Functions.

 

The architecture is shown below (click to enlarge).

 

 

The use case has also been expanded since the first post, the Lambda function that processes the results from Rekognition now categorises images into Cars, Motorcycles, Boats, Electronics, Jewellery, Wristwatches, Clocks, Bicycles, Sport Equipment and Furniture. Any image that can not determined is set to Unknown rather than adding an aspect.

 

The initial part of the solution is still the same, Camel is used to route events to Kinesis Firehose, accepted  events are sent to S3, which in turn triggers a Lambda function. That Lambda function now parses the Alfresco events and executes a Step Function State Machine (shown in the diagram below) for each event.

 

 

The State Machine calls three separate smaller Lambda functions, each function does one thing and one thing only and are re-usable outside of the Step Function. This is a much more scalable solution and allows the images to be processed in parallel.

 

Everything required to deploy and run the demo is available in this  GitHub repository. Clone the repository to your local machine using the command below and follow the much simpler deployment instructions.

 

git clone https://github.com/gavincornwell/firehose-step-functions-demo.git .

 

Once the repository is up and running follow the detailed demo steps to upload images to the system and see the metadata in Alfresco get updated automatically.

 

Currently the State Machine is fairly simple and serial but it lays the foundation for a more complex ingestion pipeline which is something I may investigate in a future post.

Event streams are becoming common place, they allow external applications to "listen" to what's happening within the server. AWS make use of this pattern in several of their services, S3 and DynamoDB being two examples.

 

This is a capability we are considering adding to the Digital Business Platform in the future. Rather than waiting for the new event stream I thought it would be an interesting experiment to build an example using existing Alfresco technologies. The example uses the events generated for desktop sync and uses Apache Camel to push them to Kinesis Firehose. Once the events reach AWS they are processed by Lambda functions and any uploaded images analysed by Rekognition.

 

The architecture for the example is shown in the diagram below (click to enlarge).

 

Architecture

 

The fictional use case is based on a vehicle insurance company claim process that only deals with cars, motorcycles and bicycles. Their process automatically analyses images added to the system, converts them to a custom type, generates a unique and sets a claim type property if the type of vehicle can be detected by Rekognition. Full technical details can be found here.

 

The Camel route used to send events from ActiveMQ to Firehose, custom model and configuration are provided in simple JAR modules for the Repository and Share available from here

 

Everything required to deploy and run the demo is available in this GitHub repository. Clone the repository to your local machine using the command below and follow the deployment instructions.


git clone https://github.com/gavincornwell/firehose-rekognition-demo.git .

 

Once the repository is up and running follow the detailed demo steps to upload images to the system and see the metadata in Alfresco get updated automatically.

 

If you take a look at the Lambda function used for processing the uploaded images you'll see it's already getting quite complex and breaking a few best practices; it's doing more than one thing, the repository password is being passed in plain text and the approach is not very scalable, in terms of performance, cost and architecture.

 

The Lambda function is effectively a multi-stage process which feels like an ideal fit for Step Functions!

 

Over the next few weeks I'm going to improve the solution by swapping the single Lambda function for several smaller Lambda functions that are orchestrated by Step Functions, simplify and secure the infrastructure setup and introduce a continuous delivery pipeline, so stay tuned!

We've come a really long way in this series of blog posts, we've now covered a majority of the functionality in the 5.2 release through the following posts:

 

 

On reflection, this post should have been the first one in the series as we're going to discuss some capabilities of the API that can be applied to most endpoints! We're also going to cover some lesser know features provided by our underlying REST API Framework. The remainder of this post will highlight 10 features that we think you should know about.

 

To help demonstrate these we've provided a Postman collection with examples, click the "Run in Postman" button below to import it into your client.

 

 

1. Tickets

Throughout this series we've used basic authentication in all the examples. The APIs also support the Alfresco ticket mechanism. You can POST the following body to http://localhost:8080/alfresco/api/-default-/public/authentication/versions/1/tickets to create a new ticket (1st request in the Postman collection):

{
  "userId": "test",
  "password": "test"
}

 

The response provides the ticket in the id property:

{
  "entry": {
    "id": "TICKET_ed4981b4bbb15fc2713f7caaffd23982d0dd4e5c",
    "userId": "test"
  }
}

 

This ticket can then be used instead of a username and password. Although the REST API supports the alf_ticket query parameter we do not recommend using it, a more secure approach is to use a HTTP header. The basic auth mechanism is still used i.e. sending an Authorisation header, however, the base64 encoded username/password is replaced with the base64 encoded ticket.

 

The API Explorer shows an example of this approach (select the Authentication API from the drop-down menu and expand the POST /tickets method) and all requests in the Postman collection accompanying this article use this approach.

 

2. Limiting results

By default the API will return a maximum of 100 results in any one request, this can be controlled via the maxItems query parameter.

 

The 2nd request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?maxItems=5 ) shows how you can limit the number of results to five.

 

This query parameter is supported across all collection endpoints.

 

3. Skipping results

By default the API will return results starting from the beginning, it's possible to skip any number of results using the skipCount query parameter. This is typically used for implementing paging or infinite scrolling in clients.

 

The 3rd request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?skipCount=2 ) shows how you can skip the first two results.

 

This query parameter is supported across all collection endpoints.

 

4. Ordering results

By default all collection endpoints (those returning a list of results) have a default sort order, it's possible to change the sort order on some endpoints via the orderBy query parameter.

 

The 4th request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?orderBy=sizeInBytes DESC) shows how you can order the nodes in your home folder by the size of the content, starting with the largest item.

 

DESC and ASC can be used to control the direction of the sorting. As previously mentioned not all endpoints allow ordering to be controlled so you'll need to consult the API Explorer to see whether the orderBy parameter is supported and what properties within the response can be used.

 

5. Filtering results

Sometimes only a subset of the response is required, several endpoints support this via the where query parameter.

 

The where parameter allows you to provide one or more clauses defining what items you want to see in the response. The 5th request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?where=(isFile=true)) shows how you can limit the nodes in your home folder to just the files.

 

The where parameter is specific to each endpoint so you'll need to consult the API Explorer to see firstly, whether the where parameter is supported and secondly, what expressions and clauses can be used.

 

6. Requesting optional information

We have taken what we're calling a "performance first" approach with the API meaning that each endpoint, by default, only returns the information that is efficient to retrieve. If additional processing is required to obtain the information it is made available via the include query parameter.

 

The 6th request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?include=properties,aspectNames) shows how you can also include the properties and aspects for each node in your home folder when listing it's children.

 

As with orderBy and where the include parameter is specific to the endpoint so you'll need to consult the API Explorer to see what extra information is available.

 

7. Limiting the response

Sometimes bandwidth is a major consideration, for example, if you're building a mobile client. To cater for this scenario the API allows you to control the amount of data sent over the wire via the fields query parameter.

 

The 7th request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?fields=id,name ) shows you can limit the response to only contain the id and name properties as shown in the response below:

{
  "list": {
    "pagination": {
      "count": 3,
      "hasMoreItems": false,
      "totalItems": 3,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "name": "A Folder",
          "id": "a5634765-ab0f-438a-8efd-bfa4139da8aa"
        }
      },
      {
        "entry": {
          "name": "lorem-ipsum.txt",
          "id": "5516aca4-df8b-43e8-8ff3-707316c60c6e"
        }
      },
      {
        "entry": {
          "name": "image.jpg",
          "id": "fc132aa5-6281-40bf-adee-5731e6ecb653"
        }
      }
    ]
  }
}

 

The fields parameter works in conjunction with the include parameter so you don't have to repeat yourself, for example, say you want to include the id, name and the optional aspectName properties in the response you can use fields=id,name&include=aspectNames, you don't have to specify aspectNames again in the fields parameter.

 

The fields parameter is supported universally across all endpoints.

 

8. -me- alias

There are several endpoints across the API that expect a person id as part of the URL, this is OK if the client knows the person id but there are some scenarios where it might not be known, for example when using tickets.

 

For this scenario the API supports the -me- alias which can be substituted in any URL that expects personId.

 

The 8th request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/-me- ) shows how this can be used to retrieve the profile information of the currently authenticated user.

 

9. Create multiple entities

Supporting batch operations i.e. updating the metadata for multiple items simultaneously, is something we plan to support in the future, however it's a little known fact that the API already has some basic batch capabilities when it comes to creating entities.

 

Most POST endpoints that create entities actually allow an array of objects to be passed in the body which creates each one individually, but within the same transaction.

 

The 9th request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children) shows how two folders can be created with the one request by passing the body shown below:

[
  {
    "name": "Folder One",
    "nodeType": "cm:folder"
  },
  {
    "name": "Folder Two",
    "nodeType": "cm:folder"
  }
]

 

The API returns a standard listing response providing the details on each entity that was created, in this case, the two folders:

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "totalItems": 2,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "aspectNames": [
            "cm:auditable"
          ],
          "createdAt": "2017-04-12T10:31:12.477+0000",
          "isFolder": true,
          "isFile": false,
          "createdByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "modifiedAt": "2017-04-12T10:31:12.477+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "name": "Folder One",
          "id": "ecbec6fd-a273-4978-9b95-00a8e783948e",
          "nodeType": "cm:folder",
          "parentId": "062b8b2a-aa7e-4cdd-bfec-7fbcd16ecd85"
        }
      },
      {
        "entry": {
          "aspectNames": [
            "cm:auditable"
          ],
          "createdAt": "2017-04-12T10:31:12.501+0000",
          "isFolder": true,
          "isFile": false,
          "createdByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "modifiedAt": "2017-04-12T10:31:12.501+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "name": "Folder Two",
          "id": "18c82e9b-5a2f-44bf-bc77-1aca7346a24a",
          "nodeType": "cm:folder",
          "parentId": "062b8b2a-aa7e-4cdd-bfec-7fbcd16ecd85"
        }
      }
    ]
  }
}

 

If the endpoint does not support creating multiple entities an error is returned.

 

10. Include the source entity for a collection

When returning a relationship collection of an entity, for example the children of a node or the members of a site, details of the entity are not included by default, to include them you can use the includeSource query parameter.

 

The last request in the Postman collection (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?includeSource=true) shows how you'd include details of the users home folder when listing it's children, shown below in the source property:

{
  "list": {
    "pagination": {
      "count": 12,
      "hasMoreItems": false,
      "totalItems": 12,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [ ... ],
    "source": {
      "name": "test",
      "createdAt": "2017-02-20T11:01:39.647+0000",
      "modifiedAt": "2017-04-12T10:31:12.509+0000",
      "createdByUser": {
        "id": "admin",
        "displayName": "Administrator"
      },
      "modifiedByUser": {
        "id": "test",
        "displayName": "Test User"
      },
      "isFolder": true,
      "isFile": false,
      "aspectNames": [
        "cm:ownable",
        "cm:auditable"
      ],
      "properties": {
        "cm:owner": {
          "id": "test",
          "displayName": "Test User"
        }
      },
      "nodeType": "cm:folder",
      "parentId": "a9ad3bc4-d30f-4910-bee0-63d497e74a22",
      "id": "062b8b2a-aa7e-4cdd-bfec-7fbcd16ecd85"
    }
  }
}

 

Another example is returning details of a site when listing it's members, to do that you'd use the following URL: http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/swsdp/members?includeSource=true

 

The includeSource parameter is supported for all endpoints that include an entity and relationship collection.

 

Well, that's it, we've reached the end of this series of blog posts, thank you very much if you've made it this far, we've covered a lot, but not everything! Despite the long series we've really only scratched the surface so I would definitely encourage you to experiment with the API Explorer and start building your first application.

 

We're always looking for feedback so please do raise any issues you encounter or any suggestions you have in JIRA and keep an eye out for more posts in the future as we enhance the v1 REST API further.

Our journey through the v1 REST APIs in 5.2 is nearly at an end, last time we looked at the trashcan API, in this part we're going to look at arguably one of the most important APIs, the discovery API.

 

As there is only one GET endpoint in this API there is no Postman collection for this post as you can follow along with this post using just a browser.

 

The discovery API is available at http://localhost:8080/alfresco/api/discovery and as the name suggests it provides information on the repository, such as version number, feature status and which modules are installed.

 

We recommend that clients call this API as early as possible in their lifecycle so that it's features can be sensitive to the repository's capabilities. A good example is the ability the manage shared links, this feature can be disabled on the server, in this scenario the /shared-links endpoints will return a 501 status code, if this is not handled by the UI it does not provide a very good user experience!

 

Calling this API on my repository (installed using the Community installer) returns the following response:

{
  "entry": {
    "repository": {
      "edition": "Community",
      "version": {
        "major": "5",
        "minor": "2",
        "patch": "0",
        "hotfix": "0",
        "schema": 10005,
        "label": "r135134-b14",
        "display": "5.2.0.0 (r135134-b14) schema 10005"
      },
      "status": {
        "isReadOnly": false,
        "isAuditEnabled": true,
        "isQuickShareEnabled": true,
        "isThumbnailGenerationEnabled": true
      },
      "modules": [
        {
          "id": "alfresco-aos-module",
          "title": "Alfresco Office Services Module",
          "description": "Allows applications that can talk to a SharePoint server to talk to your Alfresco installation",
          "version": "1.1.5",
          "installDate": "2017-02-20T10:52:26.618+0000",
          "installState": "INSTALLED",
          "versionMin": "5.1",
          "versionMax": "999"
        },
        {
          "id": "org.alfresco.integrations.google.docs",
          "title": "Alfresco / Google Docs Integration",
          "description": "The Repository side artifacts of the Alfresco / Google Docs Integration.",
          "version": "3.0.3",
          "installDate": "2017-02-20T10:52:27.288+0000",
          "installState": "INSTALLED",
          "versionMin": "5.0.0",
          "versionMax": "5.99.99"
        },
        {
          "id": "alfresco-share-services",
          "title": "Alfresco Share Services AMP",
          "description": "Module to be applied to alfresco.war, containing APIs for Alfresco Share",
          "version": "5.2.0",
          "installDate": "2017-02-20T10:52:27.779+0000",
          "installState": "INSTALLED",
          "versionMin": "5.1",
          "versionMax": "999"
        },
        {
          "id": "alfresco-trashcan-cleaner",
          "title": "alfresco-trashcan-cleaner project",
          "description": "The Alfresco Trash Can Cleaner (Alfresco Module)",
          "version": "2.2",
          "installState": "UNKNOWN",
          "versionMin": "0",
          "versionMax": "999"
        },
        {
          "id": "fdk-custom-model-module",
          "title": "FDK Custom Model Module",
          "description": "Packages the FDK custom model as a simple module",
          "version": "1.0-SNAPSHOT",
          "installState": "UNKNOWN",
          "versionMin": "0",
          "versionMax": "999"
        }
      ]
    }
  }
}

 

Although this has been the shortest post in this series so far, hopefully you can see the value and importance of this small, simple API.

 

There is one final post in this series to come where we'll cover some topics that apply to all APIs and highlight some capabilities of the API you might not have known were there!

In the last post we discussed the people API, this time I'm going to show you how you can manage deleted nodes via the trashcan API.

 

As usual, there is a Postman collection to go with this post, click the "Run in Postman" button below to import it into your client.

 

 

Before we can start looking at the trashcan API we need to do a little bit of setup by deleting a node. Firstly, make sure your repository has the test user we created back in part 8. The 1st and 2nd request of the Postman collection creates and deletes a file, respectively. 

 

To list the nodes that have been deleted we can use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/deleted-nodes (3rd request in the Postman collection), the response will look similar to the one below:

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "createdAt": "2017-04-11T13:53:02.359+0000",
          "archivedAt": "2017-04-11T13:55:56.432+0000",
          "isFolder": false,
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "modifiedAt": "2017-04-11T13:53:02.359+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "name": "content-to-be-deleted.txt",
          "archivedByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "id": "1860a21b-b6d2-4cde-aadd-e0bd521787cf",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "text/plain",
            "mimeTypeName": "Plain Text",
            "sizeInBytes": 0,
            "encoding": "UTF-8"
          }
        }
      }
    ]
  }
}

 

The response should look familiar, it's based on the response of /nodes/{{nodeId}}/children with a couple of additions. There is an additional archivedAt property and an archivedByUser property providing details of when the node was archived and by whom. By default, the list is ordered by the archivedAt property, with the most recently deleted being first in the list.

 

As with the nodes API some information is omitted for performance reasons, see the API Explorer for the additional information you can request via the include query parameter.

 

Calling this endpoint as a normal user will only return the nodes you have deleted, however, if you are an administrator, all deleted nodes in the system are returned.

 

To get details of an individual deleted node http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/deleted-nodes/{{nodeId}} (4th request in the Postman collection) can be used. This endpoint returns a little more information about the deleted node (see below) by default but again there is some optional information that can be added via the include query parameter, see the API Explorer for details.

{
  "entry": {
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test User"
    },
    "modifiedAt": "2017-04-11T13:53:02.359+0000",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 0,
      "encoding": "UTF-8"
    },
    "aspectNames": [
      "rn:renditioned",
      "cm:ownable",
      "cm:auditable",
      "cm:thumbnailModification"
    ],
    "createdAt": "2017-04-11T13:53:02.359+0000",
    "archivedAt": "2017-04-11T13:55:56.432+0000",
    "isFolder": false,
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test User"
    },
    "name": "content-to-be-deleted.txt",
    "archivedByUser": {
      "id": "test",
      "displayName": "Test User"
    },
    "id": "1860a21b-b6d2-4cde-aadd-e0bd521787cf",
    "properties": {
      "cm:lastThumbnailModification": [
        "doclib:1491918788304"
      ],
      "cm:owner": {
        "id": "test",
        "displayName": "Test User"
      }
    }
  }
}

 

As you would expect, the API also allows you to restore a deleted node which removes it from the list of deleted nodes and puts it back in the "live" store. You can do this by POSTing to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/deleted-nodes/{{nodeId}}/restore (5th request in the Postman collection). For this endpoint there is no body to send. If the node was successfully restored you will receive a 200 OK response with a representation of the "live" node.  

 

Just to make sure you can list the deleted nodes again using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/deleted-nodes (3rd request in the Postman collection) and you should see an empty list as shown below:

{
  "list": {
    "pagination": {
      "count": 0,
      "hasMoreItems": false,
      "totalItems": 0,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": []
  }
}

 

The opposite or restoring a node is to permanently delete it, once this is done it's final, there is no way to get the node back! To try this out we need to repeat the setup process so re-run the 1st and 2nd request in the Postman collection.

 

To permanently delete a node send a DELETE request to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/deleted-nodes/{{nodeId}} (6th request in the Postman collection), if the deletion was successful you will receive an empty response with a 204 status code.

 

List the deleted nodes (3rd request in the Postman collection) and you will see an empty list once again.

 

If you want to bypass the trashcan completely you can permanently delete a "live" node by using the permanent query parameter, see the API Explorer for details.

 

Hopefully that's given you a good overview of the trashcan API and what's possible, next time we're going to look at the only remaining API we haven't covered yet, the discovery API.

In the last post we looked at the queries and search APIs, today we're going to look at the people API. A couple of the endpoints have been available since 4.2 and we've added a few new ones in the 5.2 release.

 

To keep with tradition this post has a Postman collection you can use to follow along, click the "Run in Postman" button below to import it into your client.

 

 

Let's start by creating a new person using one of the new endpoints added in the 5.2 release. We can POST the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people (1st request in the Postman collection) to create a person with a username (id) of "jdoe". Any of the properties defined for the out-of-the-box cm:person type can be provided, for full details please refer to the API Explorer.

{
  "id": "jdoe",
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@example.com",
  "password": "jdoe",
  "skypeId": "johndoe_skype",
  "jobTitle": "Software Engineer"
}

 

Sending the request above results in the following response:

{
  "entry": {
    "firstName": "John",
    "lastName": "Doe",
    "skypeId": "johndoe_skype",
    "jobTitle": "Software Engineer",
    "emailNotificationsEnabled": true,
    "company": {},
    "id": "jdoe",
    "enabled": true,
    "email": "john.doe@example.com"
  }
}

 

Some customers extend the out-of-the-box cm:person object so we have added support for custom properties too, for example to create a person with a custom property called mycompany:employeeId the following body could be used (presuming the property has been defined in the content model):

{
  "id": "jdoe",
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "jane.doe@example.com",
  "password": "jdoe",
  "skypeId": "janedoe_skype",
  "jobTitle": "Software Engineer",
  "properties": {
    "mycompany:employeeId": "abc-123"
  }
}

 

Another capability added in 5.2 is the ability to retrieve a list of people in the repository by using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people (2nd request in the Postman collection) resulting in a response similar to the one below, which shows the person we just created.

{
  "list": {
    "pagination": {
      "count": 8,
      "hasMoreItems": false,
      "totalItems": 8,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "lastName": "Beecher",
          "userStatus": "Helping to design the look and feel of the new web site",
          "jobTitle": "Graphic Designer",
          "statusUpdatedAt": "2011-02-15T20:20:13.432+0000",
          "mobile": "0112211001100",
          "emailNotificationsEnabled": true,
          "description": "Alice is a demo user for the sample Alfresco Team site.",
          "telephone": "0112211001100",
          "enabled": false,
          "firstName": "Alice",
          "skypeId": "abeecher",
          "avatarId": "198500fc-1e99-4f5f-8926-248cea433366",
          "location": "Tilbury, UK",
          "company": {
            "organization": "Moresby, Garland and Wedge",
            "address1": "200 Butterwick Street",
            "address2": "Tilbury",
            "address3": "UK",
            "postcode": "ALF1 SAM1"
          },
          "id": "abeecher",
          "email": "abeecher@example.com"
        }
      },
      {
        "entry": {
          "firstName": "John",
          "lastName": "Doe",
          "skypeId": "johndoe_skype",
          "jobTitle": "Software Engineer",
          "emailNotificationsEnabled": true,
          "company": {},
          "id": "jdoe",
          "enabled": true,
          "email": "john.doe@example.com"
        }
      },
      ...
    ]
  }
}

 

You may recall way back in part 2 when we discussed the /nodes API, the properties and aspect names are not present by default but can be included via the include query parameter, the same holds true here as well. If you want to see any custom properties or aspects applied you can add include=properties,aspectNames to the URL. The results can also be sorted by id (username), firstName and lastName.

 

Unfortunately, we ran out of time in the 5.2 release to add filtering capabilities to this endpoint, however, as discussed in the last post, the /queries/people endpoint (3rd request in the Postman collection) or the /search endpoint can be used to look for people and achieve the same thing.

 

To retrieve the full details of the person we created earlier we can use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/jdoe which returns the following response (4th request in the Postman collection):

{
  "entry": {
    "firstName": "John",
    "lastName": "Doe",
    "skypeId": "johndoe_skype",
    "jobTitle": "Software Engineer",
    "emailNotificationsEnabled": true,
    "company": {},
    "id": "jdoe",
    "enabled": true,
    "email": "john.doe@example.com"
  }
}

 

It's also possible to update the details of a person (non administrator users can only update their own details for obvious reasons). The example below shows how we can update the details of the person we created earlier by PUTting the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/jdoe (5th request in the Postman collection):

{
  "firstName": "Johnathon",
  "mobile": "07000 123456"
}

 

Using the same endpoint it's also possible to change a person's password. For security reasons you can only change your own password (unless you're an administrator) and when you do you have to provide the old password together with the new password. The example below (6th request in the Postman collection) changes the password of the person we created earlier to "my-new-password":

{
  "oldPassword": "jdoe",
  "password": "my-new-password"
}

 

The last capability of the people API we're going to cover today is the ability to disable and enable people, this is obviously something only administrators can do. You may have noticed the enabled flag for each person, this can be toggled to set the state of the person. It's also possible to create a person in the disabled state by setting the enabled property to false when the person is created.

 

The example below demonstrates how we can disable the person we created earlier. Again, we use PUT against http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/jdoe to achieve this (7th request in the Postman collection):

{
  "enabled": false
}

 

Now try and make a request using "jdoe" (remember to update the credentials to use the newer password), you'll get a 401 error as disabled users are locked out of the repository.

 

At this time there is no way to remove a person from the system via the v1 REST API, there are several nuances that need further discussion, once these discussions have concluded you can expect to see this capability added to a future release.

 

That concludes our coverage of the people API, I hope you'll join me again next time when we look at the trashcan (deleted nodes) API.

In the last post we covered the Sites APIs, this time we're going to take a look at ways to find things in the repository. There are two main APIs to do this, /queries and /search.

 

As always there is a Postman collection to accompany this post, click the "Run in Postman" button below to import it into your client.

 

 

The first request uses one of the APIs we learnt about last time to create a public site named "queriesSearchSite", the second request retrieves the document library container id and stores it in a global variable. The third request can be used to upload documents to the site, to get the most out of this post upload a text file containing some lorem ipsum text, some image files and some Office documents.

 

The /queries endpoints are designed to be very simple to use and usable in "live search" scenarios i.e. they can executed upon each key press so clients can show results as the user types. The actual query used behind the scenes is hard-coded, if complex or custom queries are required the /search API should be used, which we'll look at shortly.

 

Let's first take a look at the endpoint to find nodes. The http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/queries/nodes endpoint returns nodes (files and folders) that match a simple term provided via a query parameter. The type of nodes returned can be restricted via the nodeType query parameter, for example passing my:type as the value will only return nodes of that type and any of it's subtypes. The query will look in the name, title and description properties, in the content and in tags for a match. Take a look at the API Explorer for the other options available for this endpoint.

 

The 4th request in the Postman collection shows an example of looking for the term "lorem". The number of results you get will depend on the content in your repository, some of the sample site content contains the word "lorem" so you should get a few results! The response format (shown below) is also consistent with the /nodes API so if you've been following the series it should look familiar.

{
  "list": {
    "pagination": {
      "count": 7,
      "hasMoreItems": false,
      "totalItems": 7,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "createdAt": "2017-04-10T09:12:32.761+0000",
          "isFolder": false,
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "modifiedAt": "2017-04-10T09:12:32.761+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "name": "test-lorem-ipsum.txt",
          "id": "3379e95a-fa24-418e-a1df-7d7ef9192516",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "text/plain",
            "mimeTypeName": "Plain Text",
            "sizeInBytes": 3186,
            "encoding": "ISO-8859-1"
          },
          "parentId": "d32682f0-cfd9-43da-ab74-ba78fc59a01a"
        }
      },
      ...
    ]
  }
}

 

To find sites the http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/queries/sites endpoint can be used. The 5th request in the Postman collection shows how to look for sites that have the term "queries" in the site id, title or description. Again, take a look at the API Explorer for other options, including how to order the results.

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "role": "SiteManager",
          "visibility": "PUBLIC",
          "guid": "763588b4-9c6f-4b34-af41-c92a6102711f",
          "description": "Site created for queries and search blog post",
          "id": "queriesSearchSite",
          "preset": "site-dashboard",
          "title": "Queries and Search Site"
        }
      }
    ]
  }
}

 

Finally, to find people (users) the http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/queries/people endpoint can be used. The 6th request in the Postman collection shows how to look for people that have "jackson" in their username (id), first name or last name. As my repository has the sample site loaded the sample user "Mike Jackson" is returned:

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "lastName": "Jackson",
          "userStatus": "Working on a new web design for the corporate site",
          "jobTitle": "Web Site Manager",
          "statusUpdatedAt": "2011-02-15T20:13:09.649+0000",
          "mobile": "012211331100",
          "emailNotificationsEnabled": true,
          "description": "Mike is a demo user for the sample Alfresco Team site.",
          "telephone": "012211331100",
          "enabled": false,
          "firstName": "Mike",
          "skypeId": "mjackson",
          "avatarId": "3fbde500-298b-4e80-ae50-e65a5cbc2c4d",
          "location": "Threepwood, UK",
          "company": {
            "organization": "Green Energy",
            "address1": "100 Cavendish Street",
            "address2": "Threepwood",
            "address3": "UK",
            "postcode": "ALF1 SAM1"
          },
          "id": "mjackson",
          "email": "mjackson@example.com"
        }
      }
    ]
  }
}

 

As with the other queries endpoints the options are intentionally limited, see the API Explorer for details. We will also be looking at the people API in a lot more depth in the next instalment of this series so stay tuned!

 

As mentioned earlier, if the pre-canned queries do not provide what you need you have the option to use the rich and powerful /search API, at the cost of a little more complexity.

 

Due to the number of options and functionality available via the search API, it is a little different than most of the other APIs we've looked at so far in the series. Firstly, the API is defined under the "search" namespace so it's base URL is slightly different. Secondly, the /search endpoint does not accept any query parameters and is therefore completely controlled via the POST body as we'll see in the examples that follow.

 

We'll start by executing a simple search, the 7th request in the Postman collection shows how POSTing the following body to http://localhost:8080/alfresco/api/-default-/public/search/versions/1/search searches for the term "lorem":

{
  "query": {
    "query": "lorem"
  }
}

 

The results should look familiar though, for the most part they are the same as the results from /queries and from /nodes/{id}/children

{
  "list": {
    "pagination": {
      "count": 7,
      "hasMoreItems": false,
      "totalItems": 7,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "isFile": true,
          "createdByUser": {
            "id": "mjackson",
            "displayName": "Mike Jackson"
          },
          "modifiedAt": "2011-03-03T10:31:31.651+0000",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "application/vnd.ms-powerpoint",
            "mimeTypeName": "Microsoft PowerPoint",
            "sizeInBytes": 2898432,
            "encoding": "UTF-8"
          },
          "parentId": "38745585-816a-403f-8005-0a55c0aec813",
          "createdAt": "2011-03-03T10:31:30.596+0000",
          "isFolder": false,
          "search": {
            "score": 1.6137421
          },
          "modifiedByUser": {
            "id": "mjackson",
            "displayName": "Mike Jackson"
          },
          "name": "Project Overview.ppt",
          "location": "nodes",
          "id": "99cb2789-f67e-41ff-bea9-505c138a6b23"
        }
      },
      ...
    ]
  }
}

 

There are a couple of differences though, the search API returns two additional properties, search and location. The search property (line 29) adds extra context for the individual result, in this case, the score.

 

Explaining the full details is beyond the scope of this post but it is possible to search across "live" nodes, deleted nodes and versioned nodes, the location property (line 37) shows from which area the result came from. By default only "live" nodes are included.

 

The example above used the default search language afts (Alfresco Full Text Search), however, cmis and lucene are also supported. The example body below shows how to execute a simple CMIS query (8th request in the Postman collection) to find all content with a name starting with "test.":

{
  "query": {
    "query": "select * from cmis:document WHERE cmis:name LIKE 'test.%'",
    "language": "cmis"
  }
}

 

For completeness, the example body below shows how to execute a simple Lucene query (9th request in the Postman collection) to find all the content modified in the last week:

{
  "query": {
    "query": "+@cm\\:modified:[NOW/DAY-7DAYS TO NOW/DAY+1DAY] +TYPE:\"cm:content\"",
    "language": "lucene"
  }
}

 

As with all the v1 REST APIs paging can also be controlled, it's just done via the body rather than a query parameter. The results can also be sorted. The example body below shows how to execute a search (10th request in the Postman collection) to find all content ordered by the name property, only show 25 results rather than the default of 100 and skip the first 10 results:

{
  "query": {
    "query": "+TYPE:\"cm:content\"",
    "language": "afts"
  },
  "paging": {
    "maxItems": "25",
    "skipCount": "10"
  },
  "sort": [{"type":"FIELD", "field":"cm:name", "ascending":"false"}]
}

 

Now we've covered the basics let's look at a couple of the more interesting features of the search API, faceting and term highlighting.

 

There are two types of facets; queries and fields. A query facet returns the count of results for the given query, you can provide multiple facet queries in one request. A field facet returns a number of "buckets" for a field, providing the count of results that fit into each bucket.

 

It's much easier to understand with an example, the body below shows a search request (11th request in the Postman collection) that will look for content nodes that have a name or title starting with "test". We also specify that we want to know how many of the results are small files, how many are plain text files, how many are images and how many are Office files. Additionally, we are also asking for the creator facet field to be included, which will indicate how many of the results were created by each user:

{
  "query": {
    "query": "(name:\"test*\" OR title:\"test*\") AND TYPE:\"cm:content\""
  },
  "facetQueries": [
    {"query": "content.size:[0 TO 10240]", "label": "Small Files"},
    {"query": "content.mimetype:'text/plain'", "label": "Plain Text"},
    {"query": "content.mimetype:'image/jpeg' OR content.mimetype:'image/png' OR content.mimetype:'image/gif'", "label": "Images"},
    {"query": "content.mimetype:'application/msword' OR content.mimetype:'application/vnd.ms-excel'", "label": "Office"}
  ],
  "facetFields": {"facets": [{"field": "creator"}]}
}

 

The response to this request is shown below:

{
  "list": {
    "pagination": {
      "count": 8,
      "hasMoreItems": false,
      "totalItems": 8,
      "skipCount": 0,
      "maxItems": 100
    },
    "context": {
      "facetQueries": [
        {
          "label": "Office",
          "count": 2
        },
        {
          "label": "Small Files",
          "count": 4
        },
        {
          "label": "Plain Text",
          "count": 1
        },
        {
          "label": "Images",
          "count": 3
        }
      ],
      "facetsFields": [
        {
          "label": "creator",
          "buckets": [
            {
              "label": "test",
              "count": 6,
              "display": "Test User"
            },
            {
              "label": "System",
              "count": 2,
              "display": "System"
            }
          ]
        }
      ]
    },
    "entries": [
      {
        "entry": {
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "modifiedAt": "2017-04-10T09:21:44.499+0000",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "image/gif",
            "mimeTypeName": "GIF Image",
            "sizeInBytes": 3039,
            "encoding": "UTF-8"
          },
          "parentId": "d32682f0-cfd9-43da-ab74-ba78fc59a01a",
          "createdAt": "2017-04-10T09:20:41.665+0000",
          "isFolder": false,
          "search": {
            "score": 2.0050006
          },
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test User"
          },
          "name": "test.gif",
          "location": "nodes",
          "id": "4ba71ad8-8812-4c1a-9d0b-30643dc39c51"
        }
      },
      ...
    ]
  }
}

 

As well as the expected list of nodes, the response also contains a facetQueries and a facetsFields object containing the counts we requested. The facetQueries object has an entry for each query supplied in the result whereas the facetsFields object contains an entry for each requested field which in turn contains the count for each bucket.

 

The last example we're going to look at in this post is term highlighting. The example body below shows a search request (12th request in the Postman collection) that will look for content nodes that have a name or title starting with "test", if the match occurs in either the cm:name or cm:title property the location of the match will be returned in the results. By default, the matched term is highlighted by surrounded by an em tag, to surround the match with something else the prefix and postfix properties can be used as shown in the example below:

{
  "query": {
    "query": "(name:\"test*\" OR title:\"test*\") AND TYPE:\"cm:content\""
  },
  "highlight": {
    "fields": [
      {
        "field": "cm:name",
        "prefix": "(",
        "postfix": ")"
      },
      {
        "field": "{http://www.alfresco.org/model/content/1.0}title"
      }
    ]
  }
}

 

As the highlighting is specific to each individual result the search object we saw earlier is used to return the result as shown below:

{
  "list": {
    "pagination": {
      "count": 8,
      "hasMoreItems": false,
      "totalItems": 8,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "isFile": true,
          "createdByUser": {
            "id": "System",
            "displayName": "System"
          },
          "modifiedAt": "2017-02-20T10:57:28.407+0000",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "application/x-javascript",
            "mimeTypeName": "JavaScript",
            "sizeInBytes": 2271,
            "encoding": "UTF-8"
          },
          "parentId": "a4e9e481-89b5-43da-9389-21314dbb6046",
          "createdAt": "2017-02-20T10:57:28.407+0000",
          "isFolder": false,
          "search": {
            "score": 1.1892484,
            "highlight": [
              {
                "field": "cm:name",
                "snippets": [
                  "example (test) script.js.sample"
                ]
              },
              {
                "field": "{http://www.alfresco.org/model/content/1.0}title",
                "snippets": [
                  "Example <em>Test</em> Script"
                ]
              }
            ]
          },
          "modifiedByUser": {
            "id": "System",
            "displayName": "System"
          },
          "name": "example test script.js.sample",
          "location": "nodes",
          "id": "7e02b810-4bce-4ed6-aff0-3f2f88a5ff82"
        }
      },
      ...
    ]
  }
}

 

As we specified in the request, the match in the name property is surrounded by brackets (line 35) and the em tag surrounds the match in the title property (line 41).

 

We've only just scratched the surface of the capabilities of the search API in this post so I would highly recommend you take a look at the API Explorer and select "Search API" from the drop-down menu to get more details of what's possible.

 

If you're using Community via the installer as instructed in the previous post you will have been using SOLR 4. You may have heard that we also released support for SOLR 6 with 5.2. To learn more please read the SOLR 6 blog posts on this site or visit our documentation site.

 

Next time we're going to take a look at the people API.

Just before Christmas we looked though the collaboration APIs for nodes, after a longer than planned hiatus the series continues with a look at some of the collaboration APIs for sites.

 

Way back in part 1 we installed 5.2.b, it's now time to re-install so that we can take a look at some of the newer APIs in this and future posts. Download and install the latest Community release (5.2.f at the time of writing) and optionally install the latest API Explorer by copying the WAR file as "api-explorer.war" to <install-home>/tomcat/webapps.

 

As has become tradition this post is accompanied by a Postman collection, click the "Run in Postman" button below to import it into your client.

 

 

One of the new APIs to have been added since 5.2.b is the create person API, let's use that to create a test user by POSTing the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people  (1st request in the Postman collection).

{
   "id": "test",
   "firstName": "Test",
   "lastName": "User",
   "email": "test@alfresco.com",
   "password": "test"
}

 

This will result in the response below:

{
  "entry": {
    "firstName": "Test",
    "lastName": "User",
    "emailNotificationsEnabled": true,
    "company": {},
    "id": "test",
    "enabled": true,
    "email": "test@alfresco.com"
}
}

 

As with all previous posts we'll use this user for all subsequent requests unless specified otherwise.

 

OK, let's start by using one of the new endpoints added in 5.2, arguably one of the most anticipated, the ability to create a site that can be used in Share! 

 

To create a standard site with default configuration POST the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites (2nd request in the Postman collection).

{
  "id": "publicSite",
  "title": "Public Site",
  "description": "Public site created for blog post",
  "visibility": "PUBLIC"
}

 

This will result in a response similar to the one below:

{
  "entry": {
    "role": "SiteManager",
    "visibility": "PUBLIC",
    "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
    "description": "Public site created for blog post",
    "id": "publicSite",
    "preset": "site-dashboard",
    "title": "Public Site"
  }
}

 

Now we have created our site how do we add files to the Document Library? Each "page" in a site has a container in which it stores it's data, the Document Library is no different, we can use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/publicSite/containers/documentLibrary  endpoint (3rd request in the Postman collection) to GET the details (see response below), the id property provides the node identifier of the documentLibrary folder that we can then use in other APIs, refer back to part 3 for details on creating new nodes.

{
  "entry": {
    "id": "171f7d8a-09b0-413b-b41a-11e524833323",
    "folderId": "documentLibrary"
  }
}

 

Another new feature for 5.2 is the ability to update a site, to change the description of the site we just created PUT the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/publicSite (4th request in the Postman collection).

{
  "description": "Public site created for blog post - part 8"
}

 

The response below shows that the site description has been updated.

{
  "entry": {
    "role": "SiteManager",
    "visibility": "PUBLIC",
    "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
    "description": "Public site created for blog post - part 8",
    "id": "publicSite",
    "preset": "site-dashboard",
    "title": "Public Site"
  }
}

 

Although not demonstrated above it's also possible for a site manager to update the site's title and visibility properties, for example, to make a public site private.

 

Now let's turn our attention to site membership, to prepare for that we need to create a moderated site and create a second test user.

 

We create a moderated site in the same way we created the public site earlier except we pass "MODERATED" as the value for the visibility property as shown below (5th request in the Postman collection).

{
  "id": "moderatedSite",
  "title": "Moderated Site",
  "description": "Moderated site created for blog post",
  "visibility": "MODERATED"
}

 

The visibility property is an enum, the Alfresco API Explorer can be a great help here. Use the link or navigate to the POST /sites method and scroll down to the siteBodyCreate body parameter. Click the "Model" link on the right hand side to reveal the definition, this will show the allowed values for the enum.

 

Go ahead and create a second test user by POSTing the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people (6th request in the Postman collection).

{
  "id": "test2",
  "firstName": "Test",
  "lastName": "User2",
  "password": "test2"
  "email": "test2@alfresco.com"
}

 

We'll now use this new user to join the public site we created earlier and the moderated site we just created. The same endpoint is used to do both these actions http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/test2/site-membership-requests. Join the public site by POSTing the body below (7th request in the Postman collection).

{
  "id": "publicSite"
}

 

This will return a response similar to the one shown below:

{
  "entry": {
    "createdAt": "2017-02-24T09:52:09.052+0000",
    "site": {
      "role": "SiteConsumer",
      "visibility": "PUBLIC",
      "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
      "description": "Public site created for blog post - part 8",
      "id": "publicSite",
      "preset": "site-dashboard",
      "title": "Public Site"
    },
    "id": "publicSite"
  }
}

 

As the site is public we are added immediately with the role of SiteConsumer, which can be seen in line 5.

 

Attempt to join the moderated site by POSTing the body below (8th request in the Postman collection).

{
  "id": "moderatedSite",
  "message": "I would like to join this site as it looks interesting"
}

 

This will return a response similar to the one below.

{
  "entry": {
    "createdAt": "2017-02-24T10:16:21.524+0000",
    "site": {
      "visibility": "MODERATED",
      "guid": "46b6504d-27e9-4137-b680-fb0be986942d",
      "description": "Moderated site created for blog post",
      "id": "moderatedSite",
      "preset": "site-dashboard",
      "title": "Moderated Site"
    },
    "id": "moderatedSite",
    "message": "I would like to join this site as it looks interesting"
  }
}

 

The response is similar to the one we got when joining the public site with one notable exception, there is no role property, this is because you haven't actually joined the site yet, your request has been sent to the site managers for approval.

 

To check your pending site membership requests you can GET http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/test2/site-membership-requests (9th request in the Postman collection). You can manage your site membership requests using the /people/test2/site-membership-requests/{siteId} endpoint, refer to the API Explorer for details.

Site managers can not currently accept or reject site invitations via the /sites API, this has to be done via the workflow API, I've added a story to our backlog to add this missing functionality.

 

Now we're a member of the public site let's see who else is. If you GET http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/publicSite/members (10th request in the Postman collection) you'll get the following response.

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "role": "SiteManager",
          "person": {
            "firstName": "Test",
            "lastName": "User",
            "emailNotificationsEnabled": true,
            "company": {
             
            },
            "id": "test",
            "enabled": true,
            "email": "test@alfresco.com"
          },
          "id": "test"
        }
      },
      {
        "entry": {
          "role": "SiteConsumer",
          "person": {
            "firstName": "Test",
            "lastName": "User2",
            "emailNotificationsEnabled": true,
            "company": {
             
            },
            "id": "test2",
            "enabled": true,
            "email": "test2@alfresco.com"
          },
          "id": "test2"
        }
      }
    ]
  }
}

 

This shows that the "test" user is the site manager (lines 12 and 20) and the "test2" user is a site consumer (lines 29 and 37).

 

To see all the sites you are a member of the http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/test2/sites endpoint can be used (11th request in the Postman collection), this will give the following response.

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "site": {
            "role": "SiteConsumer",
            "visibility": "PUBLIC",
            "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
            "description": "Public site created for blog post - part 8",
            "id": "publicSite",
            "preset": "site-dashboard",
            "title": "Public Site"
          },
          "role": "SiteConsumer",
          "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
          "id": "publicSite"
        }
      }
    ]
  }
}

 

As our request to join the moderated site has not been approved yet we are only a member of one site, the public site we joined earlier.

 

So how do you find sites to join? 

 

To list all public and moderated sites in the system the http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites endpoint can be used (12th request in the Postman collection), this will return the following response.

{
  "list": {
    "pagination": {
      "count": 3,
      "hasMoreItems": false,
      "totalItems": 3,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "visibility": "MODERATED",
          "guid": "46b6504d-27e9-4137-b680-fb0be986942d",
          "description": "Moderated site created for blog post",
          "id": "moderatedSite",
          "preset": "site-dashboard",
          "title": "Moderated Site"
        }
      },
      {
        "entry": {
          "role": "SiteConsumer",
          "visibility": "PUBLIC",
          "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
          "description": "Public site created for blog post - part 8",
          "id": "publicSite",
          "preset": "site-dashboard",
          "title": "Public Site"
        }
      },
      {
        "entry": {
          "visibility": "PUBLIC",
          "guid": "b4cff62a-664d-4d45-9302-98723eac1319",
          "description": "This is a Sample Alfresco Team site.",
          "id": "swsdp",
          "preset": "site-dashboard",
          "title": "Sample: Web Site Design Project"
        }
      }
    ]
  }
}

 

If you are a member of any of the sites your role in that site is indicated (line 23). The other way to find sites is to search for them and there are a couple of ways to do that. You could use the new Search API which I'll be covering in the next post or you can use the Queries API. We'll also be covering the new Queries API in a future post so I'll just briefly mention it now.

 

The /queries/sites endpoint is "pre-canned" query for sites, you pass a single term as a query parameter. For example, calling http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/queries/sites?term=public will return a response similar to the one below (13th request in the Postman collection).

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "role": "SiteConsumer",
          "visibility": "PUBLIC",
          "guid": "69a5a7b1-a338-4e11-8a61-115edd1190e6",
          "description": "Public site created for blog post - part 8",
          "id": "publicSite",
          "preset": "site-dashboard",
          "title": "Public Site"
        }
      }
    ]
  }
}

 

The last thing to cover in this section is how to leave a site you've previously joined. As you might expect a DELETE endpoint is used to do this. To leave the "publicSite" we joined earlier use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/test2/sites/publicSite (14th request in the Postman collection). A 204 response is returned if you successfully left the site.

 

For the last section of this post we're going to take a look at some of the other endpoints available to the site manager. 

 

A manager of a site is able to add members with a specific role. To add "test2" as a member of the "publicSite" created earlier with the "SiteContributor" role we can POST the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/publicSite/members. Remember to switch your credentials back to the "test" user or use the 15th request in the Postman collection.

{
  "id": "test2",
  "role": "SiteContributor"
}

Allowable values for the role property are SiteManager, SiteCollaborator, SiteContributor or SiteConsumer

 

Now we've added test2 to the site let's say we want to change their role to be a site manager, this can be accomplished by PUTing the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/publicSite/members/test2 (16th request in the Postman collection).

{
  "role": "SiteManager"
}

 

Finally, to remove the "publicSite" site use a DELETE request against http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/sites/publicSite (last request in the Postman collection), you should receive a 204 status code. 

 

We've covered a majority of the Sites API in this post but there is more so I'd encourage you to examine the API Explorer and experiment.

 

In the next instalment of the series we're going to take a look at the new queries and search APIs.

In the last post we took a look at associations, this time we're going to cover some of the collaboration APIs that have been present for a few releases, namely comments, ratings and tags.

 

As usual this post is accompanied by a Postman collection, click the "Run in Postman" button below to import it into your client.

 

 

We'll need some content to use throughout this post, either refer back to part 3, execute the first request in the Postman collection or POST the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children to create a node:

{
  "name": "collaboration.txt",
  "nodeType": "cm:content"
}

 

Let's start by creating a comment on our content (2nd request in the Postman collection), POST the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/comments:

{
  "content": "This is my comment"
}

 

This will result in a response similar to the one below:

{
  "entry": {
    "createdAt": "2016-12-13T18:15:30.450+0000",
    "createdBy": {
      "firstName": "Test",
      "lastName": "Test",
      "emailNotificationsEnabled": true,
      "company": {
       
      },
      "id": "test",
      "enabled": true,
      "email": "test@alfresco.com"
    },
    "edited": false,
    "modifiedAt": "2016-12-13T18:15:30.450+0000",
    "canEdit": true,
    "modifiedBy": {
      "firstName": "Test",
      "lastName": "Test",
      "emailNotificationsEnabled": true,
      "company": {
       
      },
      "id": "test",
      "enabled": true,
      "email": "test@alfresco.com"
    },
    "canDelete": true,
    "id": "42fbc51b-3e9a-4237-ad23-bfd8c6bcbc6a",
    "content": "This is my comment"
  }
}

 

Take a copy of the newly created id as we'll need it shortly, hereinafter referred to as "fileId".

 

To retrieve all the comments on a node we use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/comments (3rd request in the Postman collection), at the moment our node only has the one comment:

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "createdAt": "2016-12-13T18:15:30.450+0000",
          "createdBy": {
            "firstName": "Test",
            "lastName": "Test",
            "emailNotificationsEnabled": true,
            "company": {
             
            },
            "id": "test",
            "enabled": true,
            "email": "test@alfresco.com"
          },
          "edited": false,
          "modifiedAt": "2016-12-13T18:15:30.450+0000",
          "canEdit": true,
          "modifiedBy": {
            "firstName": "Test",
            "lastName": "Test",
            "emailNotificationsEnabled": true,
            "company": {
             
            },
            "id": "test",
            "enabled": true,
            "email": "test@alfresco.com"
          },
          "canDelete": true,
          "id": "42fbc51b-3e9a-4237-ad23-bfd8c6bcbc6a",
          "content": "This is my comment"
        }
      }
    ]
  }
}

 

Each comment has a canEdit (line 27) and canDelete (line 39) property, this can be used to determine whether the current user has the permission to edit or delete that comment, respectively. 

 

What if we made a typo in our comment and we need to update it, we can achieve that by using PUT with a Content-Type of application/json to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/comments/{{commentId}} (4th request in the Postman collection). Send the following body to update the content text to "Updated comment".

{
  "content": "Updated comment"
}

 

Presuming we have permission to delete a comment we can do so by sending a DELETE request to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/comments/{{commentId}} (5th request in the Postman collection).

 

Now let's take a look at "liking" a node. To do this we use the ratings API, likes is one of the rating schemes available out-of-the-box, fiveStar is the other, although this is not currently used by any Alfresco client. An explanation of rating schemes is beyond the scope of this article so I'll that as an exercise for the reader.

 

To "like" a node we can POST the body below to: http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/ratings (6th request in the Postman collection). The value required for the myRating property depends on the rating scheme, for likes it's a boolean flag.

{
  "id": "likes",
  "myRating": true
}

 

This will result in the following response. It shows our rating (line 3), the rating type we applied (line 5) and the number of times this node has been rated (line 7) with that particular scheme.

{
  "entry": {
    "myRating": true,
    "ratedAt": "2016-12-13T19:28:23.792+0000",
    "id": "likes",
    "aggregate": {
      "numberOfRatings": 1
    }
  }
}

 

If we retrieve the ratings for the node using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/ratings (7th request in the Postman collection) we'll see an entry for each rating scheme installed in the repository. If you've rated the node with one of the schemes the myRating property will be present (line 21). Each scheme also shows the total number of ratings applied (lines 15 and 25).

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "totalItems": 2,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "id": "fiveStar",
          "aggregate": {
            "numberOfRatings": 0
          }
        }
      },
      {
        "entry": {
          "myRating": true,
          "ratedAt": "2016-12-13T19:28:23.792+0000",
          "id": "likes",
          "aggregate": {
            "numberOfRatings": 1
          }
        }
      }
    ]
  }
}

 

If you rate the node again using another user and retrieve the ratings again you'll notice the numberOfRatings property on line 25 change to 2.

 

So what if we change our mind and decide that we no longer "like" the node, how do we "unlike" it? To do this we DELETE the rating scheme from the node using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/ratings/likes (8th request in the Postman collection). This doesn't remove the whole rating scheme, it just removes our rating from the node.

 

If we retrieve just the likes ratings for the node using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/ratings/likes (9th request in the Postman collection) you'll notice the myRating property has been removed and the numberOfRatings count has decreased as shown below:

{
  "entry": {
    "id": "likes",
    "aggregate": {
      "numberOfRatings": 0
    }
  }
}

 

Let's now turn our attention to tags. To add a tag to a node we simply POST the body shown below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/tags (10th request in the Postman collection):

{
  "tag": "blog"
}

 

This results in the following response:

{
  "entry": {
    "tag": "blog",
    "id": "ecbe5049-a879-4b96-8c25-c8a776e43464"
  }
}

 

Add another tag called "post" to the same node (11th request in the Postman collection). We can then retrieve the tags applied to the node by using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/tags (12th request in the Postman collection), you should see a response similar to the one below:

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "totalItems": 2,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "tag": "blog",
          "id": "2c1be151-bbae-418a-9ddc-541a6a867c94"
        }
      },
      {
        "entry": {
          "tag": "post",
          "id": "9e41ee8b-fdac-4cbd-bde1-082b486ccc7c"
        }
      }
    ]
  }
}

 

When we add a tag to a node we're actually creating a global tag in the repository which we can see by retrieving all tags using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/tags (13th request in the Postman collection). If you look closely at the response you'll notice the id of the tags are the same as the ones returned for the node above.

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "tag": "blog",
          "id": "2c1be151-bbae-418a-9ddc-541a6a867c94"
        }
      },
      {
        "entry": {
          "tag": "post",
          "id": "9e41ee8b-fdac-4cbd-bde1-082b486ccc7c"
        }
      }
    ]
  }
}

 

As tags are global to the repository removing them from a node does not delete the tag itself, let's see this in action by removing the "blog" tag from our node. To do this we need to DELETE using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{fileId}}/tags/{{tagId}} (14th request in the Postman collection) where tagId is "2c1be151-bbae-418a-9ddc-541a6a867c94".

 

Following a successful 204 response retrieve the tags for the node and the repository (12th and 13th requests in the Postman collection) and you'll see the "blog" tag has gone from the node but is still in the repository and available for use on other nodes.

 

Those of you paying close attention would have noticed that we only supplied the text of the tag when we added it to our node earlier. Before creating a new tag the API will first look for a matching existing tag and use that if it's present, meaning clients do not have to keep track of the underlying id of the tag.

 

That concludes our overview of the collaboration APIs for nodes, in the next post we'll look at the collaboration APIs for sites.

In previous posts we've looked at navigation, creation, management and versioning of nodes, this time we're going to look at the last set of functionality exposed via /nodes and that's associations. This is quite a long post as we have a lot to cover so grab yourself a coffee!

 

For this post we're going to use a custom model. I'm going to presume you already have knowledge of Alfresco content modelling, if not read this introduction first.


For those of you who've been in the Alfresco for a while may remember the Forms Development Kit (FDK) custom model. It was removed back in the 5.0 release so I've resurrected the custom model and packaged it as a simple module. Download the JAR file, copy it to $INSTALL_HOME/modules/platform and re-start the Tomcat Server.

 

Before we start trying to use the model let's take a quick look at the part we're going to use.

 

As you can see in the diagram above the model defines a type called fdk:gadget which extends cm:content. The type might be used to represent a review of a gadget in a magazine. It defines a number of properties (not shown for brevity), peer associations (blue arrows) and child associations (green arrows).

 

The full type definition is shown below, you can also look at the source in my Github repo or download the source JAR from nexus.

<type name="fdk:gadget">
  <parent>cm:content</parent>
  <properties>
    <property name="fdk:make">
      <type>d:text</type>
      <mandatory>true</mandatory>
    </property>
    <property name="fdk:model">
      <type>d:text</type>
      <mandatory>true</mandatory>
    </property>
    <property name="fdk:summary">
      <type>d:text</type>
      <mandatory>true</mandatory>
      <constraints>
        <constraint ref="fdk:summary" />
      </constraints>
    </property>
    <property name="fdk:type">
      <type>d:text</type>
      <constraints>
        <constraint ref="fdk:type" />
      </constraints>
    </property>
    <property name="fdk:subType">
      <type>d:text</type>
      <constraints>
        <constraint ref="fdk:subType" />
      </constraints>
    </property>
    <property name="fdk:rrp">
      <type>d:float</type>
    </property>
    <property name="fdk:releaseDate">
      <type>d:datetime</type>
    </property>
    <property name="fdk:endOfLifeDate">
      <type>d:date</type>
    </property>
    <property name="fdk:retailers">
      <type>d:text</type>
      <multiple>true</multiple>
    </property>
    <property name="fdk:rating">
      <type>d:int</type>
      <constraints>
        <constraint ref="fdk:percentage" />
      </constraints>
    </property>
  </properties>
  <associations>
    <association name="fdk:contact">
      <source>
        <mandatory>false</mandatory>
        <many>true</many>
      </source>
      <target>
        <class>cm:person</class>
        <mandatory>false</mandatory>
        <many>false</many>
      </target>
    </association>
    <association name="fdk:reviews">
      <source>
        <mandatory>false</mandatory>
        <many>true</many>
      </source>
      <target>
        <class>cm:content</class>
        <mandatory>false</mandatory>
        <many>true</many>
      </target>
    </association>
    <association name="fdk:company">
      <source>
        <mandatory>false</mandatory>
        <many>true</many>
      </source>
      <target>
        <class>fdk:company</class>
        <mandatory>false</mandatory>
        <many>false</many>
      </target>
    </association>
    <child-association name="fdk:pressRelease">
      <source>
        <mandatory>false</mandatory>
        <many>true</many>
      </source>
      <target>
        <class>cm:content</class>
        <mandatory>false</mandatory>
        <many>false</many>
      </target>
    </child-association>
    <child-association name="fdk:images">
      <source>
        <mandatory>false</mandatory>
        <many>true</many>
      </source>
      <target>
        <class>cm:content</class>
        <mandatory>true</mandatory>
        <many>true</many>
      </target>
    </child-association>
  </associations>
</type>

 

As usual a Postman collection accompanies this post, click the "Run in Postman" button below to import it into your client.

 

 

So let's start by trying to create an instance of an fdk:gagdet node in the test users home folder, try POSTing the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children (1st request in the Postman collection):

{
  "name": "fdk.txt",
  "nodeType": "fdk:gadget"
}

 

Unfortunately we get the error message below. If you look closely at the definition of the fdk:images child association you'll notice that the target is mandatory and we haven't provided anything!

{
  "error": {
    "errorKey": "framework.exception.ApiDefault",
    "statusCode": 422,
    "briefSummary": "10190002 Found 1 integrity violations:\nThe association child multiplicity has been violated: \n   Source Node: workspace:\/\/SpacesStore\/0fe758d7-3c1d-40c0-8c02-5805ca895351\n   Association: Association[ class=ClassDef[name={http:\/\/www.alfresco.org\/model\/fdk\/1.0}gadget], name={http:\/\/www.alfresco.org\/model\/fdk\/1.0}images, target class={http:\/\/www.alfresco.org\/model\/content\/1.0}content, source role=null, target role=null]\n   Required child Multiplicity: 1..*\n   Actual child Multiplicity: 0",
    "stackTrace": "For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.",
    "descriptionURL": "https:\/\/api-explorer.alfresco.com"
  }
}

 

Note: If you get an error saying "fdk:gadget isn't a valid QName" it means you haven't copied the simple JAR to the correct location.

 

We need something to "associate" our new node with so let's create an "Images" folder, upload some images to it and upload some content in the home folder to use as a review.

 

I won't detail all the API calls to do that here, either refer back to part 3 or examine requests 2 through 5 in the Postman collection. If you do this yourself, copy the id of each of the nodes you create as we'll need them later.

 

Let's also create an fdk:company object so that we can create an fdk:company association. I've uploaded some images of the Amazon Echo so lets create a node representing Amazon in the home folder by POSTing the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children (6th request in the Postman collection):

{
  "name": "Amazon",
  "nodeType": "fdk:company",
  "properties": {
    "fdk:email": "info@amazon.com",
    "fdk:url": "http://www.amazon.com",
    "fdk:city": "Seattle"
  }
}

 

Now we're finally ready to create our gadget node. We can specify the child associations using the secondaryChildren property and the peer associations using the targets property.

 

The body below POSTed to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children (7th request in the Postman collection) will create a gadget node named "Amazon Echo" in the home folder. It will also create two fdk:images child associations to the images we uploaded earlier (lines 7 and 11), an fdk:reviews peer association to the review text (line 17) and an fdk:company peer association to the company node (line 21).

{
  "name": "Amazon Echo",
  "nodeType": "fdk:gadget",
  "secondaryChildren": [
    {
      "childId": "{{firstImageId}}",
      "assocType": "fdk:images"
    },
    {
      "childId": "{{secondImageId}}",
      "assocType": "fdk:images"
    }
  ],
  "targets": [
    {
      "targetId": "{{reviewTextId}}",
      "assocType": "fdk:reviews"
    },
    {
      "targetId": "{{companyId}}",
      "assocType": "fdk:company"
    }
  ]
}

 

Now that we've provided the mandatory information we get a successful 201 response and a representation of the new node we created. Copy the id of the gadget node, hereinafter referred to as gadgetId.

 

There's no mention of the new associations we specified, so how do we know they were created?

 

As you may have guessed there are endpoints to return them. To get a list of the targets (peer associations) we created use the URL http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/targets (8th request in the Postman collection), this returns the following response:

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "totalItems": 2,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "createdAt": "2016-11-19T10:07:11.559+0000",
          "isFolder": false,
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "modifiedAt": "2016-11-19T10:07:11.559+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "review-text.txt",
          "association": {
            "assocType": "fdk:reviews"
          },
          "id": "85f6e9c7-0271-446f-b817-24c6d9fd338a",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "text\/plain",
            "mimeTypeName": "Plain Text",
            "sizeInBytes": 3186,
            "encoding": "ISO-8859-1"
          },
          "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f"
        }
      },
      {
        "entry": {
          "createdAt": "2016-11-19T10:17:25.997+0000",
          "isFolder": false,
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "modifiedAt": "2016-11-19T10:17:25.997+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "Amazon",
          "association": {
            "assocType": "fdk:company"
          },
          "id": "7bbbaf43-e295-46d9-9dd2-df999e42dcf9",
          "nodeType": "fdk:company",
          "content": {
            "mimeType": "application\/octet-stream",
            "mimeTypeName": "Binary File (Octet Stream)",
            "sizeInBytes": 0,
            "encoding": "UTF-8"
          },
          "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f"
        }
      }
    ]
  }
}

 

The response shows the nodes at the end of the association and which association it is (lines 27 and 56). We can also combine some of the techniques we've learnt in previous posts and just request the fdk:reviews association and show the path and properties of the target node using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/targets?include=properties,path&where=(assocType='fdk:reviews') (9th request in the Postman collection).

 

We can retrieve the list of secondary child associations by using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/secondary-children (10th request in the Postman collection). The response is similar to the previous example except we're seeing the two fdk:images child associations (lines 28 and 58).

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "totalItems": 2,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "createdAt": "2016-11-19T10:00:27.038+0000",
          "isFolder": false,
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "modifiedAt": "2016-11-19T10:23:12.485+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "alexa.jpg",
          "association": {
            "isPrimary": false,
            "assocType": "fdk:images"
          },
          "id": "e7f8a006-e027-4f06-8c30-0f5f11bc9211",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "image\/jpeg",
            "mimeTypeName": "JPEG Image",
            "sizeInBytes": 24397,
            "encoding": "UTF-8"
          },
          "parentId": "c67c8ea2-7ec3-415e-9a27-94d6cb58b2ec"
        }
      },
      {
        "entry": {
          "createdAt": "2016-11-19T10:01:53.353+0000",
          "isFolder": false,
          "isFile": true,
          "createdByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "modifiedAt": "2016-11-19T10:23:12.564+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "amazon-echo-alexa.jpg",
          "association": {
            "isPrimary": false,
            "assocType": "fdk:images"
          },
          "id": "6733f33f-f708-4747-acc5-1ed5147d0cb2",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "image\/jpeg",
            "mimeTypeName": "JPEG Image",
            "sizeInBytes": 81789,
            "encoding": "UTF-8"
          },
          "parentId": "c67c8ea2-7ec3-415e-9a27-94d6cb58b2ec"
        }
      }
    ]
  }
}

 

What if we want to go in the other direction and see what links to a particular node, for peer associations we can use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{reviewTextId}}/sources (11th request in the Postman collection) to see what links to the review text content, you should see it's the "Amazon Echo" node.

 

For child associations we use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{firstImageId}}/parents (12th request in the Postman collection) to see the parents of the first image we uploaded earlier, you should see it has 2 parents, the "Amazon Echo" node and folder where the image itself was uploaded.

 

It's also possible to create associations on nodes that already exist, let's add some more to our "Amazon Echo" fdk:gadget node. Before we do that though upload another image to the images folder and some more content to represent another review (13th and 14th requests in the Postman collection) and copy the ids of the new nodes.

 

To create another child association we use the same URL we used earlier to retrieve the list of secondary child associations (http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/secondary-children) except this time we POST to it (15th request in the Postman collection). Sending the body below will create another child association to the 3rd image:

{
  "childId": "{{thirdImageId}}",
  "assocType": "fdk:images"
}

 

We can do the same thing to create another peer association by POSTing the body below to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/targets (16th request in the Postman collection):

{
  "targetId": "{{secondReviewTextId}}",
  "assocType": "fdk:reviews"
}

 

If you now do a GET on the same URLs you should now see three fdk:images child associations and three peer associations, two of type fdk:reviews and one of type fdk:company.

 

Alfresco has a feature called multi-filing, this is where a node can appear in multiple folders, think of it as a unix symbolic link. This feature has been available via the CMIS API for a long time but we've now exposed this via the v1 REST API too.

 

When we navigate around the repository we're actually following the cm:contains child association, to make a node appear in multiple folders we can create a secondary child association from the folder to the node. To make the review text we uploaded earlier also appear in the images folder POST the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{imagesFolderId}}/secondary-children (17th request in the Postman collection):

{
  "childId": "{{reviewTextId}}",
  "assocType": "cm:contains"
}

 

Now request a listing of the images folder's children using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{imagesFolderId}}/children?include=association (18th request in the Postman collection). The trimmed response below shows that the review-text.txt node now also appears in the images folder (line 44). By asking for the association information to be included we can see that the review-text.txt node is a secondary child association via the isPrimary flag (line 46), this allows clients to handle these "linked" nodes differently i.e. restrict deletion.

{
  "list": {
    "pagination": {
      "count": 4,
      "hasMoreItems": false,
      "totalItems": 4,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          ...
          "name": "alexa.jpg",
          "association": {
            "isPrimary": true,
            "assocType": "cm:contains"
          }
        }
      },
      {
        "entry": {
          ...
          "name": "amazon-echo-alexa.jpg",
          "association": {
            "isPrimary": true,
            "assocType": "cm:contains"
          }
        }
      },
      {
        "entry": {
          ...
          "name": "echo-dot.jpg",
          "association": {
            "isPrimary": true,
            "assocType": "cm:contains"
          }
        }
      },
      {
        "entry": {
          ...
          "name": "review-text.txt",
          "association": {
            "isPrimary": false,
            "assocType": "cm:contains"
          }
        }
      }
    ]
  }
}

 

Using the where clause we can actually hide these "linked" nodes completely, try http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{imagesFolderId}}/children?where=(isPrimary=true) (19th request in the Postman collection) and you'll notice only the three images are returned.

 

The last thing to cover is deleting associations, let's start by removing the second review from our fdk:gadget node. To do this we send a DELETE request to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/targets/{{secondReviewTextId}}?assocType=fdk:reviews (20th request in the Postman collection).

 

We can do the same thing for child associations using DELETE http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{{gadgetId}}/secondary-children/{{thirdImageId}}?assocType=fdk:images (21st request in the Postman collection), this will remove the child association between the fdk:gagdet node and the third image we uploaded.

 

Although it's not mandatory it's important to call out the assocType query parameter, this defines which type of associations to remove, if we omit this parameter ALL associations (peer or child depending on the URL used) between the two nodes are removed.

 

Well done if you made it this far, I did warn you this one was going to be a long post!

 

That concludes our journey through the new /nodes endpoints added for the 5.2 release, next time we're going to cover some of the collaboration endpoints that have been in the product for a few releases now.

In the last post we looked at how to retrieve, update and delete nodes, this time we're going to concentrate on versioning.

 

 

As always there is a Postman collection to accompany this post. To import the collection click on the "Run in Postman" button below.

 

Let's start by creating an empty file like we did in the last post by POSTing the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children (1st request in the Postman collection):

 

{
  "name": "version.txt",
  "nodeType": "cm:content"
}

 

If you examine the response below you'll notice there's no mention of versioning at all, that's because empty files are created without versioning enabled.

Note: If you want to create content with versioning enabled by default use multipart/form-data instead of JSON, refer back to part 3 or see http://localhost:8080/api-explorer/#!/nodes/addNode for details.

{
  "entry": {
    "aspectNames": [
      "cm:auditable"
    ],
    "createdAt": "2016-11-10T21:35:04.389+0000",
    "isFolder": false,
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-11-10T21:35:04.389+0000",
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "version.txt",
    "id": "fa100bae-9903-44e4-9e19-5a8523be7422",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text\/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 0,
      "encoding": "UTF-8"
    },
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f"
  }
}

 

Now let's update the content like we did in the last post but this time also provide the majorVersion and comment query parameters.

 

To do this, PUT the body below with a Content-Type of text/plain to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/content?majorVersion=true&comment=First version (2nd request in Postman collection):

Note: You'll obviously need to replace fa100bae-9903-44e4-9e19-5a8523be7422 with the id from your response throughout this post.

This is the initial content for the file.

 

This should return a response similar to the one below:

 

{
  "entry": {
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-11-10T21:48:28.014+0000",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text\/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 41,
      "encoding": "ISO-8859-1"
    },
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f",
    "aspectNames": [
      "cm:versionable",
      "cm:titled",
      "cm:auditable",
      "cm:author"
    ],
    "createdAt": "2016-11-10T21:35:04.389+0000",
    "isFolder": false,
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "version.txt",
    "id": "fa100bae-9903-44e4-9e19-5a8523be7422",
    "properties": {
      "cm:versionLabel": "1.0",
      "cm:versionType": "MAJOR"
    }
  }
}

 

You can see from the response that the cm:versionable aspect has been applied (line 18) and two version properties have been set (lines 32 and 33), cm:versionLabel and cm:versionType.

 

Now versioning is enabled we can retrieve the version history for the file by using the URL http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/versions (3rd request in the Postman collection):

 

{
  "list": {
    "pagination": {
      "count": 1,
      "hasMoreItems": false,
      "totalItems": 1,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "isFolder": false,
          "isFile": true,
          "modifiedAt": "2016-11-10T21:48:28.014+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "version.txt",
          "versionComment": "First version",
          "id": "1.0",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "text\/plain",
            "mimeTypeName": "Plain Text",
            "sizeInBytes": 41,
            "encoding": "ISO-8859-1"
          }
        }
      }
    ]
  }
}

 

We can see from the response above that there's only one version right now so let's create another one. This time though we'll create a minor version by using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/content?majorVersion=false&comment=Second version (4th request in Postman collection) and setting the content to:

 

This is the second version of the content, v1.1.

Now retrieve the version history again and you should see a response like the one shown below. This is also where you'll see the comments we specified (lines 21 and 42) when creating new versions.

 

{
  "list": {
    "pagination": {
      "count": 2,
      "hasMoreItems": false,
      "totalItems": 2,
      "skipCount": 0,
      "maxItems": 100
    },
    "entries": [
      {
        "entry": {
          "isFolder": false,
          "isFile": true,
          "modifiedAt": "2016-11-10T22:03:11.658+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "version.txt",
          "versionComment": "Second version",
          "id": "1.1",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "text\/plain",
            "mimeTypeName": "Plain Text",
            "sizeInBytes": 48,
            "encoding": "ISO-8859-1"
          }
        }
      },
      {
        "entry": {
          "isFolder": false,
          "isFile": true,
          "modifiedAt": "2016-11-10T21:48:28.014+0000",
          "modifiedByUser": {
            "id": "test",
            "displayName": "Test Test"
          },
          "name": "version.txt",
          "versionComment": "First version",
          "id": "1.0",
          "nodeType": "cm:content",
          "content": {
            "mimeType": "text\/plain",
            "mimeTypeName": "Plain Text",
            "sizeInBytes": 41,
            "encoding": "ISO-8859-1"
          }
        }
      }
    ]
  }
}

 

As you'd expect, if we retrieve the content for the node using http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/content (5th request in Postman collection) we get:

 

This is the second version of the content, v1.1.


We can still get the content of the initial version though, to do this we have to use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/versions/1.0/content (6th request in the Postman collection), this gives us our original content:

 

This is the initial content for the file.


Imagine we change our mind and decide we want to revert to a previous version. To revert to version 1.0 we have to POST the following body to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/versions/1.0/revert (7th request in the Postman collection):

 

{
  "majorVersion": true,
  "comment": "Reverted to original"
}


We are able to specify whether the reverted version will create a new minor or major version and again provide a comment describing the reason for the additional version.

 

If you get the content now it should be back to what it was when we originally created the file and if you get the version history you'll see we now have an extra version, a 2.0.

 

Now, what if you wanted to make some changes to a file and not let anyone else make changes until you've finished?

 

For this situation we can lock the file by POSTing an empty JSON object (see below) to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/lock (8th request in the Postman collection).

 

{}


This results in the response below, which shows the node has been locked (lines 31, 32 and 38).

 

{
  "entry": {
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-11-10T22:07:41.857+0000",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text\/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 41,
      "encoding": "ISO-8859-1"
    },
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f",
    "aspectNames": [
      "cm:versionable",
      "cm:lockable",
      "cm:auditable"
    ],
    "createdAt": "2016-11-10T21:35:04.389+0000",
    "isFolder": false,
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "version.txt",
    "id": "fa100bae-9903-44e4-9e19-5a8523be7422",
    "properties": {
      "cm:lockType": "WRITE_LOCK",
      "cm:lockOwner": {
        "id": "test",
        "displayName": "Test Test"
      },
      "cm:versionType": "MAJOR",
      "cm:versionLabel": "2.0",
      "cm:lockLifetime": "PERSISTENT"
    }
  }
}

 

There are a few options when using lock, see http://localhost:8080/api-explorer/#!/nodes/lockNode for more details. It's also possible to include an isLocked property when retrieving a node or a children listing so that your client does not need to parse these properties.

 

As the owner of the lock we can make changes to the file including the content, use the same update content URL we used earlier (2nd or 4th request in the Postman collection) to update the content and generate a new version.

 

However, if you try the same request as another user you'll get a 409 Conflict error response.

 

To unlock the file once you're done with your changes you can POST an empty JSON object (see below) to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/fa100bae-9903-44e4-9e19-5a8523be7422/unlock (9th request in the Postman collection)

 

{}


We've almost covered all the functionality provided by the /nodes API, next time we'll look at the last remaining area, associations.

In the last post we looked at how to create files and folders in the repository, this time we're going to retrieve and update node information, retrieve and update content and remove nodes from the repository.

 

To keep with tradition, all of the endpoints we'll cover in this post have been provided in a Postman collection and can be imported by clicking on the "Run in Postman" button below.

 

button.svg

 

There is something a little different about this one though, it uses the testing capabilities of Postman. After the create request is executed some JavaScript is run to grab the id of the newly created file and store it in a global variable. The URL of subsequent requests in the collection then refer to the global variable using Postman's {{variable}} syntax. Explaining the full testing capabilities of Postman is beyond the scope of this blog post so I'll leave that as an exercise for the reader!

 

OK, let's start by creating an empty file. POST the body below using a Content-Type of application/json to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children

 

{
  "name": "content.txt",
  "nodeType": "cm:content",
  "properties": {
    "cm:title": "The Title"
  }
}

 

Copy the value of the id property from the resulting response (or use the first request in the Postman collection).

 

Now let's retrieve some information about this node. We do this by doing a GET on http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9 (you'll obviously need to replace the id with the one you copied from your response or use the 2nd request in the Postman collection). This results in the following response:

 

{
  "entry": {
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-11-01T14:57:24.388+0000",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text\/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 0,
      "encoding": "UTF-8"
    },
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f",
    "aspectNames": [
      "cm:titled",
      "cm:auditable"
    ],
    "createdAt": "2016-11-01T14:57:24.388+0000",
    "isFolder": false,
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "my-file.txt",
    "id": "8c37b5b7-b96f-49ef-817e-9808bf2309f9",
    "properties": {
      "cm:title": "The Title"
    }
  }
}

 

All the data returned by the /nodes/{id}/children endpoint we examined in the second post is present plus the node's properties (line 29) and a list of aspect names (line 17). As we saw with create in the previous post this endpoint is also following our "performance first" principle. If we wanted to also determine whether the node represents a link and see it's full path, we can use the include query parameter to ask for this data, for example (3rd request in the Postman collection):

 

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9?include=isLink,path

 

{
  "entry": {
    "isLink": false,
    "isFolder": false,
    "isFile": true,
    "path": {
      "name": "/Company Home/User Homes/test",
      "isComplete": true,
      "elements": [
        {
          "id": "03acc816-b42f-4d87-ab1f-4d4ae16e73ef",
          "name": "Company Home"
        },
        {
          "id": "fb402fa3-3a59-446e-a69e-a1c769b62281",
          "name": "User Homes"
        },
        {
          "id": "bd8f1283-3e84-4585-aafc-12da26db760f",
          "name": "test"
        }
      ]
    },
    ...
  }
}

 

Take a look at the OpenAPI specification http://localhost:8080/api-explorer/#!/nodes/getNode for other additional data you can request.

 

Let's now turn our attention to updating a node. We have decided to implement partial update via PUT (although technically this is not RESTful we feel it's worth bending the rules here to keep things as simple as possible for clients) meaning the client only needs to send the data that is changing, with one exception, that we'll come to shortly.

 

To set some properties we use the same base URL (4th request in the Postman collection) of http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9 but PUT a body using a Content-Type of application/json.

 

{
  "properties":
  {
    "cm:description": "The Description",
    "exif:manufacturer": "Canon"
  }
}

 

The response shows the state of the updated node, note that the exif aspect has also been added automatically (line 20).

 

{
  "entry": {
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-11-02T00:40:51.931+0000",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text\/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 0,
      "encoding": "UTF-8"
    },
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f",
    "aspectNames": [
      "cm:titled",
      "cm:auditable",
      "exif:exif"
    ],
    "createdAt": "2016-11-01T14:57:24.388+0000",
    "isFolder": false,
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "my-file.txt",
    "id": "8c37b5b7-b96f-49ef-817e-9808bf2309f9",
    "properties": {
      "cm:title": "The Title",
      "exif:manufacturer": "Canon",
      "cm:description": "The Description"
    }
  }
}

 

PUT can also be used to rename by just providing a cm:name property in the properties as shown below:

 

{
  "properties":
  {
    "cm:name": "renamed-name.txt"
  }
}

 

Alternatively, the top level name property can also be used (5th request in the Postman collection):

 

{
  "name": "renamed-file.txt"
}

 

Similarly, the owner of the node can be updated, just provide the cm:owner property as follows (6th request in the Postman collection):

 

{
  "properties":
  {
    "cm:owner": "gavinc"
  }
}

 

As mentioned earlier there is one exception to the partial update rule and that is for managing aspects. To change the aspects applied to a node the whole complete array has to be provided. Any aspects the node has applied but are not present in the array will be removed. Conversely, any aspects in the array that the node does not have applied are added.

 

To remove the exif aspect from the node we created earlier PUT the following body (7th request in the Postman collection):

 

{
  "aspectNames": [
    "cm:titled",
    "cm:ownable",
    "cm:auditable"
  ]
}

 

Finally, the type of the node can also be changed by updating the nodeType property, for example to change our node type to cm:savedquery use the following body (8th request in the Postman collection):

 

{
  "nodeType": "cm:savedquery"
}

 

In the examples above we've used a file, everything we went through can obviously also be done for folders.

 

Let's now turn our attention to the actual content. At the start of the post we created an empty text file, you can see this via the content property:

 

"content": {
  "mimeType": "text/plain",
  "mimeTypeName": "Plain Text",
  "sizeInBytes": 0,
  "encoding": "UTF-8"
}

 

To set some plain text content do a PUT against http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9/content using a Content-Type of text/plain and the following body (9th request in the Postman collection):

 

This is the initial content for the file.

 

The response will show the content has been updated and the encoding set accordingly:

 

{
  "entry": {
     ....
    "content": {
      "mimeType": "text/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 41,
      "encoding": "ISO-8859-1"
    }
  }
}

 

The PUT endpoint accepts any binary stream so we could also use Postman to choose a file to upload as shown in the screenshot below:

 

Screen_Shot_2016-11-02_at_08_10_27.png

 

To retrieve the content simply use a GET against the content URL (10th request in the Postman collection):

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9/content

 

The last thing we're going to cover in this post is deleting. To delete the file we just created use the DELETE method against the URL we've been using throughout this post (11th request in the Postman collection), for example:

 

DELETE http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9

 

This will actually perform a soft delete, the node gets moved to the trash can so it can be restored if necessary, we'll cover the trash can endpoints in a future post. If you want to take a look before then have a look through the documentation.

 

To permanently delete the node i.e. skip the trash can, use the permanent query parameter set to true, for example (12th request in the Postman collection):

 

DELETE http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/8c37b5b7-b96f-49ef-817e-9808bf2309f9?permanent=true

 

Hopefully you've found these posts useful so far, if there's anything I can improve to make things easier or any other suggestions, please let me know via comments.

 

In the next post we're going to start looking into some more advanced topics, starting with versioning and locking.

In the last post we looked at how to navigate the repository, this time we're going to create some files and folders.

 

As before all of the endpoints we'll cover in this blog post have been provided in a Postman collection and can be imported by clicking the "Run in Postman" button below.

 

button.svg

 

Let's start with creating a folder in our home folder. We use the same URL as we did for navigating the repository but this time we'll POST to it and use the -my- alias.

 

To create a folder named "My Folder" POST the body below using a Content-Type of application/json to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children

 

{
  "name":"My Folder",
  "nodeType":"cm:folder"
}

 

This results in a response representing the newly created folder as shown below:

 

{
  "entry": {
    "aspectNames": [
      "cm:auditable"
    ],
    "createdAt": "2016-10-17T18:30:28.870+0000",
    "isFolder": true,
    "isFile": false,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-10-17T18:30:28.870+0000",
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "My Folder",
    "id": "de8f6834-1d5a-4137-ab1f-67e6978f1aa8",
    "nodeType": "cm:folder",
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f"
  }
}

 

You may have noticed that slightly more information about the node (aspectNames and properties) is returned by default but even here we are still using a "performance first" principle. The include parameter can also be used with create, see http://localhost:8080/api-explorer/#!/nodes/addNode for the list of extra information you can request.

 

Let's now create an empty file within our home folder. Once again we'll POST to http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children but this time we'll send the following body:

 

{
  "name": "my-file.txt",
  "nodeType": "cm:content"
}

 

This results in a response representing the newly created document as shown below:

 

{
  "entry": {
    "aspectNames": [
      "cm:auditable"
    ],
    "createdAt": "2016-10-17T18:58:38.717+0000",
    "isFolder": false,
    "isFile": true,
    "createdByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "modifiedAt": "2016-10-17T18:58:38.717+0000",
    "modifiedByUser": {
      "id": "test",
      "displayName": "Test Test"
    },
    "name": "my-file.txt",
    "id": "e88e9b0f-7975-4398-beb4-db593b0b7aee",
    "nodeType": "cm:content",
    "content": {
      "mimeType": "text/plain",
      "mimeTypeName": "Plain Text",
      "sizeInBytes": 0,
      "encoding": "UTF-8"
    },
    "parentId": "bd8f1283-3e84-4585-aafc-12da26db760f"
  }
}

 

As well as accepting JSON the same endpoint also accepts multipart/form-data, allowing us to upload content from a standard HTML form or from the command line using curl.

 

Presuming there is a file named test.txt in the current directory try the following command:

 

curl -X POST -H "Authorization: Basic dGVzdDp0ZXN0" -H "Content-Type: multipart/form-data; boundary=----FormBoundary7MA4YWxkTrZu0gW" -F "filedata=@test.txt" "http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children"

 

Alternatively, the 3rd request in the Postman collection can be used after selecting a file in the "Body" tab as shown in the screenshot below.

 

Screen_Shot_2016-10-17_at_20_56_04.png

 

If you hadn't already noticed, one of the benefits of the API Explorer is that it allows you to "Try it out!" on all documented APIs. Unfortunately the OpenAPI specification does not currently allow multiple content types to be documented for the same endpoint. For POST /nodes/-my-/children we made the decision to focus the API Explorer on JSON to allow the creation of folders and files. We have however fully documented what is possible via multipart/form-data, we'll discuss a few of those options next.

 

To specify the name of the file that gets created you can use a form field called name as shown in the screenshot below. You can try this for yourself via the 4th request in the Postman collection.

 

Screen Shot 2016-10-18 at 20.04.27.png

 

When uploading content it's quite common for a file with the same name to exist, this will generate an error by default, to avoid the error the autoRename form field can be used as shown below. You can try this for yourself via the 5th request in the Postman collection. If a file name clash is detected a suffix will be added to the file name, for example my-file.txt will become my-file-1.txt.

 

Screen Shot 2016-10-18 at 20.25.36.png

 

The same thing can be achieved whilst creating folders and empty files via the same named query parameter, for example: http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children?autoRename=true

 

In some scenarios the path of the destination folder is known, so to avoid having to lookup the id of the folder a relativePath form field can be provided as shown in the screenshot below. You can try this for yourself via the 6th request in the Postman collection.

 

Screen Shot 2016-10-18 at 21.21.29.png

 

This will create a file named "my-file.txt" in the folder we created earlier i.e. in "/Company Home/User Homes/test/My Folder". The same thing can be achieved whilst creating folders and empty files by using the same named property in the body as follows:

 

{
   ...
   "relativePath": "/My Folder"
}

 

Another feature of the repository we can control when uploading content is the generation of a rendition. To have the thumbnail rendition used by Share generated, provide a renditions form field with a value of doclib as shown in the screenshot below. You can try this for yourself via the 7th request in the Postman collection.

 

Screen Shot 2016-10-18 at 21.43.36.png

Currently only one rendition can be requested, we plan to allow multiple in the future hence the plural form field name.

 

Finally, let's take a look at how we set properties. Any other form field will be presumed to represent a property to set. The screenshot below and the last request in the Postman collection shows how to set the cm:title, cm:description and exif:manufacturer properties. Properties have to follow the prefix:localname format and be a registered property via the content model otherwise they are silently ignored.

 

Screen Shot 2016-10-18 at 21.43.58.png

If we take a look at the response we'll see that the appropriate aspects have also been automatically applied.

 

{
  "entry": {
    ...
    "aspectNames": [
      "cm:versionable","cm:titled","cm:auditable","cm:author","exif:exif"
    ],
    "properties": {
      "cm:title": "The Title",
      "cm:versionType": "MAJOR",
      "cm:versionLabel": "1.0",
      "exif:manufacturer": "Canon",
      "cm:description":"The Description"
    }
  }
}

 

We've covered a lot of ground in this post but we still haven't looked at all the capabilities available; overwriting, versioning, custom types and associations will all be covered in future posts. Next time we'll be looking at how to retrieve, update and delete files & folders.

Following the v1 REST API - Part 1 - Introduction to the v1 REST API in my last post it's now time to dive in and start using the API.

 

Whilst the v1 REST API has had a /nodes endpoint since it's introduction in 4.2 it could not be used to navigate the repository. One of the new endpoints added in the 5.2 EA Community releases is /nodes/{id}/children which allows us to retrieve the child nodes of a folder.

 

All of the endpoints we'll cover in this blog post have been provided in a shared Postman collection. To import the collection into Postman click the "Run in Postman" button below.

 

button.svg

 

The requests in the collection are configured to use the test user we created in the v1 REST API - Part 1 - Introduction post.

 

To retrieve the children for a folder we need it's id, to help get started we have provided three aliases, -root, -my- and -shared-. As the name suggests -root- maps to the root folder of the repository, -my- maps to the currently authenticated users home on-premise and to the users private site on my.alfresco.com and finally -shared- maps to the repository's shared folder.

 

Let's retrieve the children of the repositories root folder, to do this we use http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children. If you've executed this request in Postman you should see something similar to the screenshot below (you can of course alternatively use curl or your favourite REST client).

 

Screen Shot 2016-10-13 at 21.55.21.png

 

If you've used the v1 REST API before you should recognise the collection response format. Each child node is represented within an entry object which is contained in an entries array. New v1 REST APIs are following a "performance first" principle, meaning by default, we only return the data that is the most performant to collect. More data can be requested but the client has to make a conscious decision to request more data and sacrifice a slight performance hit.

 

Let's build on the first request and ask for properties and a list of aspect names to be included for each node, we do that by using the new include query parameter as follows:

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?include=properties,aspectNames

 

One of the entry objects including the extra information is shown in the response snippet below.

 

"entry": {
"aspectNames": [
"cm:titled",
"cm:auditable",
"app:uifacets"
],
"createdAt": "2016-10-05T19:44:11.127+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2016-10-05T19:44:56.559+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Data Dictionary",
"id": "0be671ab-5fd9-4cc2-b93f-10fa0939e70f",
"nodeType": "cm:folder",
"properties": {
"cm:title": "Data Dictionary",
"cm:description": "User managed definitions",
"app:icon": "space-icon-default"
},
"parentId": "03acc816-b42f-4d87-ab1f-4d4ae16e73ef"
}

 

The 2nd request in the Postman collection can also be used to see this in action. The list of extra data that can be included is documented for each endpoint in the OpenAPI Specification, see http://localhost:8080/api-explorer/#!/nodes/getNodeChildren for an example. We'll cover customising the responses in much more detail in a future blog post.

 

As you'll see from the screenshot above the pagination object tells us a maximum of 100 items is returned by default, this can be controlled with the maxItems query parameter and the starting point can be controlled via the skipCount query parameter, for example, to get items 3 through 5 use the following URL (3rd request in the Postman collection):

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?skipCount=2&maxItems=3

 

The pagination object in the response will indicate there are more results to retrieve via the hasMoreItems property. If it's possible to determine the total number of items available the totalItems property will also be present as shown below.

 

"pagination": {
"count": 3,
"hasMoreItems": true,
"totalItems": 7,
"skipCount": 2,
"maxItems": 3
}

 

The items returned can also be filtered via the where query parameter, the filtering supported is specific to each endpoint, again this is documented in the OpenAPI Specification, see [http://localhost:8080/api-explorer/#!/nodes/getNodeChildren] for an example, we'll take a look at a couple of them here.

 

Firstly, the isFile property can be used to just return nodes that represent content i.e. a subtype of cm:content by using the following URL (4th request in the Postman collection):

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?where=(isFile=true)

 

The same result can be achieved by using the isFolder property too, for example http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?where=(isFolder=false)

 

As there are no files in the root of the repository we get an empty response, we'll cover creating files in the next post.

 

To filter the results by a specific content type nodeType can be used in the where clause, for example, to retrieve just the Sites folder use the following URL (5th request in the Postman collection):

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?where=(nodeType=st:sites)

 

This will result in a single result as shown below.

 

{
"list": {
"pagination": {
"count": 1,
"hasMoreItems": false,
"totalItems": 1,
"skipCount": 0,
"maxItems": 100
},
"entries": [
{
"entry": {
"createdAt": "2016-10-05T19:44:14.968+0000",
"isFolder": true,
"isFile": false,
"createdByUser": {
"id": "System",
"displayName": "System"
},
"modifiedAt": "2016-10-05T19:44:29.073+0000",
"modifiedByUser": {
"id": "System",
"displayName": "System"
},
"name": "Sites",
"id": "4f00d061-cdad-42b5-bfd6-8b0759a7fa7c",
"nodeType": "st:sites",
"parentId": "03acc816-b42f-4d87-ab1f-4d4ae16e73ef"
}
}
]
}
}

 

To retrieve all nodes of a specific type and it's sub-types use the INCLUDESUBTYPES moniker, for example where=(nodeType='my:specialNodeType INCLUDESUBTYPES')

 

Finally, the items returned can also be ordered via the orderBy query parameter, the ordering supported is specific to each endpoint, once again this is documented in the OpenAPI Specification, see http://localhost:8080/api-explorer/#!/nodes/getNodeChildren for an example.

 

By default, the nodes/{id}/children endpoint uses orderBy=isFolder DESC,name ASC as the default sort order i.e. folders first alphabetically followed by files. To mix files and folders and order them alphabetically in reverse order use the following URL (6th request in the Postman collection):

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?orderBy=name DESC

 

So far, we've only used -root- as the folder id, we can of course also use an explicit id, for example to retrieve the children of my Data Dictionary folder I would use the following URL:

 

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/0be671ab-5fd9-4cc2-b93f-10fa0939e70f/children

 

You've now learnt how to navigate the repository, in the v1 REST API - Part 3 - Creating Nodes we'll see how to create files and folders.

Filter Blog

By date: By tag: