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