v1 REST API - Part 3 - Creating Nodes

cancel
Showing results for 
Search instead for 
Did you mean: 

v1 REST API - Part 3 - Creating Nodes

gavincornwell
Senior Member
11 34 40K

In the 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?autoRena...

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. we'll be looking at how to retrieve, update and delete files & folders.

34 Comments
cver
Active Member

This could be a solution to an issue we have at the moment : we try to connect Alfresco to an internally developed application that generates reports on the fly. At the moment, we managed to import the documents to Alfresco (ONE v.4.2) using FTP, and directories are created if necessary, but this does not allow us (yet) to fill some metadata fields, nor manage aspects.

Can you explain how aspects can be "automatically applied" ? I don't see it in the postman collection at all !

gavincornwell
Senior Member

Hi Christophe,

The fact that you provide a "cm:title" property, which belongs to the "cm:titled" aspect when creating the node means the repository will automatically apply the aspect during creation, there's nothing special you have to do from a client perspective.

If you want to explicitly manage aspects (add/remove) take a look at the https://community.alfresco.com/community/ecm/blog/2016/11/02/v1-rest-api-part-4-managing-nodes as I cover that exact topic!

Hope that helps!

mindo
Member II

Hi Gavin,

thanks for this blog, it's very helpful.

I'm trying to upload a file into -my- folder using following code: 

File file = new File("aaa.txt");

HttpPost uploadFile = new HttpPost("http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-my-/children");
uploadFile.addHeader("Authorization", basicAuth);
String boundary = "===" + System.currentTimeMillis() + "===";
uploadFile.addHeader("Content-type", "multipart/form-data; charset=utf-8; boundary=--"+boundary);

MultipartEntityBuilder builder = MultipartEntityBuilder.create();
FileBody fileBody = new FileBody(file);
builder.addPart("filedata", fileBody);

HttpEntity multipart = builder.build();
uploadFile.setEntity(multipart);
HttpResponse response = httpClient.execute(uploadFile);

But, all i get is this response:

{"error":{"errorKey":"No disk space available","statusCode":409,"briefSummary":"10280056 No disk space available","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"}}

Using the 3rd set from postman to upload a file works.

Can you please help me?

Thanks!

gavincornwell
Senior Member

Hi Marek,

 

