001/* 002 * Copyright (c) 2006-2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * vpasquier 011 * slacoin 012 */ 013package org.nuxeo.ecm.automation.core.impl; 014 015import java.net.URL; 016import java.util.ArrayList; 017import java.util.Arrays; 018import java.util.Calendar; 019import java.util.Collection; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023 024import org.nuxeo.ecm.automation.AutomationService; 025import org.nuxeo.ecm.automation.InvalidChainException; 026import org.nuxeo.ecm.automation.OperationChain; 027import org.nuxeo.ecm.automation.OperationContext; 028import org.nuxeo.ecm.automation.OperationDocumentation; 029import org.nuxeo.ecm.automation.OperationException; 030import org.nuxeo.ecm.automation.OperationNotFoundException; 031import org.nuxeo.ecm.automation.OperationParameters; 032import org.nuxeo.ecm.automation.OperationType; 033import org.nuxeo.ecm.automation.core.Constants; 034import org.nuxeo.ecm.automation.core.OperationChainContribution; 035import org.nuxeo.ecm.automation.core.util.BlobList; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.DocumentModelList; 039import org.nuxeo.ecm.core.api.DocumentRef; 040import org.nuxeo.ecm.core.api.DocumentRefList; 041 042/** 043 * @since 5.7.2 Operation Type Implementation for a chain 044 */ 045public class ChainTypeImpl implements OperationType { 046 047 protected final OperationChain chain; 048 049 /** 050 * Chain/Operation Parameters. 051 */ 052 protected Map<String, Object> chainParameters; 053 054 /** 055 * The service that registered the operation. 056 */ 057 protected AutomationService service; 058 059 /** 060 * The operation ID - used for lookups. 061 */ 062 protected String id; 063 064 /** 065 * The operation ID Aliases array. 066 * 067 * @since 7.1 068 */ 069 protected final String[] aliases; 070 071 /** 072 * Chain/Operation Parameters. 073 */ 074 protected OperationDocumentation.Param[] params; 075 076 /** 077 * Invocable methods. 078 */ 079 protected InvokableMethod[] methods = new InvokableMethod[] { runMethod() }; 080 081 /** 082 * The contribution fragment name. 083 */ 084 protected String contributingComponent; 085 086 /** 087 * The operations listing. 088 */ 089 protected OperationParameters[] operations; 090 091 /** 092 * The operation chain XMAP contribution 093 */ 094 protected OperationChainContribution contribution; 095 096 /** 097 * An output of operation type 098 */ 099 protected Class<?> outputChain; 100 101 /** 102 * A method of operation type 103 */ 104 protected InvokableMethod method; 105 106 /** 107 * The input type of a chain/operation. If set, the following input types {"document", "documents", "blob", "blobs"} 108 * for all 'run method(s)' will handled. Other values will be adapted as java.lang.Object. If not set, Automation 109 * will set the input type(s) as the 'run methods(s)' parameter types (by introspection). 110 * 111 * @since 7.4 112 */ 113 protected String inputType; 114 115 public ChainTypeImpl(AutomationService service, OperationChain chain) { 116 this.service = service; 117 operations = chain.getOperations().toArray(new OperationParameters[chain.getOperations().size()]); 118 id = chain.getId(); 119 aliases = chain.getAliases(); 120 chainParameters = chain.getChainParameters(); 121 this.chain = chain; 122 } 123 124 public ChainTypeImpl(AutomationService service, OperationChain chain, OperationChainContribution contribution) { 125 this.service = service; 126 operations = chain.getOperations().toArray(new OperationParameters[chain.getOperations().size()]); 127 id = chain.getId(); 128 aliases = chain.getAliases(); 129 chainParameters = chain.getChainParameters(); 130 this.contribution = contribution; 131 this.chain = chain; 132 } 133 134 public OperationChain getChain() { 135 return chain; 136 } 137 138 public Map<String, Object> getChainParameters() { 139 return chainParameters; 140 } 141 142 @Override 143 public Object newInstance(OperationContext ctx, Map<String, Object> args) throws OperationNotFoundException, 144 InvalidChainException { 145 Object input = ctx.getInput(); 146 Class<?> inputType = input == null ? Void.TYPE : input.getClass(); 147 CompiledChainImpl op = CompiledChainImpl.buildChain(service, inputType, operations); 148 op.context = ctx; 149 return op; 150 } 151 152 @Override 153 public AutomationService getService() { 154 return service; 155 } 156 157 @Override 158 public String getId() { 159 return id; 160 } 161 162 @Override 163 public String[] getAliases() { 164 return aliases; 165 } 166 167 @Override 168 public Class<?> getType() { 169 return CompiledChainImpl.class; 170 } 171 172 @Override 173 public String getInputType() { 174 return inputType; 175 } 176 177 @Override 178 public OperationDocumentation getDocumentation() throws OperationException { 179 OperationDocumentation doc = new OperationDocumentation(id); 180 doc.label = id; 181 doc.requires = contribution.getRequires(); 182 doc.category = contribution.getCategory(); 183 doc.setAliases(contribution.getAliases()); 184 OperationChainContribution.Operation[] operations = contribution.getOps(); 185 doc.operations = operations; 186 doc.since = contribution.getSince(); 187 if (doc.requires.length() == 0) { 188 doc.requires = null; 189 } 190 if (doc.label.length() == 0) { 191 doc.label = doc.id; 192 } 193 doc.description = contribution.getDescription(); 194 doc.params = contribution.getParams(); 195 // load signature 196 if (operations.length != 0) { 197 // Fill signature with first inputs of the first operation and 198 // related outputs of last operation 199 // following the proper automation path 200 ArrayList<String> result = getSignature(operations); 201 doc.signature = result.toArray(new String[result.size()]); 202 } else { 203 doc.signature = new String[] { "void", "void" }; 204 } 205 return doc; 206 } 207 208 /** 209 * @since 5.7.2 210 * @param operations operations listing that chain contains. 211 * @return the chain signature. 212 */ 213 protected ArrayList<String> getSignature(OperationChainContribution.Operation[] operations) 214 throws OperationException { 215 ArrayList<String> result = new ArrayList<String>(); 216 Collection<String> collectedSigs = new HashSet<String>(); 217 OperationType operationType = service.getOperation(operations[0].getId()); 218 for (InvokableMethod method : operationType.getMethods()) { 219 String inputChain = getParamDocumentationType(method.getInputType(), method.isIterable()); 220 outputChain = method.getInputType(); 221 String outputChain = getParamDocumentationType(getChainOutput(operations)); 222 String sigKey = inputChain + ":" + outputChain; 223 if (!collectedSigs.contains(sigKey)) { 224 result.add(inputChain); 225 result.add(outputChain); 226 collectedSigs.add(sigKey); 227 } 228 } 229 return result; 230 } 231 232 /** 233 * @since 5.7.2 234 */ 235 protected Class<?> getChainOutput(OperationChainContribution.Operation[] operations) throws OperationException { 236 for (OperationChainContribution.Operation operation : operations) { 237 OperationType operationType = service.getOperation(operation.getId()); 238 if (operationType instanceof OperationTypeImpl) { 239 outputChain = getOperationOutput(outputChain, operationType); 240 } else { 241 outputChain = getChainOutput(operationType.getDocumentation().getOperations()); 242 } 243 } 244 return outputChain; 245 } 246 247 /** 248 * @since 5.7.2 249 */ 250 public Class<?> getOperationOutput(Class<?> input, OperationType operationType) { 251 InvokableMethod[] methods = operationType.getMethodsMatchingInput(input); 252 if (methods == null || methods.length == 0) { 253 return input; 254 } 255 // Choose the top priority method 256 InvokableMethod topMethod = getTopMethod(methods); 257 Class<?> nextInput = topMethod.getOutputType(); 258 // If output is void, skip this method 259 if (nextInput == Void.TYPE) { 260 return input; 261 } 262 return nextInput; 263 } 264 265 /** 266 * @since 5.7.2 Define the top priority method to take into account for chain operations signature. 267 */ 268 protected InvokableMethod getTopMethod(InvokableMethod[] methods) { 269 InvokableMethod topMethod = methods[0]; 270 for (InvokableMethod method : methods) { 271 if (method.getPriority() > topMethod.getPriority()) { 272 topMethod = method; 273 } 274 } 275 return topMethod; 276 } 277 278 @Override 279 public String getContributingComponent() { 280 return contributingComponent; 281 } 282 283 @Override 284 public InvokableMethod[] getMethodsMatchingInput(Class<?> in) { 285 return methods; 286 } 287 288 protected InvokableMethod runMethod() { 289 try { 290 return new InvokableMethod(this, CompiledChainImpl.class.getMethod("run")); 291 } catch (NoSuchMethodException | SecurityException e) { 292 throw new UnsupportedOperationException("Cannot use reflection for run method", e); 293 } 294 } 295 296 /** 297 * @since 5.7.2 298 */ 299 protected String getParamDocumentationType(Class<?> type) { 300 return getParamDocumentationType(type, false); 301 } 302 303 /** 304 * @since 5.7.2 305 */ 306 protected String getParamDocumentationType(Class<?> type, boolean isIterable) { 307 String t; 308 if (DocumentModel.class.isAssignableFrom(type) || DocumentRef.class.isAssignableFrom(type)) { 309 t = isIterable ? Constants.T_DOCUMENTS : Constants.T_DOCUMENT; 310 } else if (DocumentModelList.class.isAssignableFrom(type) || DocumentRefList.class.isAssignableFrom(type)) { 311 t = Constants.T_DOCUMENTS; 312 } else if (BlobList.class.isAssignableFrom(type)) { 313 t = Constants.T_BLOBS; 314 } else if (Blob.class.isAssignableFrom(type)) { 315 t = isIterable ? Constants.T_BLOBS : Constants.T_BLOB; 316 } else if (URL.class.isAssignableFrom(type)) { 317 t = Constants.T_RESOURCE; 318 } else if (Calendar.class.isAssignableFrom(type)) { 319 t = Constants.T_DATE; 320 } else { 321 t = type.getSimpleName().toLowerCase(); 322 } 323 return t; 324 } 325 326 @Override 327 public String toString() { 328 return "ChainTypeImpl [id=" + id + "]"; 329 } 330 331 public OperationChainContribution getContribution() { 332 return contribution; 333 } 334 335 /** 336 * @since 5.7.2 337 */ 338 @Override 339 public List<InvokableMethod> getMethods() { 340 return Arrays.asList(methods); 341 } 342}