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