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.lang.reflect.Method; 023import java.net.URL; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Calendar; 027import java.util.Collection; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031 032import org.nuxeo.ecm.automation.AutomationService; 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.OperationType; 038import org.nuxeo.ecm.automation.core.Constants; 039import org.nuxeo.ecm.automation.core.OperationChainContribution; 040import org.nuxeo.ecm.automation.core.util.BlobList; 041import org.nuxeo.ecm.core.api.Blob; 042import org.nuxeo.ecm.core.api.DocumentModel; 043import org.nuxeo.ecm.core.api.DocumentModelList; 044import org.nuxeo.ecm.core.api.DocumentRef; 045import org.nuxeo.ecm.core.api.DocumentRefList; 046import org.nuxeo.runtime.api.Framework; 047 048/** 049 * @since 5.7.2 Operation Type Implementation for a chain 050 */ 051public class ChainTypeImpl implements OperationType { 052 053 protected final OperationChain chain; 054 055 /** 056 * The service that registered the operation. 057 */ 058 protected AutomationService service; 059 060 /** 061 * Invocable methods. 062 */ 063 protected InvokableMethod[] methods = new InvokableMethod[] { new InvokableMethod(this, runMethod) }; 064 065 /** 066 * The contribution fragment name. 067 */ 068 protected String contributingComponent; 069 070 /** 071 * The operation chain XMAP contribution 072 */ 073 protected OperationChainContribution contribution; 074 075 /** 076 * @deprecated since 11.1, see other constructor 077 */ 078 @Deprecated 079 public ChainTypeImpl(AutomationService service, OperationChain chain, OperationChainContribution contribution) { 080 this(service, chain, contribution, null); 081 } 082 083 /** 084 * @since 11.1 085 */ 086 public ChainTypeImpl(AutomationService service, OperationChain chain, OperationChainContribution contribution, 087 String contributingComponent) { 088 this.service = service; 089 this.contribution = contribution; 090 this.chain = chain; 091 this.contributingComponent = contributingComponent; 092 } 093 094 public OperationChain getChain() { 095 return chain; 096 } 097 098 public Map<String, Object> getChainParameters() { 099 return chain.getChainParameters(); 100 } 101 102 @Override 103 public Object newInstance(OperationContext ctx, Map<String, Object> args) throws OperationException { 104 Object input = ctx.getInput(); 105 Class<?> inputType = input == null ? Void.TYPE : input.getClass(); 106 return service.compileChain(inputType, chain); 107 } 108 109 @Override 110 public AutomationService getService() { 111 return service; 112 } 113 114 @Override 115 public String getId() { 116 return chain.getId(); 117 } 118 119 @Override 120 public String[] getAliases() { 121 return chain.getAliases(); 122 } 123 124 @Override 125 public Class<?> getType() { 126 return OperationChainCompiler.CompiledChainImpl.class; 127 } 128 129 @Override 130 public OperationDocumentation getDocumentation() throws OperationException { 131 OperationDocumentation doc = new OperationDocumentation(chain.getId()); 132 doc.label = chain.getId(); 133 doc.requires = contribution.getRequires(); 134 doc.category = contribution.getCategory(); 135 doc.setAliases(contribution.getAliases()); 136 OperationChainContribution.Operation[] operations = contribution.getOps(); 137 doc.operations = operations; 138 doc.since = contribution.getSince(); 139 if (doc.requires.length() == 0) { 140 doc.requires = null; 141 } 142 if (doc.label.length() == 0) { 143 doc.label = doc.id; 144 } 145 doc.description = contribution.getDescription(); 146 doc.params = contribution.getParams(); 147 // load signature 148 if (operations.length != 0) { 149 // Fill signature with first inputs of the first operation and 150 // related outputs of last operation 151 // following the proper automation path 152 ArrayList<String> result = getSignature(operations); 153 doc.signature = result.toArray(new String[result.size()]); 154 } else { 155 doc.signature = new String[] { "void", "void" }; 156 } 157 return doc; 158 } 159 160 /** 161 * @since 5.7.2 162 * @param operations operations listing that chain contains. 163 * @return the chain signature. 164 */ 165 protected ArrayList<String> getSignature(OperationChainContribution.Operation[] operations) 166 throws OperationException { 167 ArrayList<String> result = new ArrayList<>(); 168 Collection<String> collectedSigs = new HashSet<>(); 169 OperationType operationType = service.getOperation(operations[0].getId()); 170 for (InvokableMethod method : operationType.getMethods()) { 171 String chainInput = getParamDocumentationType(method.getInputType(), method.isIterable()); 172 String chainOutput = getParamDocumentationType(getChainOutput(method.getInputType(), operations)); 173 String sigKey = chainInput + ":" + method.getInputType(); 174 if (!collectedSigs.contains(sigKey)) { 175 result.add(chainInput); 176 result.add(chainOutput); 177 collectedSigs.add(sigKey); 178 } 179 } 180 return result; 181 } 182 183 /** 184 * @since 5.7.2 185 */ 186 protected Class<?> getChainOutput(Class<?> chainInput, OperationChainContribution.Operation[] operations) 187 throws OperationException { 188 for (OperationChainContribution.Operation operation : operations) { 189 OperationType operationType = service.getOperation(operation.getId()); 190 if (operationType instanceof ChainTypeImpl) { 191 chainInput = getChainOutput(chainInput, operationType.getDocumentation().getOperations()); 192 } else { 193 chainInput = getOperationOutput(chainInput, operationType); 194 } 195 } 196 return chainInput; 197 } 198 199 /** 200 * @since 5.7.2 201 */ 202 public Class<?> getOperationOutput(Class<?> input, OperationType operationType) { 203 InvokableMethod[] methodsMatchingInput = operationType.getMethodsMatchingInput(input); 204 if (methodsMatchingInput.length == 0) { 205 return input; 206 } 207 // Choose the top priority method 208 InvokableMethod topMethod = getTopMethod(methodsMatchingInput); 209 Class<?> nextInput = topMethod.getOutputType(); 210 // If output is void, skip this method 211 if (nextInput == Void.TYPE) { 212 return input; 213 } 214 return nextInput; 215 } 216 217 /** 218 * @since 5.7.2 Define the top priority method to take into account for chain operations signature. 219 */ 220 protected InvokableMethod getTopMethod(InvokableMethod[] methods) { 221 InvokableMethod topMethod = methods[0]; 222 for (InvokableMethod method : methods) { 223 if (method.getPriority() > topMethod.getPriority()) { 224 topMethod = method; 225 } 226 } 227 return topMethod; 228 } 229 230 @Override 231 public String getContributingComponent() { 232 return contributingComponent; 233 } 234 235 @Override 236 public InvokableMethod[] getMethodsMatchingInput(Class<?> in) { 237 return methods; 238 } 239 240 protected static final Method runMethod = loadRunMethod(); 241 242 protected static Method loadRunMethod() { 243 try { 244 return OperationChainCompiler.CompiledChainImpl.class.getMethod("invoke", OperationContext.class); 245 } catch (NoSuchMethodException | SecurityException e) { 246 throw new UnsupportedOperationException("Cannot use reflection for run method", e); 247 } 248 } 249 250 /** 251 * @since 5.7.2 252 */ 253 protected String getParamDocumentationType(Class<?> type) { 254 return getParamDocumentationType(type, false); 255 } 256 257 /** 258 * @since 5.7.2 259 */ 260 protected String getParamDocumentationType(Class<?> type, boolean isIterable) { 261 String t; 262 if (DocumentModel.class.isAssignableFrom(type) || DocumentRef.class.isAssignableFrom(type)) { 263 t = isIterable ? Constants.T_DOCUMENTS : Constants.T_DOCUMENT; 264 } else if (DocumentModelList.class.isAssignableFrom(type) || DocumentRefList.class.isAssignableFrom(type)) { 265 t = Constants.T_DOCUMENTS; 266 } else if (BlobList.class.isAssignableFrom(type)) { 267 t = Constants.T_BLOBS; 268 } else if (Blob.class.isAssignableFrom(type)) { 269 t = isIterable ? Constants.T_BLOBS : Constants.T_BLOB; 270 } else if (URL.class.isAssignableFrom(type)) { 271 t = Constants.T_RESOURCE; 272 } else if (Calendar.class.isAssignableFrom(type)) { 273 t = Constants.T_DATE; 274 } else { 275 t = type.getSimpleName().toLowerCase(); 276 } 277 return t; 278 } 279 280 @Override 281 public String toString() { 282 return "ChainTypeImpl [id=" + chain.getId() + "]"; 283 } 284 285 public OperationChainContribution getContribution() { 286 return contribution; 287 } 288 289 /** 290 * @since 5.7.2 291 */ 292 @Override 293 public List<InvokableMethod> getMethods() { 294 return Arrays.asList(methods); 295 } 296 297 @Override 298 public int hashCode() { 299 final int prime = 31; 300 int result = 1; 301 result = prime * result + ((chain == null) ? 0 : chain.hashCode()); 302 return result; 303 } 304 305 @Override 306 public boolean equals(Object obj) { 307 if (this == obj) { 308 return true; 309 } 310 if (obj == null) { 311 return false; 312 } 313 if (!(obj instanceof ChainTypeImpl)) { 314 return false; 315 } 316 ChainTypeImpl other = (ChainTypeImpl) obj; 317 if (chain == null) { 318 if (other.chain != null) { 319 return false; 320 } 321 } else if (!chain.equals(other.chain)) { 322 return false; 323 } 324 return true; 325 } 326 327 public static ChainTypeImpl typeof(OperationChain chain, boolean replace) { 328 return new ChainTypeImpl(Framework.getService(AutomationService.class), chain, 329 OperationChainContribution.contribOf(chain, replace), null); 330 } 331 332}