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.engine.node;
017
018 import java.util.ArrayList;
019 import java.util.HashSet;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023 import java.util.Set;
024
025 import org.apache.commons.lang.StringUtils;
026 import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
027 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
028 import org.kuali.rice.kew.api.exception.WorkflowException;
029 import org.kuali.rice.kew.api.rule.Rule;
030 import org.kuali.rice.kew.engine.RouteContext;
031 import org.kuali.rice.kew.engine.RouteHelper;
032 import org.kuali.rice.kew.exception.RouteManagerException;
033 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
034 import org.kuali.rice.kew.rule.FlexRM;
035 import org.kuali.rice.kew.rule.KRAMetaRuleEngine;
036 import org.kuali.rice.kew.rule.RuleExpressionResult;
037 import org.kuali.rice.kew.service.KEWServiceLocator;
038 import org.kuali.rice.kew.util.Utilities;
039
040
041 /**
042 * Node that implements a KRAMetaRule, with multiple request/response phases
043 *
044 * @author Kuali Rice Team (rice.collab@kuali.org)
045 */
046 public class KRAMetaRuleNode extends IteratedRequestActivationNode {
047 private static String SUPPRESS_POLICY_ERRORS_KEY = "_suppressPolicyErrorsRequestActivationNode";
048
049 protected List<ActionRequestValue> generateUninitializedRequests(ActionRequestFactory arFactory, RouteContext context, RuleExpressionResult result) throws WorkflowException {
050 FlexRM flexRM = new FlexRM();
051 flexRM.makeActionRequests(arFactory, result.getResponsibilities(), context, Rule.Builder.create(result.getRule().getDefinition()).build(), context.getDocument(), null, null);
052 return new ArrayList<ActionRequestValue>(arFactory.getRequestGraphs());
053 }
054
055 protected List<ActionRequestValue> initializeRequests(List<ActionRequestValue> requests, RouteContext context) {
056 // RequestNode.getNewActionRequests
057 List<ActionRequestValue> newRequests = new ArrayList<ActionRequestValue>();
058 for (Iterator iterator = requests.iterator(); iterator.hasNext();) {
059 ActionRequestValue actionRequest = (ActionRequestValue) iterator.next();
060 actionRequest = KEWServiceLocator.getActionRequestService().initializeActionRequestGraph(actionRequest, context.getDocument(), context.getNodeInstance());
061 saveActionRequest(context, actionRequest);
062 newRequests.add(actionRequest);
063 }
064 return newRequests;
065 }
066
067 @Override
068 protected boolean generateNewRequests(boolean initial, RouteContext context, RouteHelper routeHelper)
069 throws WorkflowException, Exception {
070 RouteNodeInstance nodeInstance = context.getNodeInstance();
071 RouteNode nodeDef = nodeInstance.getRouteNode();
072
073 // get the expression
074 Map<String, String> cfg = Utilities.getKeyValueCollectionAsMap(nodeDef.getConfigParams());
075 String expression = cfg.get("expression");
076 if (StringUtils.isEmpty(expression)) {
077 throw new WorkflowException("No meta-rule expression supplied in node " + nodeDef);
078 }
079 KRAMetaRuleEngine engine = new KRAMetaRuleEngine(expression);
080
081 // get the current statement
082 int curStatement = getCurStatement(nodeInstance);
083
084 engine.setCurStatement(curStatement);
085
086 if (engine.isDone()) {
087 return false;
088 }
089
090 // generate next round of action requests
091 RuleExpressionResult result = engine.processSingleStatement(context);
092 if (!result.isSuccess()) {
093 return false;
094 }
095
096 boolean suppressPolicyErrors = isSupressingPolicyErrors(context);
097 boolean pastFinalApprover = isPastFinalApprover(context.getDocument(), nodeInstance);
098
099 // actionRequests.addAll(makeActionRequests(context, rule, routeHeader, null, null));
100 // copied from parts of FlexRM and RequestsNode
101 ActionRequestFactory arFactory = new ActionRequestFactory(context.getDocument(), context.getNodeInstance());
102 List<ActionRequestValue> requests = generateUninitializedRequests(arFactory, context, result);
103 requests = initializeRequests(requests, context);
104 // RequestNode
105 if ((requests.isEmpty()) && nodeDef.getMandatoryRouteInd().booleanValue() && ! suppressPolicyErrors) {
106 LOG.warn("no requests generated for mandatory route - " + nodeDef.getRouteNodeName());
107 throw new RouteManagerException("No requests generated for mandatory route " + nodeDef.getRouteNodeName() + ":" + nodeDef.getRouteMethodName(), context);
108 }
109 // determine if we have any approve requests for FinalApprover checks
110 boolean hasApproveRequest = false;
111 for (Iterator iter = requests.iterator(); iter.hasNext();) {
112 ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
113 hasApproveRequest = actionRequest.isApproveOrCompleteRequest() || hasApproveRequest;
114 }
115 // if final approver route level and no approve request send to exception routing
116 if (nodeDef.getFinalApprovalInd().booleanValue()) {
117 // we must have an approve request generated if final approver level.
118 if (!hasApproveRequest && ! suppressPolicyErrors) {
119 throw new RuntimeException("No Approve Request generated after final approver");
120 }
121 } else if (pastFinalApprover) {
122 // we can't allow generation of approve requests after final approver. This guys going to exception routing.
123 if (hasApproveRequest && ! suppressPolicyErrors) {
124 throw new RuntimeException("Approve Request generated after final approver");
125 }
126 }
127
128 // increment to next statement
129 nodeInstance.getNodeState("stmt").setValue(String.valueOf(curStatement + 1));
130 return !requests.isEmpty();
131 }
132
133 /**
134 * @param nodeInstance the current node instance under execution
135 * @return the meta-rule statement this node instance should process
136 */
137 protected static int getCurStatement(RouteNodeInstance nodeInstance) {
138 int statement = 0;
139 NodeState nodeState = nodeInstance.getNodeState("stmt");
140 if (nodeState == null) {
141 nodeState = new NodeState();
142 nodeState.setKey("stmt");
143 nodeState.setNodeInstance(nodeInstance);
144 nodeInstance.addNodeState(nodeState);
145 }
146 if (StringUtils.isEmpty(nodeState.getValue())) {
147 nodeState.setValue("0");
148 } else {
149 statement = Integer.parseInt(nodeState.getValue());
150 }
151 return statement;
152 }
153
154 @Override
155 protected RequestFulfillmentCriteria getRequestFulfillmentCriteria(RouteContext routeContext) {
156 return super.getRequestFulfillmentCriteria(routeContext);
157 }
158
159
160 // -- copied from request node; a lot of this action request evaluating code should probably go into helper classes or factored into a common subclass
161
162 /**
163 * The method will get a key value which can be used for comparison purposes. If the node instance has a primary key value, it will be returned. However, if the node instance has not been saved to the database (i.e. during a simulation) this method will return the node instance passed in.
164 */
165 private Object getKey(RouteNodeInstance nodeInstance) {
166 String id = nodeInstance.getRouteNodeInstanceId();
167 return (id != null ? (Object) id : (Object) nodeInstance);
168 }
169
170 /**
171 * Checks if the document has past the final approver node by walking backward through the previous node instances.
172 * Ignores any previous nodes that have been "revoked".
173 */
174 private boolean isPastFinalApprover(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
175 FinalApproverContext context = new FinalApproverContext();
176 List revokedNodeInstances = KEWServiceLocator.getRouteNodeService().getRevokedNodeInstances(document);
177 Set revokedNodeInstanceIds = new HashSet();
178 for (Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
179 RouteNodeInstance revokedNodeInstance = (RouteNodeInstance) iterator.next();
180 revokedNodeInstanceIds.add(revokedNodeInstance.getRouteNodeInstanceId());
181 }
182 isPastFinalApprover(nodeInstance.getPreviousNodeInstances(), context, revokedNodeInstanceIds);
183 return context.isPast;
184 }
185
186 private void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context, Set revokedNodeInstanceIds) {
187 if (previousNodeInstances != null && !previousNodeInstances.isEmpty()) {
188 for (Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext();) {
189 if (context.isPast) {
190 return;
191 }
192 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
193 if (context.inspected.contains(getKey(nodeInstance))) {
194 continue;
195 } else {
196 context.inspected.add(getKey(nodeInstance));
197 }
198 if (Boolean.TRUE.equals(nodeInstance.getRouteNode().getFinalApprovalInd())) {
199 // if the node instance has been revoked (by a Return To Previous action for example)
200 // then we don't want to consider that node when we determine if we are past final
201 // approval or not
202 if (!revokedNodeInstanceIds.contains(nodeInstance.getRouteNodeInstanceId())) {
203 context.isPast = true;
204 }
205 return;
206 }
207 isPastFinalApprover(nodeInstance.getPreviousNodeInstances(), context, revokedNodeInstanceIds);
208 }
209 }
210 }
211 public static boolean isSupressingPolicyErrors(RouteContext routeContext) {
212 Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(SUPPRESS_POLICY_ERRORS_KEY);
213 if (suppressPolicyErrors == null || ! suppressPolicyErrors) {
214 return false;
215 }
216 return true;
217 }
218
219 private class FinalApproverContext {
220 public Set inspected = new HashSet();
221 public boolean isPast = false;
222 }
223
224 }