/** * 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()); } }