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