Skip to content

Instantly share code, notes, and snippets.

@ogrisel
Last active December 11, 2015 12:58
Show Gist options
  • Select an option

  • Save ogrisel/4604351 to your computer and use it in GitHub Desktop.

Select an option

Save ogrisel/4604351 to your computer and use it in GitHub Desktop.
Workaround for complex properties issues using the Nuxeo 5.6 Automation HTTP API
.classpath
.project
.settings/
target/
<?xml version="1.0"?>
<xs:schema targetNamespace="http://www.nuxeo.org/ecm/schemas/book/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nxs="http://www.nuxeo.org/ecm/schemas/book/">
<xs:include schemaLocation="core-types.xsd" />
<xs:element name="identifiers" type="nxs:identifierList" />
<xs:complexType name="identifierList">
<xs:sequence>
<xs:element name="item" type="nxs:identifier" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="identifier">
<xs:sequence>
<xs:element name="type" type="xs:string" />
<xs:element name="value" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
package org.nuxeo.sample.operation.complextypes.client;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.nuxeo.ecm.automation.client.model.PropertyList;
/**
* Override the toString method to be able to use JSON marshalling instead of
* the flat line based representation.
*/
@JsonSerialize(using = ComplexPropertyListSerializer.class)
public class ComplexPropertyList extends PropertyList {
private static final long serialVersionUID = 1L;
ObjectMapper mapper = new ObjectMapper();
@Override
public String toString() {
try {
return mapper.writeValueAsString(this);
} catch (Exception e) {
// this should never happen...
return super.toString();
}
}
}
package org.nuxeo.sample.operation.complextypes.client;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class ComplexPropertyListSerializer extends JsonSerializer<ComplexPropertyList> {
@Override
public void serialize(ComplexPropertyList value, JsonGenerator jgen,
SerializerProvider sp) throws IOException,
JsonProcessingException {
jgen.writeObject(value.list());
}
}
package org.nuxeo.sample.operation.complextypes.client;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.nuxeo.ecm.automation.client.model.PropertyMap;
/**
* Override the toString method to be able to use JSON marshalling instead of
* the flat key=value line based representation.
*/
@JsonSerialize(using = ComplexPropertyMapSerializer.class)
public class ComplexPropertyMap extends PropertyMap {
private static final long serialVersionUID = 1L;
ObjectMapper mapper = new ObjectMapper();
@Override
public String toString() {
try {
return mapper.writeValueAsString(this);
} catch (Exception e) {
// this should never happen...
return super.toString();
}
}
}
package org.nuxeo.sample.operation.complextypes.client;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class ComplexPropertyMapSerializer extends JsonSerializer<ComplexPropertyMap> {
@Override
public void serialize(ComplexPropertyMap value, JsonGenerator jgen,
SerializerProvider sp) throws IOException,
JsonProcessingException {
jgen.writeObject(value.map());
}
}
/*
* Copyright (c) 2013 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package org.nuxeo.sample.operation.complextypes;
import java.io.Serializable;
import java.util.Map;
import org.codehaus.jackson.map.ObjectMapper;
import org.nuxeo.ecm.automation.core.Constants;
import org.nuxeo.ecm.automation.core.annotations.Context;
import org.nuxeo.ecm.automation.core.annotations.Operation;
import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
import org.nuxeo.ecm.automation.core.annotations.Param;
import org.nuxeo.ecm.automation.core.collectors.DocumentModelCollector;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentRef;
/**
* Alternative version of the UpdateDocument operation that accepts a raw JSON
* representation of the fields to update to make it possible to deal with
* complex types such as list of maps of strings.
*/
@Operation(id = CreateDocumentWithComplexProperties.ID, category = Constants.CAT_DOCUMENT, label = "Create", description = "Create a new document in the input folder. You can initialize the document properties using the 'properties' parameter. The properties are specified as <i>key=value</i> pairs separated by a new line. The key used for a property is the property xpath. To specify multi-line values you can use a \\ charcater followed by a new line. <p>Example:<pre>dc:title=The Document Title<br>dc:description=foo bar</pre>. Returns the created document.")
public class CreateDocumentWithComplexProperties {
public static final String ID = "Document.CreateWithComplexProperties";
@Context
protected CoreSession session;
@Param(name = "type")
protected String type;
@Param(name = "name", required = false)
protected String name;
@Param(name = "properties", required = false)
protected String jsonProperties;
@OperationMethod(collector = DocumentModelCollector.class)
public DocumentModel run(DocumentModel parentDoc) throws Exception {
if (name == null) {
name = "Untitled";
}
DocumentModel newDoc = session.createDocumentModel(
parentDoc.getPathAsString(), name, type);
if (jsonProperties != null) {
ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> propertyMap = mapper.readValue(jsonProperties,
Map.class);
for (Map.Entry<String, Object> field : propertyMap.entrySet()) {
newDoc.setPropertyValue(field.getKey(),
(Serializable) field.getValue());
}
}
return session.createDocument(newDoc);
}
@OperationMethod(collector = DocumentModelCollector.class)
public DocumentModel run(DocumentRef doc) throws Exception {
return run(session.getDocument(doc));
}
}
Manifest-Version: 1.0
Bundle-Vendor: Nuxeo
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Bundle-Version: 5.6
Bundle-Name: org.nuxeo.drive.core
Bundle-ManifestVersion: 2
Nuxeo-Component: OSGI-INF/operation-contrib.xml
Bundle-SymbolicName: org.nuxeo.sample.operation.complextypes;singleton:=true
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
<?xml version="1.0"?>
<component name="org.nuxeo.sample.operation.complextypes.operations"
version="1.0">
<extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent"
point="operations">
<operation
class="org.nuxeo.sample.operation.complextypes.UpdateDocumentWithComplexProperties" />
<operation
class="org.nuxeo.sample.operation.complextypes.CreateDocumentWithComplexProperties" />
</extension>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-features-parent</artifactId>
<version>5.6</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.nuxeo.features</groupId>
<artifactId>nuxeo-automation-complex-property-updates</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-server</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-core</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi-core</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.webengine</groupId>
<artifactId>nuxeo-webengine-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-api</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-schema</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.runtime</groupId>
<artifactId>nuxeo-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.common</groupId>
<artifactId>nuxeo-common</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.runtime</groupId>
<artifactId>nuxeo-runtime-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.core</groupId>
<artifactId>nuxeo-core-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-features-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-client</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-features</artifactId>
</dependency>
<!-- For webengine servlet? -->
<dependency>
<groupId>org.nuxeo.ecm.webengine</groupId>
<artifactId>nuxeo-webengine-core</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-naming</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-plus</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-util</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.runtime</groupId>
<artifactId>nuxeo-runtime-jtajca</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.automation</groupId>
<artifactId>nuxeo-automation-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-url-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-url-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-types-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.nuxeo.ecm.platform</groupId>
<artifactId>nuxeo-platform-types-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>public</id>
<url>http://maven.nuxeo.org/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>public-snapshot</id>
<url>http://maven.nuxeo.org/nexus/content/groups/public-snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
<?xml version="1.0"?>
<component name="org.nuxeo.sample.tests.book.type" version="1.0">
<extension target="org.nuxeo.ecm.core.schema.TypeService"
point="schema">
<schema name="book" src="book.xsd" />
</extension>
<extension target="org.nuxeo.ecm.core.schema.TypeService"
point="doctype">
<doctype name="Book" extends="File">
<schema name="book" />
<facet name="Downloadable" />
<facet name="Versionable" />
<facet name="Publishable" />
<facet name="Commentable" />
<facet name="HasRelatedText" />
</doctype>
</extension>
</component>
/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.sample.operation.complextypes.tests;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.ecm.automation.AutomationService;
import org.nuxeo.ecm.automation.client.Constants;
import org.nuxeo.ecm.automation.client.Session;
import org.nuxeo.ecm.automation.client.jaxrs.impl.HttpAutomationClient;
import org.nuxeo.ecm.automation.client.model.Document;
import org.nuxeo.ecm.automation.client.model.PropertyList;
import org.nuxeo.ecm.automation.core.operations.document.FetchDocument;
import org.nuxeo.ecm.automation.server.AutomationServer;
import org.nuxeo.ecm.automation.test.RestFeature;
import org.nuxeo.ecm.core.test.annotations.Granularity;
import org.nuxeo.ecm.core.test.annotations.RepositoryConfig;
import org.nuxeo.runtime.test.runner.Deploy;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.test.runner.Jetty;
import org.nuxeo.runtime.test.runner.LocalDeploy;
import org.nuxeo.sample.operation.complextypes.CreateDocumentWithComplexProperties;
import org.nuxeo.sample.operation.complextypes.UpdateDocumentWithComplexProperties;
import org.nuxeo.sample.operation.complextypes.client.ComplexPropertyList;
import org.nuxeo.sample.operation.complextypes.client.ComplexPropertyMap;
import com.google.inject.Inject;
/**
* @author <a href="mailto:og@nuxeo.com">Olivier Grisel</a>
*/
@RunWith(FeaturesRunner.class)
@Deploy({ "org.nuxeo.ecm.platform.url.api", "org.nuxeo.ecm.platform.url.core",
"org.nuxeo.ecm.platform.types.api",
"org.nuxeo.ecm.platform.types.core",
"org.nuxeo.sample.operation.complextypes" })
@LocalDeploy({ "org.nuxeo.sample.operation.complextypes:test-core-type-contrib.xml" })
@Features(RestFeature.class)
@Jetty(port = 18080)
@RepositoryConfig(cleanup = Granularity.METHOD)
public class TestComplexTypeUpdate {
@Inject
AutomationServer server;
@Inject
AutomationService service;
@Inject
Session session;
@Inject
HttpAutomationClient client;
@Test
public void testCreateWithComplexProperties() throws Exception {
ComplexPropertyMap props = new ComplexPropertyMap();
props.set("dc:title", "Harry Potter and the Philosopher's Stone");
ComplexPropertyMap isbn10 = new ComplexPropertyMap();
isbn10.set("value", "0-7475-3269-9");
isbn10.set("type", "isbn-10");
ComplexPropertyList identifiers = new ComplexPropertyList();
identifiers.add(isbn10);
props.set("book:identifiers", identifiers);
Document root = (Document) session.newRequest(FetchDocument.ID).set(
"value", "/").execute();
Document book = (Document) session.newRequest(
CreateDocumentWithComplexProperties.ID).setInput(root).setHeader(
Constants.HEADER_NX_SCHEMAS, "*").set("type", "Book").set(
"name", "harry").set("properties", props).execute();
PropertyList returnedIdentifiers = book.getProperties().getList(
"book:identifiers");
assertEquals(1, returnedIdentifiers.size());
assertEquals("isbn-10", returnedIdentifiers.getMap(0).get("type"));
assertEquals("0-7475-3269-9", returnedIdentifiers.getMap(0).get("value"));
// add a second identifier and update the same document
ComplexPropertyMap isbn13 = new ComplexPropertyMap();
isbn10.set("value", "978-0-7475-3269-9");
isbn10.set("type", "isbn-13");
identifiers.add(isbn13);
book = (Document) session.newRequest(
UpdateDocumentWithComplexProperties.ID).setInput(book).setHeader(
Constants.HEADER_NX_SCHEMAS, "*").set("properties", props).execute();
returnedIdentifiers = book.getProperties().getList("book:identifiers");
assertEquals(2, returnedIdentifiers.size());
}
}
/*
* Copyright (c) 2013 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package org.nuxeo.sample.operation.complextypes;
import java.io.Serializable;
import java.util.Map;
import org.codehaus.jackson.map.ObjectMapper;
import org.nuxeo.ecm.automation.ConflictOperationException;
import org.nuxeo.ecm.automation.core.Constants;
import org.nuxeo.ecm.automation.core.annotations.Context;
import org.nuxeo.ecm.automation.core.annotations.Operation;
import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
import org.nuxeo.ecm.automation.core.annotations.Param;
import org.nuxeo.ecm.automation.core.collectors.DocumentModelCollector;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
/**
* Alternative version of the UpdateDocument operation that accepts a raw JSON
* representation of the fields to update to make it possible to deal with
* complex types such as list of maps of strings.
*/
@Operation(id = UpdateDocumentWithComplexProperties.ID, category = Constants.CAT_DOCUMENT, label = "Update Complex Properties", description = "Set multiple properties on the input document. The properties are specified as <i>key=value</i> pairs separated by a new line. The key used for a property is the property xpath. To specify multi-line values you can use a \\ character followed by a new line. <p>Example:<pre>dc:title=The Document Title<br>dc:description=foo bar</pre>For updating a date, you will need to expose the value as ISO 8601 format, for instance : <p>Example:<pre>dc:title=The Document Title<br>dc:issued=@{org.nuxeo.ecm.core.schema.utils.DateParser.formatW3CDateTime(CurrentDate.date)}</pre><p>Returns back the updated document.")
public class UpdateDocumentWithComplexProperties {
public static final String ID = "Document.UpdateWithComplexProperties";
@Context
protected CoreSession session;
@Param(name = "properties")
protected String jsonProperties;
@Param(name = "save", required = false, values = "true")
protected boolean save = true;
@Param(name = "changeToken", required = false)
protected String changeToken = null;
@OperationMethod(collector = DocumentModelCollector.class)
public DocumentModel run(DocumentModel doc) throws Exception {
if (jsonProperties == null) {
return doc;
}
if (changeToken != null) {
// Check for dirty update
String repoToken = doc.getChangeToken();
if (!changeToken.equals(repoToken)) {
throw new ConflictOperationException(doc);
}
}
ObjectMapper mapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> propertyMap = mapper.readValue(jsonProperties,
Map.class);
for (Map.Entry<String, Object> field : propertyMap.entrySet()) {
doc.setPropertyValue(field.getKey(),
(Serializable) field.getValue());
}
if (save) {
doc = session.saveDocument(doc);
}
return doc;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment