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 }