/**
* This file was automatically generated by the Mule Development Kit
*/
package org.nuxeo.mule;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.mule.api.ConnectionException;
import org.mule.api.MuleException;
import org.mule.api.annotations.Category;
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Connect;
import org.mule.api.annotations.ConnectionIdentifier;
import org.mule.api.annotations.Connector;
import org.mule.api.annotations.Disconnect;
import org.mule.api.annotations.MetaDataKeyRetriever;
import org.mule.api.annotations.MetaDataRetriever;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.Source;
import org.mule.api.annotations.SourceThreadingModel;
import org.mule.api.annotations.Transformer;
import org.mule.api.annotations.ValidateConnection;
import org.mule.api.annotations.display.FriendlyName;
import org.mule.api.annotations.display.Password;
import org.mule.api.annotations.display.Placement;
import org.mule.api.annotations.display.Summary;
import org.mule.api.annotations.lifecycle.Start;
import org.mule.api.annotations.lifecycle.Stop;
import org.mule.api.annotations.param.ConnectionKey;
import org.mule.api.annotations.param.Default;
import org.mule.api.annotations.param.InboundHeaders;
import org.mule.api.annotations.param.Optional;
import org.mule.api.callback.HttpCallback;
import org.mule.api.callback.SourceCallback;
import org.mule.api.callback.StopSourceCallback;
import org.mule.common.metadata.MetaData;
import org.mule.common.metadata.MetaDataKey;
import org.nuxeo.ecm.automation.client.AutomationClient;
import org.nuxeo.ecm.automation.client.OperationRequest;
import org.nuxeo.ecm.automation.client.adapters.DocumentService;
import org.nuxeo.ecm.automation.client.jaxrs.impl.HttpAutomationClient;
import org.nuxeo.ecm.automation.client.model.Blob;
import org.nuxeo.ecm.automation.client.model.DocRef;
import org.nuxeo.ecm.automation.client.model.Document;
import org.nuxeo.ecm.automation.client.model.Documents;
import org.nuxeo.ecm.automation.client.model.FileBlob;
import org.nuxeo.ecm.automation.client.model.OperationDocumentation;
import org.nuxeo.ecm.automation.client.model.OperationDocumentation.Param;
import org.nuxeo.ecm.automation.client.model.PropertyMap;
import org.nuxeo.ecm.automation.client.model.RecordSet;
import org.nuxeo.ecm.automation.client.model.StringBlob;
import org.nuxeo.mule.blob.BlobConverters;
import org.nuxeo.mule.blob.NuxeoBlob;
import org.nuxeo.mule.mapper.Doc2Map;
import org.nuxeo.mule.mapper.MapUnmangler;
import org.nuxeo.mule.metadata.MetaDataIntrospector;
import org.nuxeo.mule.poll.EventPollingClient;
import org.nuxeo.mule.poll.ListenerConfig;
/**
* Connector that uses Nuxeo Automation java client to leverage Nuxeo Rest API
*
* @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
*
*/
@Connector(name = "nuxeo", schemaVersion = "1.0-SNAPSHOT")
public class NuxeoConnector extends BaseDocumentService {
private static final Logger logger = Logger.getLogger(NuxeoConnector.class);
/**
* Nuxeo Server name (IP or DNS name)
*/
@Configurable
@Placement(group = "Connection")
@Optional
@Default("127.0.0.1")
private String serverName = "127.0.0.1";
/**
* Protocol (http/https) to access Nuxeo Server
*/
@Configurable
@Placement(group = "Connection")
@Optional
@Default("http")
private String protocol = "http";
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
/**
* Port used to connect to Nuxeo Server
*/
@Configurable
@Placement(group = "Connection")
@Optional
@Default("8080")
private String port = "8080";
/**
* Context Path for Nuxeo instance
*/
@Configurable
@Placement(group = "Connection")
@Optional
@Default("nuxeo")
private String contextPath = "nuxeo";
public void setPollingInterval(long pollingInterval) {
this.pollingInterval = pollingInterval;
}
public long getPollingInterval() {
return pollingInterval;
}
public List<ListenerConfig> getPendingListeners() {
return pendingListeners;
}
/**
* comma separated String listing schemas that must be sent by the server
* when returning Documents
*/
@Configurable
@Optional
@Placement(group = "Marshaling")
@Default("dublincore,common")
private String defaultSchemas = "dublincore,common";
/**
* Define interval ued between polls to Nuxeo server to fetch events
*/
@Configurable
@Placement(group = "Polling")
@Optional
@Default("5000")
private long pollingInterval = 5000;
private List<ListenerConfig> pendingListeners = new ArrayList<ListenerConfig>();
protected EventPollingClient eventPollingClient;
protected MetaDataIntrospector introspector;
public NuxeoConnector() {
serverName = "localhost";
port = "8080";
contextPath = "nuxeo";
}
/**
* get the default schemas that should be set by the server when sending
* Documents
*
* @return comma separated String listing schemas
*/
public String getDefaultSchemas() {
return defaultSchemas;
}
/**
* set the default schemas that should be set by the server when sending
* Documents
*
* @param defaultSchemas comma separated String listing schemas
*/
public void setDefaultSchemas(String defaultSchemas) {
this.defaultSchemas = defaultSchemas;
}
/**
* get Nuxeo Server Name
*
* @return Nuxeo Server Name
*/
public String getServerName() {
return serverName;
}
/**
* get Nuxeo Server Port
*
* @return Nuxeo Server Port
*/
public String getPort() {
return port;
}
/**
* get Nuxeo Server Context pat
*
* @return Nuxeo Server Context path
*/
public String getContextPath() {
return contextPath;
}
/**
* set Nuxeo Server name
*
* @param serverName
*/
public void setServerName(String serverName) {
this.serverName = serverName;
}
/**
* set port used to connect to Nuxeo Server
*
* @param port
*/
public void setPort(String port) {
this.port = port;
}
/**
* set Context path of the target Nuxeo Server
*
* @param contextPath
*/
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
protected String getServerUrl() {
if (protocol == null) {
protocol = "http";
}
if (contextPath == null || contextPath.isEmpty()) {
return protocol + "://" + serverName + ":" + port + "/"
+ "automation";
} else {
return protocol + "://" + serverName + ":" + port + "/"
+ contextPath + "/site/automation";
}
}
/**
* Connect to Nuxeo Server via Automation java client
*
* @param username Nuxeo user name
* @param password Nuxeo password
* @throws ConnectionException
*/
@Connect
public void connect(@ConnectionKey
String username, @Password
String password) throws ConnectionException {
AutomationClient client = new HttpAutomationClient(getServerUrl());
session = client.getSession(username, password);
session.setDefaultSchemas(defaultSchemas);
docService = session.getAdapter(DocumentService.class);
logger.info("Connect Nuxeo Connector");
}
/**
* Disconnect
*/
@Disconnect
public void disconnect() {
if (session != null) {
session.close();
}
}
/**
* Are we connected
*
* @return true if an Automation Session is active
*/
@ValidateConnection
public boolean isConnected() {
return (session != null);
}
/**
* Are we connected
*
* @return fake ConnectionId based on serverUrl and username
*/
@ConnectionIdentifier
public String connectionId() {
if (session != null) {
return getServerUrl() + session.getLogin();
} else {
return getServerUrl();
}
}
/**
* Runs a NXQL Query against repository, result is returned as a list of
* pages of Document
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample nuxeo:query}
*
* @param query the NXQL Query
* @param page the page number
* @param pageSize the page size
* @param queryParams the query parameters if any
* @param sortInfo sort columns
* @return a batched list of Documents
* @throws Exception if operation can not be executed
*/
@Processor
@Category(name = "Query", description = "execute a NXQL Query")
public Documents query(@Placement(group = "operation parameters")
String query, @Placement(group = "operation parameters")
@Optional
@Default("0")
Integer page, @Placement(group = "operation parameters")
@Optional
@Default("20")
Integer pageSize, @Placement(group = "operation parameters")
@Optional
@FriendlyName("Query parameters")
List<String> queryParams, @Placement(group = "operation parameters")
@Optional
@FriendlyName("Sort columns")
List<String> sortInfo) throws Exception {
logger.info("Execute simple query on " + query);
return (Documents) execPageProvider(null, query, page, pageSize,
queryParams, sortInfo, false);
}
/**
* Runs a NXQL Query against repository, result is returned as a list of
* pages of Records
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:query-and-fetch}
*
* @param query the NXQL Query
* @param page the page number
* @param pageSize the page size
* @param queryParams the query parameters if any
* @param sortInfo sort columns
* @return a batched list of Records
* @throws Exception if operation can not be executed
*/
@Category(name = "Query", description = "execute a queryAndFetch")
@Processor
public RecordSet queryAndFetch(@Placement(group = "operation parameters")
String query, @Placement(group = "operation parameters")
@Optional
@Default("0")
Integer page, @Placement(group = "operation parameters")
@Optional
@Default("20")
Integer pageSize, @Placement(group = "operation parameters")
@Optional
@FriendlyName("Query parameters")
List<String> queryParams, @Placement(group = "operation parameters")
@Optional
@FriendlyName("Sort columns")
List<String> sortInfo) throws Exception {
return (RecordSet) execPageProvider(null, query, page, pageSize,
queryParams, sortInfo, true);
}
/**
* Runs a Page Provider (named query) against repository, result is returned
* as a list of pages of Document
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample nuxeo:page-provider}
*
* @param pageProviderName the name if the PagteProvider to run
* @param page the page number
* @param pageSize the page size
* @param queryParams the query parameters if any
* @param sortInfo sort columns
* @return a batched list of Documents
* @throws Exception if operation can not be executed
*/
@Processor
@Category(name = "Query", description = "execute a PageProvider")
public Documents pageProvider(@Placement(group = "operation parameters")
String pageProviderName, @Placement(group = "operation parameters")
@Optional
@Default("0")
Integer page, @Placement(group = "operation parameters")
@Optional
@Default("20")
Integer pageSize, @Placement(group = "operation parameters")
@Optional
@FriendlyName("Query parameters")
List<String> queryParams, @Placement(group = "operation parameters")
@Optional
@FriendlyName("Sort columns")
List<String> sortInfo) throws Exception {
return (Documents) execPageProvider(pageProviderName, null, page,
pageSize, queryParams, sortInfo, false);
}
protected Object execPageProvider(String pageProvider, String query,
Integer page, Integer pageSize, List<String> queryParams,
List<String> sortInfo, boolean useRecordSet) throws Exception {
OperationRequest request;
if (useRecordSet) {
request = session.newRequest("Resultset.PageProvider");
} else {
request = session.newRequest("Document.PageProvider");
}
if (query != null && !query.isEmpty()) {
request.set("query", query);
} else {
request.set("providerName", pageProvider);
}
request.set("page", page);
request.set("pageSize", pageSize);
if (queryParams != null) {
request.set("queryParams", queryParams);
}
if (sortInfo != null) {
request.set("sortInfo", sortInfo);
}
return request.execute();
}
/**
* Executes an arbitrary operation
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample nuxeo:run-operation}
*
* @param operationId Name of the Automation Operation
* @param input Input of the Operation
* @param params Parameters of the Operation
* @param inbound inbound headers that can hold some automation Context info
*
* @return Result of the Operation
* @throws Exception if operation can not be executed
*/
@Processor
@Category(name = "Automation RPC", description = "Call any Automation Operation or Chain")
public Object runOperation(@Placement(group = "operation parameters")
String operationId, @Placement(group = "operation parameters")
@Optional
Object input, @Optional
@Placement(group = "operation parameters")
Map<String, String> params,
@InboundHeaders("*") final Map<String, Object> inbound) throws Exception {
OperationRequest request = session.newRequest(operationId);
OperationDocumentation opDef = request.getOperation();
propagateAutomationContext(inbound, request);
// fill operation parameter according to signature
for (Param param : opDef.getParams()) {
for (String pname : params.keySet()) {
if (pname.equals(param.getName())) {
Object val = params.get("name");
// handle specific cases for properties
if (param.getType().equals("properties")) {
if (val instanceof Map) {
val = MapUnmangler.unMangle((Map<String, Object>)val);
}
}
request.set(pname, val);
break;
}
}
}
if (input != null) {
String[] sig = opDef.getSignature();
for (int i = 0; i < sig.length; i = i + 2) {
String inputType = sig[i];
if (inputType.equals("Document")) {
request.setInput(new DocRef(input.toString()));
} else if (inputType.equals("Blob")) {
if (input instanceof File) {
request.setInput(new FileBlob((File) input));
}
}
}
}
return request.execute();
}
/**
* Get an {@link InputStream} from a {@link Blob}, or a {@link Map}
* representing a Blob in a Document
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample nuxeo:get-as-stream}
*
* @param input the {@link PropertyMap}, {@link Map} or {@link Blob}
* @return the {@link InputStream} if any
*/
//@Transformer(sourceTypes = { PropertyMap.class, Map.class, Blob.class })
@Processor
@Category(name = "Dynamic Converters", description = "converts a Blob or a Map representing a Blob to a Stream")
public InputStream getAsStream(@Placement(group = "input (PropertyMap.class, Map.class, Blob.class)") Object input) {
try {
return BlobConverters.blobToStream(session, input);
} catch (UnsupportedEncodingException e) {
logger.error("Unable to get Stream from Blob", e);
return null;
}
}
/**
* Get an {@link Blob} from a {@link Map} of properties representing a Blob in the Document
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample nuxeo:get-as-blob}
*
* @param input the {@link PropertyMap}, {@link Map}
* @return the {@link InputStream} if any
*/
//@Transformer(sourceTypes = { PropertyMap.class, Map.class })
@Processor
@Category(name = "Dynamic Converters", description = "converts a Map representing a Blob to a Blob")
public Blob getAsBlob(@Placement(group = "input (PropertyMap.class, Map.class)") Object input) {
try {
return BlobConverters.mapToBlob(session, input);
} catch (UnsupportedEncodingException e) {
logger.error("Unable to get Blob from input", e);
return null;
}
}
/**
* Register an event listener on the Nuxeo side that will callback Mule on
* an automatically generated endpoint, passing the list of events as
* payload
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:register-listener}
*
* @param eventNames lists of events to listen to, ca be null to listen to
* all events
* @param categories lists of event categories to listen to, ca be null to
* listen to all categories
* @param repository target repository
* @param userName listen only to events generated by a given user, can be
* null to listen to all users
* @param docId listen only to events generated for a given doc, can be null
* to listen to all docs
* @param eventCallBack the inbout callback endpoint
*/
// @Processor
public void registerListener(@Optional
List<String> eventNames, @Optional
List<String> categories, @Optional
String repository, @Optional
String userName, @Optional
String docId, @Optional
HttpCallback eventCallBack) {
String callBackUrl = eventCallBack.getUrl();
// call operation to deploy a listener on the Nuxeo side
}
/****************************** Transformers **************/
/**
* Creates a Blob from a File or a FileInputStream
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample nuxeo:file-to-blob}
*
* @param input the input File
* @return the Blob wrapping the File
*/
@Transformer(sourceTypes = { File.class, FileInputStream.class,
byte[].class })
@Summary("converts a File to a Blob")
public static NuxeoBlob fileToBlob(Object input) {
return BlobConverters.fileToBlob(input);
}
/**
* Creates a Blob from a String
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:string-to-blob}
*
* @param input the input String
* @return the Blob wrapping the String
*/
@Transformer(sourceTypes = { String.class })
@Summary("converts a String to a Blob")
public static NuxeoBlob stringToBlob(String input) {
return new NuxeoBlob(new StringBlob(input));
}
/**
* Convert a Blob to a File
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:blob-to-file}
*
* @param blob the input blob
* @return the File extracted from the Blob
*/
@Transformer(sourceTypes = { Blob.class })
@Summary("converts a Blob to a File")
public static File blobToFile(Blob blob) {
try {
return BlobConverters.blobToFile(blob);
} catch (Exception e) {
logger.error("Failed to extract File from Blob", e);
return null;
}
}
/**
* Convert a Document to a Simple Map
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:document-to-map}
*
* @param doc the Document to convert
* @return the resulting Map<String, Object>
*/
@Transformer(sourceTypes = { Document.class })
@Summary("converts a Nuxeo document to a Map")
public static Map<String, Object> documentToMap(Document doc) {
return Doc2Map.documentToMap(doc);
}
/**
* Converts a list of Documents into a simple list of Map
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:documents-to-list-of-map}
*
* @param docs the Documents list to convert
* @return the resulting List of Map
*/
@Transformer(sourceTypes = { Documents.class })
@Summary("converts lists of documents to lists of Map")
public static List<Map<String, Object>> documentsToListOfMap(Documents docs) {
return Doc2Map.documentsToListOfMap(docs);
}
@Start
public void init() throws MuleException {
logger.info("Starting Nuxeo Connector");
if (isConnected() && pendingListeners.size() > 0) {
for (ListenerConfig config : pendingListeners) {
getPollingClient().subscribe(config);
}
introspector = new MetaDataIntrospector(session);
}
}
@Stop
public void tearDown() {
logger.info("Stoping Nuxeo Connector");
try {
BlobConverters.cleanup();
} catch (Exception e) {
logger.error("Error during cleanup", e);
}
}
protected EventPollingClient getPollingClient() {
if (eventPollingClient == null && session != null) {
eventPollingClient = new EventPollingClient(session,
pollingInterval);
}
return eventPollingClient;
}
/**
* Make Mule listen to Nuxeo events.
*
* {@sample.xml ../../../doc/Nuxeo-connector.xml.sample
* nuxeo:listen-to-events}
*
* @param callback the Mule CallBack
* @return
*/
@Source(primaryNodeOnly = true, threadingModel = SourceThreadingModel.NONE)
@Summary("listen to events on a remote Nuxeo server")
public StopSourceCallback listenToEvents(
/*
* @Placement(group = "Events filtering") List<String> eventNames,
* @Placement(group = "Events filtering") Map<EventFilter, String> filters,
*/final SourceCallback callback) {
ListenerConfig config = new ListenerConfig(null, callback);
if (this.isConnected()) {
getPollingClient().subscribe(config);
} else {
pendingListeners.add(config);
logger.info("Pending Subscription");
}
return new StopSourceCallback() {
@Override
public void stop() throws Exception {
getPollingClient().unsubscribe();
}
};
}
// ******************************************************************
//
// Data Sense mapping
//
// For now, map MetaDataKey to Nuxeo doc types
//
protected MetaDataIntrospector getIntrospector() {
if (introspector == null) {
introspector = new MetaDataIntrospector(session);
}
return introspector;
}
@MetaDataKeyRetriever
public List<MetaDataKey> getMetaDataKeys() throws Exception {
List<MetaDataKey> types = getIntrospector().getMuleTypes();
for (MetaDataKey key : types) {
logger.info("registering type " + key.getId()
+ " with display name " + key.getDisplayName());
}
return types;
}
@MetaDataRetriever
public MetaData getMetaData(MetaDataKey key) throws Exception {
logger.info("retrieve metadata for " + key.getId());
return getIntrospector().getMuleTypeMetaData(key.getId());
}
}