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