001/**
002 * Copyright 2005-2017 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 */
016package org.kuali.rice.kew.rule;
017
018import org.apache.log4j.Logger;
019import org.kuali.rice.core.api.exception.RiceIllegalStateException;
020import org.kuali.rice.kew.api.KewApiServiceLocator;
021import org.kuali.rice.kew.api.WorkflowRuntimeException;
022import org.kuali.rice.kew.api.exception.WorkflowException;
023import org.kuali.rice.kew.engine.RouteContext;
024
025import javax.script.ScriptEngine;
026import javax.script.ScriptEngineManager;
027import 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
037public 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}