001/* 002 * (C) Copyright 2016 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 */ 017package org.nuxeo.ecm.automation.core.impl; 018 019import java.util.concurrent.ExecutionException; 020 021import org.nuxeo.ecm.automation.AutomationService; 022import org.nuxeo.ecm.automation.CompiledChain; 023import org.nuxeo.ecm.automation.ExitException; 024import org.nuxeo.ecm.automation.InvalidChainException; 025import org.nuxeo.ecm.automation.OperationContext; 026import org.nuxeo.ecm.automation.OperationException; 027import org.nuxeo.ecm.automation.OperationNotFoundException; 028import org.nuxeo.ecm.automation.OperationParameters; 029import org.nuxeo.ecm.automation.OperationType; 030import org.nuxeo.ecm.automation.core.scripting.Expression; 031 032import com.google.common.cache.CacheBuilder; 033import com.google.common.cache.CacheLoader; 034import com.google.common.cache.LoadingCache; 035 036public class OperationChainCompiler { 037 038 protected final AutomationService service; 039 040 protected final LoadingCache<Connector, OperationMethod> cache; 041 042 protected static final int MAX_CACHE_SIZE = 1000; 043 044 protected OperationChainCompiler(AutomationService service) { 045 this.service = service; 046 cache = CacheBuilder.newBuilder() // 047 .maximumSize(MAX_CACHE_SIZE) 048 .build(new CacheLoader<Connector, OperationMethod>() { 049 @Override 050 public OperationMethod load(Connector connector) throws OperationException { 051 return connector.connect(); 052 } 053 }); 054 } 055 056 public CompiledChain compile(ChainTypeImpl typeof, Class<?> typein) throws OperationException { 057 Connector connector = new Connector(typeof, typein); 058 OperationMethod operationMethod; 059 try { 060 operationMethod = cache.get(connector); 061 } catch (ExecutionException e) { 062 Throwable cause = e.getCause(); 063 if (cause instanceof OperationException) { 064 throw (OperationException) cause; 065 } else { 066 throw new OperationException(cause); 067 } 068 } 069 return new CompiledChainImpl(typeof, typein, operationMethod); 070 } 071 072 protected class Connector { 073 074 protected final ChainTypeImpl typeof; 075 076 protected final Class<?> typein; 077 078 protected Connector(ChainTypeImpl typeof, Class<?> typein) { 079 this.typeof = typeof; 080 this.typein = typein; 081 } 082 083 @Override 084 public int hashCode() { 085 int prime = 31; 086 int result = 1; 087 result = prime * result + typeof.hashCode(); 088 result = prime * result + typein.hashCode(); 089 return result; 090 } 091 092 @Override 093 public boolean equals(Object obj) { 094 if (this == obj) { 095 return true; 096 } 097 if (obj == null) { 098 return false; 099 } 100 if (!(obj instanceof Connector)) { 101 return false; 102 } 103 Connector other = (Connector) obj; 104 return typeof.equals(other.typeof) && typein.equals(other.typein); 105 } 106 107 protected OperationMethod connect() throws OperationException { 108 OperationMethod head = null; 109 OperationMethod prev = null; 110 for (OperationParameters params : typeof.chain.getOperations()) { 111 OperationMethod next = new OperationMethod(params, prev); 112 if (prev != null) { 113 prev.next = next; 114 } 115 if (next.prev == null) { 116 head = next; 117 } 118 prev = next; 119 } 120 if (head != null) { 121 head.solve(typein); 122 } 123 return head; 124 } 125 } 126 127 protected class OperationMethod { 128 129 protected final OperationType typeof; 130 131 protected final OperationParameters params; 132 133 protected InvokableMethod method; 134 135 protected OperationMethod prev; 136 137 protected OperationMethod next; 138 139 protected OperationMethod(OperationParameters params, OperationMethod prev) throws OperationNotFoundException { 140 typeof = service.getOperation(params.id()); 141 this.params = params; 142 this.prev = prev; 143 } 144 145 protected Object invoke(OperationContext context) throws OperationException { 146 context.getCallback().onOperationEnter(context, typeof, method, params.map()); 147 Object output = method.invoke(context, params.map()); 148 if (output instanceof Expression) { 149 output = ((Expression) output).eval(context); 150 } 151 context.getCallback().onOperationExit(output); 152 context.setInput(output); 153 if (next != null) { 154 return next.invoke(context); 155 } 156 return output; 157 } 158 159 /** 160 * Compute the best matching path to perform the chain of operations. The path is computed using a backtracking 161 * algorithm. 162 */ 163 void solve(Class<?> in) throws InvalidChainException { 164 InvokableMethod[] methods = typeof.getMethodsMatchingInput(in); 165 if (methods.length == 0) { 166 throw new InvalidChainException( 167 "Cannot find any valid path in operation chain - no method found for operation '" 168 + typeof.getId() + "' and for first input type '" + in.getName() + "'"); 169 } 170 if (next == null) { 171 method = methods[0]; 172 return; 173 } 174 for (InvokableMethod m : methods) { 175 Class<?> nextIn = m.getOutputType(); 176 if (nextIn == Void.TYPE || nextIn.equals(Object.class)) { 177 nextIn = in; // preserve last input 178 } 179 try { 180 next.solve(nextIn); 181 method = m; 182 return; 183 } catch (InvalidChainException cause) { 184 // continue solving 185 } 186 } 187 throw new InvalidChainException( 188 "Cannot find any valid path in operation chain - no method found for operation '" + typeof.getId() 189 + "' and for first input type '" + in.getName() + "'"); 190 } 191 } 192 193 protected class CompiledChainImpl implements CompiledChain { 194 195 protected final ChainTypeImpl typeof; 196 197 protected final Class<?> typein; 198 199 protected final OperationMethod head; 200 201 protected CompiledChainImpl(ChainTypeImpl typeof, Class<?> typein, OperationMethod head) { 202 this.typeof = typeof; 203 this.typein = typein; 204 this.head = head; 205 } 206 207 @Override 208 public Object invoke(OperationContext ctx) throws OperationException { 209 return ctx.callWithChainParameters(() -> { 210 ctx.getCallback().onChainEnter(typeof); 211 try { 212 return head.invoke(ctx); 213 } catch (ExitException e) { 214 if (e.isRollback()) { 215 ctx.setRollback(); 216 } 217 return ctx.getInput(); 218 } catch (OperationException op) { 219 throw ctx.getCallback().onError(op); 220 } finally { 221 ctx.getCallback().onChainExit(); 222 } 223 }, typeof.getChainParameters()); 224 } 225 226 @Override 227 public String toString() { 228 return "CompiledChainImpl [op=" + typeof + "," + "input=" + typein + "]"; 229 } 230 231 } 232 233}