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 java.text.ParseException;
019
020import org.apache.commons.lang.StringUtils;
021import org.apache.log4j.Logger;
022import org.kuali.rice.core.api.exception.RiceIllegalStateException;
023import org.kuali.rice.kew.api.KewApiServiceLocator;
024import org.kuali.rice.kew.engine.RouteContext;
025
026/**
027 * Implements the KRA meta-rule processing and state machine engine 
028 * 
029 * @author Kuali Rice Team (rice.collab@kuali.org)
030 */
031public class KRAMetaRuleEngine {
032    private static final Logger LOG = Logger.getLogger(KRAMetaRuleEngine.class);
033
034    /**
035     * KRA meta-rule processing flag
036     */
037    private static enum KRA_RULE_FLAG {
038        NEXT, TRUE, FALSE
039    }
040
041    private final String expression;
042    private final String[] statements;
043    private int curStatement = 0;
044    /**
045     * Whether processing has indicated that we should stop
046     */
047    private boolean stop = false;
048    
049    public KRAMetaRuleEngine(String expression) throws ParseException {
050        this.expression = expression;
051        statements = expression.split("\\s*[;\r\n]\\s*");
052        
053        if (statements.length == 0) {
054            throw new ParseException("No statements parsed in expression: " + expression, 0);
055        }
056    }
057
058    /**
059     * @return the expression the engine was initialized with
060     */
061    public String getExpression() {
062        return expression;
063    }
064
065    /**
066     * @return the parsed statements
067     */
068    public String[] getStatements() {
069        return statements;
070    }
071
072    /**
073     * @return the next statement the engine will process
074     */
075    public int getCurStatement() {
076        return curStatement;
077    }
078
079    /**
080     * @param statementNo the statement index at which to resume processing 
081     */
082    public void setCurStatement(int statementNo) {
083        this.curStatement = statementNo;
084    }
085
086    /**
087     * @return whether we are done processing
088     */
089    public boolean isDone() {
090        return curStatement >= statements.length || stop;
091    }
092
093    /**
094     * Processes a single statement and returns the result
095     * @param context the current RouteContext
096     * @return the expression result that resulted from the evaluation of a single statement
097     * @throws ParseException if the statement could not be parsed
098     */
099    public RuleExpressionResult processSingleStatement(RouteContext context) throws ParseException {
100        if (isDone()) {
101            return null;
102        }
103
104        int stmtNum = curStatement + 1;
105        String statement = statements[curStatement];
106        LOG.debug("Processing statement: " + statement);
107        String[] words = statement.split("\\s*:\\s*");
108        if (words.length < 2) {
109            throw new ParseException("Invalid statement (#" + stmtNum + "): " + statement, 0);
110        }
111        String ruleName = words[0];
112        if (StringUtils.isEmpty(ruleName)) {
113            throw new ParseException("Invalid rule in statement (#" + stmtNum + "): " + statement, 0);
114        }
115        String flag = words[1];
116        LOG.debug(flag.toUpperCase());
117        KRA_RULE_FLAG flagCode = KRA_RULE_FLAG.valueOf(flag.toUpperCase());
118        if (flagCode == null) {
119            throw new ParseException("Invalid flag in statement (#" + stmtNum + "): " + statement, 0);
120        }
121        org.kuali.rice.kew.api.rule.Rule nestedRule = KewApiServiceLocator.getRuleService().getRuleByName(ruleName);
122        if (nestedRule == null) {
123            throw new ParseException("Rule '" + ruleName + "' in statement (#" + stmtNum + ") not found: " + statement, 0);
124        }
125
126        Rule rule = new RuleImpl(nestedRule);
127        RuleExpressionResult result;
128        switch (flagCode) {
129            case NEXT:
130                result = rule.evaluate(rule, context);
131                break;
132            case TRUE:
133                result = rule.evaluate(rule, context);
134                if (!result.isSuccess()) {
135                    stop = true;
136                }
137                break;
138            case FALSE:
139                result = rule.evaluate(rule, context);
140                if (result.isSuccess()) {
141                    stop = true;
142                    // we need to just invert the ultimate expression result success, because in this case
143                    // we wanted the expression to fail but it didn't
144                    result = new RuleExpressionResult(rule, false, result.getResponsibilities());
145                }
146                break;
147            default:
148                throw new RiceIllegalStateException("Unhandled statement flag: " + flagCode);
149        }
150
151        curStatement++;
152        LOG.debug("Result of statement '" + statement + "': " + result);
153        return result;
154    }
155}