/* * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.nuxeo.ecm.automation.core.impl; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.nuxeo.ecm.automation.AutomationService; import org.nuxeo.ecm.automation.CompiledChain; import org.nuxeo.ecm.automation.ExitException; import org.nuxeo.ecm.automation.InvalidChainException; import org.nuxeo.ecm.automation.OperationContext; import org.nuxeo.ecm.automation.OperationException; import org.nuxeo.ecm.automation.OperationNotFoundException; import org.nuxeo.ecm.automation.OperationParameters; import org.nuxeo.ecm.automation.OperationType; import org.nuxeo.ecm.automation.core.scripting.Expression; public class OperationChainCompiler { protected final AutomationService service; protected final Map<Connector, OperationMethod> cache = new ConcurrentHashMap<>(); protected OperationChainCompiler(AutomationService service) { this.service = service; } public CompiledChain compile(ChainTypeImpl typeof, Class<?> typein) throws OperationException { Connector connector = new Connector(typeof, typein); if (!cache.containsKey(connector)) { cache.put(connector, connector.connect()); } return new CompiledChainImpl(typeof, typein, cache.get(connector)); } protected class Connector { protected final ChainTypeImpl typeof; protected final Class<?> typein; protected final int hashcode; protected Connector(ChainTypeImpl typeof, Class<?> typein) { this.typeof = typeof; this.typein = typein; hashcode = hashcode(typeof, typein); } protected int hashcode(OperationType typeof, Class<?> typein) { int prime = 31; int result = 1; result = prime * result + typeof.hashCode(); result = prime * result + typein.hashCode(); return result; } @Override public int hashCode() { return hashcode; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Connector)) { return false; } Connector other = (Connector) obj; return hashcode == other.hashcode; } protected OperationMethod connect() throws OperationException { OperationMethod head = null; OperationMethod prev = null; for (OperationParameters params : typeof.chain.getOperations()) { OperationMethod next = new OperationMethod(params, prev); if (prev != null) { prev.next = next; } if (next.prev == null) { head = next; } prev = next; } head.solve(typein); return head; } } protected class OperationMethod { protected final OperationType typeof; protected final OperationParameters params; protected InvokableMethod method; protected OperationMethod prev; protected OperationMethod next; protected OperationMethod(OperationParameters params, OperationMethod prev) throws OperationNotFoundException { typeof = service.getOperation(params.id()); this.params = params; this.prev = prev; } protected Object invoke(OperationContext context) throws OperationException { context.getCallback().onOperationEnter(context, typeof, method, params.map()); Object output = method.invoke(context, params.map()); if (output instanceof Expression) { output = ((Expression) output).eval(context); } context.getCallback().onOperationExit(output); context.setInput(output); if (next != null) { return next.invoke(context); } return output; } /** * Compute the best matching path to perform the chain of operations. The path is computed using a backtracking * algorithm. * * @throws InvalidChainException */ void solve(Class<?> in) throws InvalidChainException { InvokableMethod[] methods = typeof.getMethodsMatchingInput(in); if (methods == null) { throw new InvalidChainException( "Cannot find any valid path in operation chain - no method found for operation '" + typeof.getId() + "' and for first input type '" + in.getName() + "'"); } if (next == null) { method = methods[0]; return; } for (InvokableMethod m : methods) { Class<?> nextIn = m.getOutputType(); if (nextIn == Void.TYPE || nextIn.equals(Object.class)) { nextIn = in; // preserve last input } try { next.solve(nextIn); method = m; return; } catch (InvalidChainException cause) { ; } } throw new InvalidChainException( "Cannot find any valid path in operation chain - no method found for operation '" + typeof.getId() + "' and for first input type '" + in.getName() + "'"); } } protected class CompiledChainImpl implements CompiledChain { protected final ChainTypeImpl typeof; protected final Class<?> typein; protected final OperationMethod head; protected CompiledChainImpl(ChainTypeImpl typeof, Class<?> typein, OperationMethod head) { this.typeof = typeof; this.typein = typein; this.head = head; } @Override public Object invoke(OperationContext ctx) throws OperationException { ctx.push(typeof.getChainParameters()); try { ctx.getCallback().onChainEnter(typeof); try { return head.invoke(ctx); } catch (ExitException e) { if (e.isRollback()) { ctx.setRollback(); } return ctx.getInput(); } finally { ctx.getCallback().onChainExit(); } } finally { ctx.pop(typeof.getChainParameters()); } } @Override public String toString() { return "CompiledChainImpl [op=" + typeof + "," + "input=" + typein + "]"; } } }