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