Unfortunately you've hit an edge case we have where you don't get a very descriptive error message :-(

 

Believe it or not, that error message is seen when the request has provided no form data fields, so my guess is that something has gone wrong with the construction of your multipart request. You don't normally have to generate boundaries yourself with client libraries, try not setting the header and seeing if that makes a difference. Your current code is also generating a boundary but you don't tell the MultipartEntityBuilder about it, could that be the problem?

 

If that still fails I would suggest sending your requests (your code and Postman) through a proxy so you can compare them, that may help pinpoint what's wrong.

 

Hope that helps!

 

Gavin

mindo
Member II

Hi Gavin, 

i removed the second addHeader line based on you advice and now the upload works like a charm. Smiley Happy

Thank you very much for your cooperation,

Marek

gavincornwell
Senior Member

You're welcome and glad you got it working!

zhihailiu
Active Member

Hi Gavin, can you manage ACL/permission with the API?

gavincornwell
Senior Member

Yes, it's possible but I haven't written any blog posts about that yet, I was waiting for the /groups REST API to be completed and released. I will try and get round to writing that soon as the /groups API was actually released in 5.2.1.

In the meantime, if you take a look at GET/PUT /nodes/{nodeId} in the API Explorer you'll see some mention of "permissions". Permissions can be "include"d with a response, their optional for performance reasons. PUT supports the update of permissions, see the documentation for PUT for an example.

Hope that helps!

zhihailiu
Active Member

Thank you Gavin.

I tried POST /nodes/{nodeId} with permissions in JSON body but got 405 error. I also added parameter include=permissions but it didn't work either. Is this specific to 5.2.1? Mine is 5.2.0.

1. Existing permissions

GET /nodes/{nodeId}?include=permissions

...

"permissions":{
"inherited":[
{
"authorityId":"GROUP_site_acme_SiteManager",
"name":"SiteManager",
"accessStatus":"ALLOWED"
},
{
"authorityId":"GROUP_site_acme_SiteConsumer",
"name":"SiteConsumer",
"accessStatus":"ALLOWED"
},
{
"authorityId":"GROUP_site_acme_SiteCollaborator",
"name":"SiteCollaborator",
"accessStatus":"ALLOWED"
},
{
"authorityId":"GROUP_site_acme_SiteContributor",
"name":"SiteContributor",
"accessStatus":"ALLOWED"
}
],
"settable":[
"Contributor",
"Collaborator",
"Coordinator",
"Editor",
"Consumer"
],
"isInheritanceEnabled":true
},

...

2. Update permissions

POST /nodes/{nodeId}

{
"permissions":
{
"isInheritanceEnabled": false,
"locallySet":
[
{"authorityId": "joe", "name": "Read", "accessStatus":"ALLOWED"}
]
}
}

3. Response

405 Method Not Allowed

"error":{
"errorKey": "framework.exception.UnsupportedResourceOperation",
"statusCode": 405,
"briefSummary": "08200067 The operation is unsupported",
"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"
}

gavincornwell
Senior Member

It's a PUT not a POST to update permissions 

zhihailiu
Active Member

I must have lost my mind... Thank you!

zhihailiu
Active Member

Hi Gavin,

A related question - can I create a node and set its permissions in one POST call? or do I have to do POST to create the node and then PUT to update permissions? I tried below (even with include=permissions). The node was created but withe default/inherited permissions. Thanks.

POST /nodes/{nodeId}/children

{  
    "name":"Test",
    "nodeType":"cm:folder",
    "properties":{  
        "cm:title":"Test"
    },
    "permissions":{  
        "isInheritanceEnabled":false,
        "locallySet":[  
            {  
                "authorityId":"joe",
                "name":"SiteManager",
                "accessStatus":"ALLOWED"
            }
        ]
    }
}

gavincornwell
Senior Member

That's a great question! Unfortunately we don't support that currently so yes, you'd have to do a POST followed by a PUT.

This is something we should support so please raise an enhancement request at issues.alfresco.com.

lcelenza
Member II

Hi Gavin,

I'm trying to create a node with data content in .NET (C#) calling the nodes/{nodeId}/children endpoint, but I can't seem to make it work. It's driving me nuts! So far I only managed to make it work using Postman. I tried with both RestSharp and HttpClient from WebApi.Client nuget package, but the result is always 400 Bad Request. Here's the response content:

{"error":{"errorKey":"Could not read content from HTTP request body: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value\n at [Source: java.io.BufferedReader@4e290940; line: 1, column: 3]","statusCode":400,"briefSummary":"00180071 Could not read content from HTTP request body: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value\n at [Source: java.io.BufferedReader@4e290940; line: 1, column: 3]","stackTrace":"Per motivi di sicurezza l'analisi dello stack non viene più visualizzata, ma viene mantenuta la proprietà per le versioni precedenti.","descriptionURL":"https://api-explorer.alfresco.com"}}

Any help would be much appreciated, thanks

gavincornwell
Senior Member

This sounds like a client issue to me, from the error message it looks like the client library is sending a malformed request body with the request, it should be a JSON body. Are you correctly setting the Content-Type for the request to be "application/json"?

The easiest way to debug this would be to place a HTTP proxy in between your client and server and record the traffic being sent by the .NET client. 

ivanovpavel1983
Active Member

Hi Gavin!

Is it possible to create node of sys:base class?

I got an error:

The request is:

I have requested post /nodes/{nodeId}/children

otp:referenceRequiredDocuments is :

{nodeId} is folder (cm:folder class):

gavincornwell
Senior Member

No, as the error message says it's only possible to create sub-types of cm:cmobject via the REST API at this time.

The "sys" namespace is reserved for system level objects that should generally remain hidden from APIs/UIs.  Is there a reason you need to extend from sys:base? I would recommend extending from cm:cmobject.

ivanovpavel1983
Active Member

We have types (contacts, applications, operations) without content. We have otp:document that extends cm:cmobject and associations for join contacts, applications, operations to documents... Is it correct to extend cmSmiley Surprisedbject for types without content?

gavincornwell
Senior Member

If you have custom types without content the recommended type to extend is "cm:cmobject" whereas if you have custom types with content the recommended type to extend is "cm:content".

ivanovpavel1983
Active Member

Hello, Gavin.

We used inherit from sys:base according to book alfresco developers guide 5.0 ((

Can we change parent class of our objects from sys:base to cm:cmobject after production system start?

gavincornwell
Senior Member

Hmm, interesting, I wasn't aware of that statement.

I wouldn't advocate testing the model change in production, try on a dev/test system first after making a backup ;-)

In theory it should be possible to change the type hierarchy, however, cm:name is a mandatory property on cm:cmobject which your nodes are not likely to have which may cause some problems. If it does you may have to "migrate" all nodes by adding a cm:name property.

If you already have a production system using this type hierarchy maybe you could consider using the older v0 REST API to create your nodes? Then raise an enhancement request against the v1 REST API to support the creation of objects that extend sys:base.

ivanovpavel1983
Active Member

Our classes that inherit from sys:base have property cm:name without this property it would be an error Smiley Happy while creating.

Now, I try to change our model in test/dev.

ivanovpavel1983
Active Member

I've updated test. Everything works correctly. Tomorrow I will update model on prod.

Thanks!

shalini1403
Member II

Hi Gavin,

This blog was very helpful while i was creating a folder in alfresco from processmaker 3.2

I'm trying to upload a file using processmaker triggers by the following code:

$data = array(

"filedata" => "C:/Users/test/Desktop/ALFRESCO/test.doc"

);
$data_string = json_encode($data);


$ch = curl_init('http://localhost/alfresco/api/-default-/public/alfresco/versions/1/nodes/c5edfbbe-e9ee-4158-add1-37b...

curl_setopt($ch, CURLOPT_USERPWD, 'admin:test');

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type:multipart/form-data'));

curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$result = curl_exec($ch);
echo($result);

 I get the following error :

"No disk space available","statusCode":409,"briefSummary":"04090141 

Can u pls suggest what is wrong in the above code?

gavincornwell
Senior Member

You're POSTing using multipart/form-data (which is correct) but you're encoding as JSON first!?

Using plain curl you can use the -F option to pass in the file contents, you will need to do the equivalent in processmaker.

If this doesn't help try turn on debug using "log4j.logger.org.alfresco.rest".

shalini1403
Member II

Thanks Gavin!!

I have successfully uploaded multiple files using trigger in processmaker 3.2 

gavincornwell
Senior Member

That's great news, glad you got it working!

boyzoid
Member II

Gavin - this entire series of posts has been an incredible resource. It has answered most of the questions I have had regarding using the API. I do have a situation that I have been unable to figure out, though.

I need to add tags to files that are uploaded using the API.

Right now, I am waiting for a successful response from the upload and making another request to add the tags.

Is there a way to tag a file during the upload?

gavincornwell
Senior Member

Hi Scott,

Glad the blog posts were of some help to you.

No, unfortunately, there is no way to provide tags when uploading. However, that is a great idea so I'd encourage you to raise an enhancement request in JIRA.

chuchel
Member II

Hi Gavin,

I am using:

http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/{parentID}/children

endpoint to create document in alfresco (parentID == parent folder and file content is delivered as multipart file). Everything works great, document is created and I can see it in share application. But seems like content of the document (docx in this case) is not being indexed (when I upload file manually with share i can search document by its content). Is this a known bug (feature) or am I missing something ?

Cheers,

Leszek.

gavincornwell
Senior Member

Hi Leszek,

That's not a problem I'm aware of, once the content is added to the system, regardless of where its added from, SOLR should track in the background and index the text.

Is it only docx files you are having a problem with or all files? If it's the former that may point to an issue with transformations, if it's the latter that points to SOLR being the problem.

If you can't spot anything obvious try posting a question in the forums or contact support as it's most likely an environmental/setup issue.

chuchel
Member II

Thanks for you feedback.

Just tried with other formats (txt, log, ...) looks like this affects all types. When uploaded with share everything gets indexed properly and I am able to search by the content. When uploaded with REST API nothing gets indexed. I will try to dig deeper into alfresco and solr logs maybe.

Thanks,

Leszek.

chuchel
Member II

After spending some time playing with file upload and searching by content I figured out that:

1. files uploaded with Share application can be found in Share but cannot be found using REST API

2. files uploaded with REST API are found using REST API but are not found in Share application

I tried to reboot Share and Repository (thinking of caching or something) but did not help.

ronifabian
Member II

Hello Experts,

Can anyone help me with the below error

"Error Details   org.apache.camel.component.ahc.AhcOperationFailedException: HTTP operation failed invoking https://xxxx.alfrescocloud.com/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/childr... with statusCode: 507".

I am trying to pick a file from a SFTP folder and upload the file to the relative path via SAP CPI application. Passing the below Headers.

C2.jpgC1.JPG

and below the groovy script am using.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
   // byte[] fileContent = message.getBody(byte[]);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    //String fileName = message.getProperty("fileName")
    
	String fileContent = message.getProperty("fileContent")
	
	String formDataPart1='--cpi\r\nContent-Disposition: form-data; name="relativePath"\r\nxxxxxxxxxxxxxxxxx\r\n--cpi--\r\n'
	
    String formDataPart2='--cpi\r\nContent-Disposition: form-data; name="filedata";filename="abc.xlsx"\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n'+fileContent+'\r\n'
	

    byte[]  formDataPart1Bytes = formDataPart1.getBytes();
    byte[]  formDataPart2Bytes = formDataPart2.getBytes();
    outputStream.write(formDataPart2Bytes);
    //outputStream.write(fileContent);
    outputStream.write(formDataPart1Bytes);
    message.setBody(outputStream);
//    message.setHeader("Content-Type","multipart/form-data; boundary=--cpi");
    return message;
}

The Http response is received form Alfresco API is

{"error":{"errorKey":"framework.exception.ApiDefault","statusCode":507,"briefSummary":"08054298 Stream ended unexpectedly","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"}}

Can you please help me?

 

Regards,

Roni Fabian