Nuxeo provides a high level client implementation for Java programmers: Nuxeo Automation Client API simplifies your task since it handles all the protocol level details.
Dependencies
- This documentation applies for
nuxeo-automation-client
versions greater or equal to5.4
. - The Nuxeo Automation Client is also available with all dependencies included (JAR shaded - nuxeo-automation-XX-jar-with-dependencies)
To use the Java Automation client you need to put a dependency on the following Maven artifact:
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-client</artifactId>
<version>...</version>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-client</artifactId>
<version>...</version>
<classifier>jar-with-dependencies</classifier>
</dependency>
For a direct download, see https://maven.nuxeo.org/.
The client library depends on:
net.sf.json-lib:json-lib
,net.sf.ezmorph:ezmorph
- for JSON supportorg.codehaus.jackson:jackson-core-asl
org.codehaus.jackson:jackson-mapper-asl
.org.apache.httpcomponents:httpcore
,org.apache.httpcomponents:httpclient
- for HTTP supportjavax.mail
- for multipart content support
Automation Client API
Let's take an example and execute the "SELECT * FROM Document"
query against a remote Automation server:
import org.nuxeo.ecm.automation.client.jaxrs.impl.HttpAutomationClient;
import org.nuxeo.ecm.automation.client.model.Documents;
import org.nuxeo.ecm.automation.client.Session;
public static void main(String[] args) throws Exception {
HttpAutomationClient client = new HttpAutomationClient(
"http://localhost:8080/nuxeo/site/automation");
Session session = client.getSession("Administrator", "Administrator");
Documents docs = (Documents) session.newRequest("Document.Query").set(
"query", "SELECT * FROM Document").execute();
System.out.println(docs);
client.shutdown();
}
You can see the code above has three distinctive parts:
- Opening a connection.
- Invoking remote operations.
- Destroying the client.
Opening a connection
Before using the Automation client you should first create a new client that is connecting to a remote address you can specify through the constructor URL argument. As the remote server URL you should use the URL of the Automation service.
// create a new client instance HttpAutomationClient client = new HttpAutomationClient("http://localhost:8080/nuxeo/site/automation");
No connection to the remote service is made at this step. The Automation service definition will be downloaded the first time you create a session.
A local registry of available operations is created from the service definition sent by the server.
The local registry of operations contains all operations on the server - but you can invoke only operations that are accessible to your user - otherwise a 404 (operation not found) will be sent by the server.
Once you created a client instance you must create a new session to be able to start to invoke remote operations. When creating a session you should pass the credentials to be used to authenticate against the server.
So you create a new session by calling:
import org.nuxeo.ecm.automation.client.Session; Session session = client.getSession("Administrator", "Administrator");
This will authenticate you onto the server using the basic authentication scheme.
If needed, you can use another authentication scheme by setting an interceptor.
client.setInterceptor(new PortalSSOAuthInterceptor("nuxeo5secretkey", "Administrator")); Session session = client.getSession();
Invoking remote operations
Using a session you can now invoke remote operations.
To create a new invocation request you should pass in the right operation or chain ID.
OperationRequest request = session.newRequest("Document.Query");
Then populate the request with all the required arguments.
request.set("query", "SELECT * FROM Document");
You can see in the example that you have to specify only the
query
argument. If you have more arguments, call theset
method in turn for each of these arguments. If you need to specify execution context parameters you can userequest.setContextProperty
method. On the same principle, if you need to specify custom HTTP headers you can use therequest.setHeader
method.After having filled all the required request information you can execute the request by calling the execute method.
Object result = request.execute();
The client API provides both synchronous and asynchronous executionFor an asynchronous execution you can make a call to the
execute(AsyncCallback<Object> cb)
method.Beware that you can set the timeout in milliseconds for the wait of the aynchronous thread pool termination at client shutdown by passing it to the
HttpAutomationClient
constructor:public HttpAutomationClient(String url, long httpConnectionTimeout, long asyncAwaitTerminationTimeout)
.Default value is 2 seconds. If you don't use any asynchronous call you can set this timeout to 0 in order not to wait at client shutdown.
Setting the HTTP connection timeoutYou can set a timeout in milliseconds for the HTTP connection in order to avoid an infinite wait in case of a network cut or if the Nuxeo server is not responding.
Just pass it to the
HttpAutomationClient
constructor:public HttpAutomationClient(String url, long httpConnectionTimeout, long asyncAwaitTerminationTimeout)
.Default value is 0 = no timeout.
Executing a request will either throw an exception or return the result. The result object can be null if the operation has no result (i.e. a void operation) - otherwise a Java object is returned. The JSON result is automatically decoded into a proper Java object. The following objects are supported as operation results:
- Document - a document object
- Documents - a list of documents
- Blob - a file
- Blobs - a file list
In case the operation invocation fails - an exception described by a JSON entity will be sent by the server and the client will automatically decode it into a real Java exception derived from
org.nuxeo.ecm.automation.client.jaxrs.RemoteException
.Before sending the request the client will check the operation arguments to see if they match the operation definition and will throw an exception if some required argument is missing. The request will be sent only after validation successfully completes.
The query example is pretty simply. The query operation doesn't need an input object to be executed. (i.e. the input can be null). But most operations require an input. In that case you must call request.setInput
method to set the input. We will see more about this in the following examples.
If you prefer a most compact notation you can use the fluent interface way of calling methods:
Object result = session.newRequest("OperationId").set("var1", "val1").set("var2", "val2").execute();
Destroying the client
When you are done with the client you must call the client.disconnect
method to free any resource held by the client. Usually this is done only once when the client application is shutdown. Creating new client instances and destroying them may be costly so you should use a singleton client instance and use it from different threads (which is safe).
If you need different logins then create one session per login. A session is thread safe.
Create Read Update Delete
In this example we assume we already have a session instance.
Here is an example with Document.Create, Document.Update, Document.Delete, Document.Fetch operations.
You can see The Automation Client Service Adapter section to know how CRUD operations are wrapped into DocumentService adapter and how to use it.
import org.nuxeo.ecm.automation.client.model.Document;
import org.nuxeo.ecm.automation.client.Session;
import org.nuxeo.ecm.automation.client.Constants;
import org.nuxeo.ecm.automation.client.model.IdRef;
// Fetch the root of Nuxeo repository
Document root = (Document) session.newRequest("Document.Fetch").set("value", "/").execute();
// Instantiate a new Document with the simple constructor
Document document = new Document("myDocument", "File");
document.set("dc:title", "My File");
document.set("dc:description", "My Description");
// Create a document of File type by setting the parameter 'properties' with String metadata values delimited by comma ','
document = (Document) session.newRequest("Document.Create").setHeader(Constants.HEADER_NX_SCHEMAS, "*").setInput(root).set("type", document.getType()).set("name", document.getId()).set("properties", document).execute();
// Set another title to update
document.set("dc:title", "New Title");
// Update the document
document = (Document) session.newRequest("Document.Update").setInput(document).set("properties", document).execute();
// Delete the document
session.newRequest("Document.Delete").setInput(document).execute();
Operations examples above fetch the document with common properties: common
, dublincore
, file
. If you wish to fetch all properties or a specific schema, you can do as this following example:
import org.nuxeo.ecm.automation.client.model.Document;
import org.nuxeo.ecm.automation.client.Session;
import org.nuxeo.ecm.automation.client.Constants;
import org.nuxeo.ecm.automation.client.model.IdRef;
Document document = new Document("myDocument", "File");
// For all operations -> use setHeader method to specify specific schemas or every ones (*)
Document root = (Document) session.newRequest("Document.Fetch").setHeader(Constants.HEADER_NX_SCHEMAS, "*").set("value", "/").execute();
document = (Document) session.newRequest("Document.Create").setHeader(Constants.HEADER_NX_SCHEMAS, "dublincore,blog").setInput(root).set("type", document.getType()).set("name", document.getId()).set("properties", document).execute();
document = (Document) session.newRequest("Document.Update").setHeader(Constants.HEADER_NX_SCHEMAS, "dublincore,blog").setInput(document).set("properties", document).execute();
You can fetch all updated properties during the session with the following method of the document:
// Fetch the dirty properties (updated values) from the document
PropertyMap dirties = document.getDirties();
To see complex properties example and the use of propertyMap
, see the Managing Complex Properties section.
Managing Blobs
In this example we assume we already have a session instance.
The example will create a new File document into the root "/" document and then will upload a file into it. Finally we will download back this file.
Get the root document and create a new File document at location
/myfile.
import org.nuxeo.ecm.automation.client.model.Document; import org.nuxeo.ecm.automation.client.Session; // get the root Document root = (Document) session.newRequest("Document.Fetch").set("value", "/").execute(); // create a file document session.newRequest("Document.Create").setInput(root).set("type", "File").set("name", "myfile").set("properties", "dc:title=My File").execute();
Note the usage of
setInput()
method. This is to specify that the create operation must be executed in the context of the root document - so the new document will be created under the root document. Also you can notice that the input object is a Document instance.Now get the file to upload and put it into the newly created document.
File file = getTheFileToUpload(); FileBlob fb = new FileBlob(file); fb.setMimeType("text/xml"); // uploading a file will return null since we used HEADER_NX_VOIDOP session.newRequest("Blob.Attach").setHeader( Constants.HEADER_NX_VOIDOP, "true").setInput(fb) .set("document", "/myfile").execute();
The last
execute
call will returnnull
since theHEADER_NX_VOIDOP
header was used. This is to avoid receiving back from the server the blob we just uploaded.Note that to upload a file we need to use a
Blob
object that wrap the file to upload.- Now get the the file document where the blob was uploaded.
Then retrieve the blob remote URL from the document metadata. We can use this URL to download the blob.
import org.nuxeo.ecm.automation.client.model.Document; import org.nuxeo.ecm.automation.client.Session; import org.nuxeo.ecm.automation.client.Constants; // get the file document where blob was attached Document doc = (Document) session.newRequest( "Document.Fetch").setHeader( Constants.HEADER_NX_SCHEMAS, "*").set("value", "/myfile").execute(); // get the file content property PropertyMap map = doc.getProperties().getMap("file:content"); // get the data URL String path = map.getString("data");
You can see we used the special
HEADER_NX_SCHEMAS
header to specify we want all properties of the document to be included in the response.Now download the file located on the server under the
path
we retrieved from the document properties:// download the file from its remote location blob = (FileBlob) session.getFile(path); // ... do something with the file // at the end delete the temporary file blob.getFile().delete();
We can do the same by invoking the
Blob.Get
operation.// now test the GetBlob operation on the same blob blob = (FileBlob) session.newRequest("Blob.Get").setInput(doc).set( "xpath", "file:content").execute(); // ... do something with the file // at the end delete the temporary file blob.getFile().delete();
Managing Complex Properties
Complex properties can have different levels complexity. This part of the documentation is about managing them with Automation Client API using JSON format.
Let's see a complex property schema example:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mc="http://nuxeo.org/schemas/dataset/"
elementFormDefault="qualified"
targetNamespace="http://nuxeo.org/schemas/dataset/">
...
<xs:complexType name="field">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="fieldType" type="mc:fieldType">
</xs:element>
<xs:element name="columnName" type="xs:string"/>
<xs:element name="sqlTypeHint" type="xs:string"/>
<xs:element name="roles">
<xs:complexType>
<xs:sequence>
<xs:element name="role" type="mc:roleType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
...
</xs:schema>
Let's see a JSON example of these complex property values:
[
{
"fieldType": "string",
"description": "desc field0",
"roles": [
"Decision",
"Score"
],
"name": "field0",
"columnName": "col0",
"sqlTypeHint": "whatever"
},
{
"fieldType": "string",
"description": "desc field1",
"roles": [
"Decision",
"Score"
],
"name": "field1",
"columnName": "col1",
"sqlTypeHint": "whatever"
},
{
"fieldType": "string",
"description": "desc field2",
"roles": [
"Decision",
"Score"
],
"name": "field2",
"columnName": "col2",
"sqlTypeHint": "whatever"
},
{
"fieldType": "string",
"description": "desc field3",
"roles": [
"Decision",
"Score"
],
"name": "field3",
"columnName": "col3",
"sqlTypeHint": "whatever"
},
{
"fieldType": "string",
"description": "desc field4",
"roles": [
"Decision",
"Score"
],
"name": "field4",
"columnName": "col4",
"sqlTypeHint": "whatever"
}
]
Finally updating a document with the JSON values into its related metadata looks like this.
// Fetch the document
Document document = (Document) session.newRequest(DocumentService.GetDocumentChild).setInput(new PathRef("/")).set("name", "testDoc").execute();
// Send the fields representation as json
// Read the json file
File fieldAsJsonFile = FileUtils.getResourceFileFromContext("creation.json");
String fieldsDataAsJSon = FileUtils.readFile(fieldAsJsonFile);
// Don't forget to replace CRLF or LF
fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\n", "");
fieldsDataAsJSon = fieldsDataAsJSon.replaceAll("\r", "");
// Set the json values to the related metadata
document.set("ds:fields", fieldsDataAsJSon);
// Document Update
session.newRequest(UpdateDocument.ID).setInput(document).set("properties",document).execute();
Managing Business Objects
Since 5.7.2, managing business objects (Plain Old Java Object client side for mapping the Nuxeo Document Model Adapter (Documentation) server side) is now available.
Why manage business object? To manipulate business object to avoid Nuxeo Document manipulation on client side:
pull : BusinessObject (POJO) <----JSON---- DocumentModelAdapter <---- DocumentModel push : BusinessObject (POJO) -----JSON---> DocumentModelAdapter ----> DocumentModel
Let's see an example.
Create a Nuxeo Document Model Adapter registered on server side and which should extend the
BusinessAdapter
class:import org.nuxeo.ecm.automation.core.operations.business.adapter.BusinessAdapter; import org.codehaus.jackson.annotate.JsonCreator; import org.codehaus.jackson.annotate.JsonProperty; import org.nuxeo.ecm.core.api.DocumentModel; /** * Document Model Adapter example server side */ public class BusinessBeanAdapter extends BusinessAdapter { private static final Log log = LogFactory.getLog(BusinessBeanAdapter.class); /** * Default constructor is needed for jackson mapping */ public BusinessBeanAdapter() { super(); } public BusinessBeanAdapter(DocumentModel documentModel) { super(documentModel); } public String getTitle() { try { return (String) getDocument().getPropertyValue("dc:title"); } catch (ClientException e) { log.error("cannot get property title", e); } return null; } public void setTitle(String value) { try { getDocument().setPropertyValue("dc:title", value); } catch (ClientException e) { log.error("cannot set property title", e); } } public String getDescription() { try { return (String) getDocument().getPropertyValue("dc:description"); } catch (ClientException e) { log.error("cannot get description property", e); } return null; } public void setDescription(String value) { try { getDocument().setPropertyValue("dc:description", value); } catch (ClientException e) { log.error("cannot set description property", e); } } }
Declare a POJO on client side as following:
import org.nuxeo.ecm.automation.client.annotations.EntityType; //Automation client File pojo example annotated with EntityType setting the adapter server side built previously (just the simple name) @EntityType("BusinessBeanAdapter") public class BusinessBean { protected String title; protected String description; protected String id; protected String type; /** * this default contructor is mandatory for jackson mapping on the nuxeo server side **/ public BusinessBean() { } public BusinessBean(String title, String description, String type) { this.title = title; this.type = type; this.description = description; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
We assume than the POJO client side stub is similar to the document model adapter stub server side.
So now we have on client side a POJO
BusinessBean
and on server side a Document Model AdapterBusinessBeanAdapter
.We are going to use two operations for creating/updating process:
Business.BusinessCreateOperation
Business.BusinessUpdateOperation
Since 5.7.2, you can see the Automation Client Service Adapter section to know how Business operations are wrapped into
BusinessService
adapter and how to use it.Let's see how to map them directly using these operations:
import org.nuxeo.ecm.automation.client.jaxrs.spi.JsonMarshalling; // Let's instiante a BusinessBean 'File': BusinessBean file = new BusinessBean("File", "File description", "File"); // BusinessBean 'File' Marshaller has to be registered in Automation Client following this way: JsonMarshalling.addMarshaller(PojoMarshaller.forClass(file.getClass())); // Injecting the BusinessBean 'File' into the operation BusinessCreateOperation let you create a document server side. file = (BusinessBean) session.newRequest("Operation.BusinessCreateOperation").setInput(file).set("name",file.getTitle()).set("parentPath","/").execute(); // Update document on server (pojo <-> adapter update) using BusinessUpdateOperation file.setTitle("Title Updated"); file = (BusinessBean) session.newRequest("Operation.BusinessUpdateOperation").setInput(file).execute();
GitHub Nuxeo Automation Tests
Here is the complete code of the example. For more examples, see the unit tests in nuxeo-automation-test
project.
Automation Client Service Adapter
Default Service Adapter
Nuxeo Automation Client provides an "Adapter Service" to encapsulate business logic.
Default services provided:
DocumentService
- (Class on GitHub): This API provides access to basic operation like CRUD, lock, version...- BusinessService (Class on GitHub): This API provides access to Business Operations.
DocumentService Usage
Here is an example of using DocumentService
API:
import org.nuxeo.ecm.automation.client.model.Document;
import org.nuxeo.ecm.automation.client.adapters.DocumentService;
import org.nuxeo.ecm.automation.client.Session;
// Test document creation
Document myDocument = new Document("myfolder2", "Folder");
myDocument.set("dc:title", "My Test Folder");
myDocument.set("dc:description", "test");
myDocument.set("dc:subjects", "a,b,c\\,d");
DocumentService documentService = session.getAdapter(DocumentService.class);
// Here is the way to create a document by using DocumentService
// Note you can put in first parameter, the Id or Path of the parent document
myDocument = documentService.createDocument(DocumentParent.getId(), myDocument);
// Fetch the document with its dublincore properties
// You can add several schemas like ...(myDocument, "dublincore", "yourSchema");
myDocument = documentService.getDocument(myDocument, "dublincore");
// Update the document title
myDocument.set("dc:title", "New Title");
documentService.update(myDocument);
// Fetch the document with all its properties (all schemas)
myDocument = documentService.getDocument(folder, "*");
BusinessService Usage (since 5.7.2)
Here is an example of using BusinessService
API (this example is the same previous one with Business Objects but this time using BusinessService
):
import org.nuxeo.ecm.automation.client.adapters.BusinessService;
import org.nuxeo.ecm.automation.client.Session;
// A simple custom POJO client side
BusinessBean file = new BusinessBean("File", "File description", "File");
// Fetching the business service adapter
BusinessService<BusinessBean> businessService = session.getAdapter(BusinessService.class);
// Create server side injecting the POJO to the service method create
file = (BusinessBean) businessService.create(file, file.getTitle(), "/");
// Process to update with POJO
file.setTitle("Update");
file = (BusinessBean) businessService.update(file);
Contributing a New Custom Service Adapter
You can contribute your own adapter service by Java to encapsulate your own business logic.
To achieve this registration, you have to provide an adapter service factory that is going to instantiate your adapter. You can find an example of factory for the DocumentService
class: DocumentServiceFactory
(source code on GitHub).
Here is an example of registration:
HttpAutomationClient client = new HttpAutomationClient("http://localhost:8080/nuxeo/site/automation");
// Register you own service factory
client.registerAdapter(new MyServiceFactory());
// And then retrieve your custom adapter service
MyService myService = session.getAdapter(MyService.class);
Automation Client in OSGi Environment
Since 5.7.1, Nuxeo Automation Client is OSGi compliant.
Example to Get Automation Client in an OSGi Environment
(Here we're using Apache Felix Framework)
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import javax.inject.Inject;
@Inject
BundleContext bundleContext;
// Example with Felix ServiceReference
ServiceReference serviceReference = bc.getServiceReference(AutomationClientFactory.class.getName());
// Fetch the Automation Client Factory
AutomationClientFactory factory = (AutomationClientFactory) bundleContext.getService(serviceReference);
// Get the Automation Client with given URL of you server
AutomationClient client = factory.getClient(new URL("http://localhost:8080/nuxeo/site/automation"));
// Get client with http connection timeout as parameter in milliseconds
client = factory.getClient(new URL("http://localhost:8080/nuxeo/site/automation"), 3600);
Bundle-SymbolicName: org.nuxeo.ecm.automation.client
Bundle-Activator: org.nuxeo.ecm.automation.client.jaxrs.impl.AutomationClientActivator
Export-Package: org.nuxeo.ecm.automation.client.adapters;version="0.0.
0.qualifier",org.nuxeo.ecm.automation.client;version="0.0.0.qualifier
",org.nuxeo.ecm.automation.client.model;version="0.0.0.qualifier"
Import-Package: javax.activation,javax.mail;version="1.4",javax.mail.i
nternet;version="1.4",org.apache.commons.lang;version="2.6",org.apach
e.commons.logging;version="1.1",org.apache.http;version="4.2",org.apa
che.http.auth;version="4.2",org.apache.http.client;version="4.2",org.
apache.http.client.methods;version="4.2",org.apache.http.conn;version
="4.2",org.apache.http.entity;version="4.2",org.apache.http.impl.clie
nt;version="4.2",org.apache.http.impl.conn.tsccm;version="4.2",org.ap
ache.http.params;version="4.2",org.apache.http.protocol;version="4.2"
,org.codehaus.jackson;version="1.8",org.codehaus.jackson.map;version=
"1.8",org.codehaus.jackson.map.annotate;version="1.8",org.codehaus.ja
ckson.map.deser;version="1.8",org.codehaus.jackson.map.introspect;ver
sion="1.8",org.codehaus.jackson.map.module;version="1.8",org.codehaus
.jackson.map.type;version="1.8",org.codehaus.jackson.type;version="1.
8",org.nuxeo.ecm.automation.client;version="0.0",org.nuxeo.ecm.automa
tion.client.adapters;version="0.0",org.nuxeo.ecm.automation.client.mo
del;version="0.0",org.osgi.framework;version="1.5"