001    /**
002     * Copyright 2005-2012 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 java.text.ParseException;
019    
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.log4j.Logger;
022    import org.kuali.rice.core.api.exception.RiceIllegalStateException;
023    import org.kuali.rice.kew.api.KewApiServiceLocator;
024    import 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     */
031    public 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    }