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