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 }