/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * 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.jumpmind.symmetric.route; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.jumpmind.extension.IBuiltInExtensionPoint; import org.jumpmind.symmetric.ISymmetricEngine; import org.jumpmind.symmetric.model.DataMetaData; import org.jumpmind.symmetric.model.Node; import org.jumpmind.symmetric.model.TriggerRouter; import bsh.EvalError; import bsh.Interpreter; import bsh.TargetError; /** * This data router is invoked when the router_type is 'bsh'. The * router_expression is always a bean shell expression. See <a * href='http://www.beanshell.org'>the bean shell site</a> for information about * the capabilities of the bean shell scripting language. * <P/> * Bound to the interpreter are the names of both the current and old column * values. They can be used in the expression. They should always be referenced * using upper case. Also bound to the interpreter is a {@link Collection} of * targetNodes. The script is expected to add the the list of target nodes a * list of the node_ids that should be routed to. */ public class BshDataRouter extends AbstractDataRouter implements IBuiltInExtensionPoint { protected ISymmetricEngine engine; final String INTERPRETER_KEY = String.format("%d.BshInterpreter", hashCode()); public BshDataRouter(ISymmetricEngine engine) { this.engine = engine; } public Set<String> routeToNodes(SimpleRouterContext context, DataMetaData dataMetaData, Set<Node> nodes, boolean initialLoad, boolean initialLoadSelectUsed, TriggerRouter triggerRouter) { try { long ts = System.currentTimeMillis(); Interpreter interpreter = getInterpreter(context); context.incrementStat(System.currentTimeMillis() - ts, "bsh.init.ms"); HashSet<String> targetNodes = new HashSet<String>(); ts = System.currentTimeMillis(); bind(interpreter, dataMetaData, nodes, targetNodes, initialLoad); context.incrementStat(System.currentTimeMillis() - ts, "bsh.bind.ms"); ts = System.currentTimeMillis(); Object returnValue = interpreter.eval(dataMetaData.getRouter().getRouterExpression()); context.incrementStat(System.currentTimeMillis() - ts, "bsh.eval.ms"); return eval(returnValue, nodes, targetNodes); } catch (EvalError e) { if (e instanceof TargetError) { Throwable t = ((TargetError)e).getTarget(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new RuntimeException("Routing script failed at line " + ((TargetError)e).getErrorLineNumber(), t); } } else { throw new RuntimeException(e); } } } protected Interpreter getInterpreter(SimpleRouterContext context) { Interpreter interpreter = (Interpreter) context.getContextCache().get(INTERPRETER_KEY); if (interpreter == null) { interpreter = new Interpreter(); context.getContextCache().put(INTERPRETER_KEY, interpreter); } return interpreter; } protected Set<String> eval(Object value, Set<Node> nodes, Set<String> targetNodes) { targetNodes.remove(null); if (targetNodes.size() > 0) { return targetNodes; } else if (value instanceof Set<?>) { Set<?> values = (Set<?>) value; Set<String> nodeIds = new HashSet<String>(values.size()); for (Object v : values) { if (v != null) { nodeIds.add(v.toString()); } } return nodeIds; } else if (value instanceof Boolean && value.equals(Boolean.TRUE)) { return toNodeIds(nodes, null); } else if (value instanceof String) { Set<String> node = new HashSet<String>(1); node.add(value.toString()); return node; } else { return Collections.emptySet(); } } protected void bind(Interpreter interpreter, DataMetaData dataMetaData, Set<Node> nodes, Set<String> targetNodes, boolean initialLoad) throws EvalError { interpreter.set("log", log); interpreter.set("initialLoad", initialLoad); interpreter.set("dataMetaData", dataMetaData); interpreter.set("nodes", nodes); interpreter.set("nodeIds", toNodeIds(nodes, null)); interpreter.set("identityNodeId", engine.getNodeService().findIdentityNodeId()); interpreter.set("targetNodes", targetNodes); interpreter.set("engine", engine); Map<String, Object> params = getDataObjectMap(dataMetaData, engine.getSymmetricDialect(), true); if (params != null) { for (String param : params.keySet()) { interpreter.set(param, params.get(param)); } } } }