001 /** 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php 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 package org.kuali.rice.kew.rule; 017 018 import org.apache.log4j.Logger; 019 import org.kuali.rice.core.api.exception.RiceIllegalStateException; 020 import org.kuali.rice.kew.api.KewApiServiceLocator; 021 import org.kuali.rice.kew.api.WorkflowRuntimeException; 022 import org.kuali.rice.kew.api.exception.WorkflowException; 023 import org.kuali.rice.kew.engine.RouteContext; 024 025 import javax.script.ScriptEngine; 026 import javax.script.ScriptEngineManager; 027 import javax.script.ScriptException; 028 029 /** 030 * A rule expression implementation that uses Bean Scripting Framework. 031 * The language is given by the type qualifier, e.g.: 032 * <expression type="BSF:groovy">... 033 * 034 * @author Kuali Rice Team (rice.collab@kuali.org) 035 */ 036 //TODO: this should really be renamed since it is no longer using apache BSF 037 public class BSFRuleExpression implements RuleExpression { 038 private static final Logger LOG = Logger.getLogger(BSFRuleExpression.class); 039 040 public RuleExpressionResult evaluate(Rule rule, RouteContext context) { 041 org.kuali.rice.kew.api.rule.RuleContract ruleDefinition = rule.getDefinition(); 042 String name = "" + ruleDefinition.getName(); 043 String type = ruleDefinition.getRuleExpressionDef().getType(); 044 String lang = parseLang(type, "groovy"); 045 String expression = ruleDefinition.getRuleExpressionDef().getExpression(); 046 RuleExpressionResult result; 047 ScriptEngineManager factory = new ScriptEngineManager(); 048 ScriptEngine engine = factory.getEngineByName(lang); 049 try { 050 declareBeans(engine, rule, context); 051 result = (RuleExpressionResult) engine.eval(expression); 052 } catch (ScriptException e) { 053 String details = ( e.getLineNumber() >= 0 ? " line: " + e.getLineNumber() + " column: " + e.getColumnNumber() : "" ); 054 LOG.debug("Error evaluating rule '" + name + "' " + type + " expression" + details + ": '" + expression + "'" + details, e); 055 throw new RiceIllegalStateException("Error evaluating rule '" + name + "' " + type + " expression" + details, e); 056 } 057 if (result == null) { 058 return new RuleExpressionResult(rule, false); 059 } else { 060 return result; 061 } 062 } 063 064 /** 065 * Parses the language component from the type string 066 * @param type the type string 067 * @param deflt the default language if none is present in the type string 068 * @return the language component or null 069 */ 070 protected String parseLang(String type, String deflt) { 071 int colon = type.indexOf(':'); 072 if (colon > -1) { 073 return type.substring(colon + 1); 074 } else { 075 return deflt; 076 } 077 } 078 079 /** 080 * Populates the BSFManager with beans that are accessible to BSF scripts. May be overridden by 081 * subclasses. The standard implementation exposes the rule and routeContext 082 * @param manager the BSFManager 083 * @param rule the current Rule object 084 * @param context the current RouteContext 085 */ 086 protected void declareBeans(ScriptEngine engine, Rule rule, RouteContext context) throws ScriptException { 087 engine.put("rule", rule); 088 engine.put("routeContext", context); 089 engine.put("workflow", new WorkflowRuleAPI(context)); 090 } 091 092 /** 093 * A helper bean that is declared for use by BSF scripts. 094 * This functionality should really be part of a single internal API that can be exposed 095 * to various pieces of code that are plugged into KEW. For comparison EDocLite also 096 * has its own such API that it exposes. 097 */ 098 protected static final class WorkflowRuleAPI { 099 private final RouteContext context; 100 WorkflowRuleAPI(RouteContext context) { 101 this.context = context; 102 } 103 /** 104 * Evaluates a named rule 105 * @param name the rule name 106 * @return the RuleExpressionResult 107 * @throws WorkflowException 108 */ 109 public RuleExpressionResult invokeRule(String name) throws WorkflowException { 110 org.kuali.rice.kew.api.rule.Rule rbv = KewApiServiceLocator.getRuleService().getRuleByName(name); 111 if (rbv == null) throw new WorkflowRuntimeException("Could not find rule named \"" + name + "\""); 112 Rule r = new RuleImpl(rbv); 113 return r.evaluate(r, context); 114 } 115 } 116 }