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 }