View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.engine.node;
17  
18  import java.util.ArrayList;
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
27  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
28  import org.kuali.rice.kew.engine.RouteContext;
29  import org.kuali.rice.kew.engine.RouteHelper;
30  import org.kuali.rice.kew.exception.RouteManagerException;
31  import org.kuali.rice.kew.exception.WorkflowException;
32  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
33  import org.kuali.rice.kew.rule.FlexRM;
34  import org.kuali.rice.kew.rule.KRAMetaRuleEngine;
35  import org.kuali.rice.kew.rule.RuleExpressionResult;
36  import org.kuali.rice.kew.service.KEWServiceLocator;
37  import org.kuali.rice.kew.util.Utilities;
38  
39  
40  /**
41   * Node that implements a KRAMetaRule, with multiple request/response phases
42   *
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class KRAMetaRuleNode extends IteratedRequestActivationNode {
46  	private static String SUPPRESS_POLICY_ERRORS_KEY = "_suppressPolicyErrorsRequestActivationNode";
47  
48  	protected List<ActionRequestValue> generateUninitializedRequests(ActionRequestFactory arFactory, RouteContext context, RuleExpressionResult result) throws WorkflowException {
49  		FlexRM flexRM = new FlexRM();
50  		flexRM.makeActionRequests(arFactory, result.getResponsibilities(), context, result.getRule().getDefinition(), context.getDocument(), null, null);
51  		return new ArrayList<ActionRequestValue>(arFactory.getRequestGraphs());
52  	}
53  
54  	protected List<ActionRequestValue> initializeRequests(List<ActionRequestValue> requests, RouteContext context) {
55  		// RequestNode.getNewActionRequests
56  		List<ActionRequestValue> newRequests = new ArrayList<ActionRequestValue>();
57  		for (Iterator iterator = requests.iterator(); iterator.hasNext();) {
58  			ActionRequestValue actionRequest = (ActionRequestValue) iterator.next();
59  			actionRequest = KEWServiceLocator.getActionRequestService().initializeActionRequestGraph(actionRequest, context.getDocument(), context.getNodeInstance());
60  			saveActionRequest(context, actionRequest);
61  			newRequests.add(actionRequest);
62  		}
63  		return newRequests;
64  	}
65  
66  	@Override
67  	protected boolean generateNewRequests(boolean initial, RouteContext context, RouteHelper routeHelper)
68  	throws WorkflowException, Exception {
69  		RouteNodeInstance nodeInstance = context.getNodeInstance();
70  		RouteNode nodeDef = nodeInstance.getRouteNode();
71  
72  		// get the expression
73  		Map<String, String> cfg = Utilities.getKeyValueCollectionAsMap(nodeDef.getConfigParams());
74  		String expression = cfg.get("expression");
75  		if (StringUtils.isEmpty(expression)) {
76  			throw new WorkflowException("No meta-rule expression supplied in node " + nodeDef);
77  		}
78  		KRAMetaRuleEngine engine = new KRAMetaRuleEngine(expression);
79  
80  		// get the current statement
81  		int curStatement = getCurStatement(nodeInstance);
82  
83  		engine.setCurStatement(curStatement);
84  
85  		if (engine.isDone()) {
86  			return false;
87  		}
88  
89  		// generate next round of action requests
90  		RuleExpressionResult result = engine.processSingleStatement(context);
91  		if (!result.isSuccess()) {
92  			return false;
93  		}
94  
95  		boolean suppressPolicyErrors = isSupressingPolicyErrors(context);
96  		boolean pastFinalApprover = isPastFinalApprover(context.getDocument(), nodeInstance);
97  
98  		// actionRequests.addAll(makeActionRequests(context, rule, routeHeader, null, null));
99  		// copied from parts of FlexRM and RequestsNode
100 		ActionRequestFactory arFactory = new ActionRequestFactory(context.getDocument(), context.getNodeInstance());
101 		List<ActionRequestValue> requests = generateUninitializedRequests(arFactory, context, result);
102 		requests = initializeRequests(requests, context);
103 		// RequestNode
104 		if ((requests.isEmpty()) && nodeDef.getMandatoryRouteInd().booleanValue() && ! suppressPolicyErrors) {
105 			LOG.warn("no requests generated for mandatory route - " + nodeDef.getRouteNodeName());
106 			throw new RouteManagerException("No requests generated for mandatory route " + nodeDef.getRouteNodeName() + ":" + nodeDef.getRouteMethodName(), context);
107 		}
108 		// determine if we have any approve requests for FinalApprover checks
109 		boolean hasApproveRequest = false;
110 		for (Iterator iter = requests.iterator(); iter.hasNext();) {
111 			ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
112 			hasApproveRequest = actionRequest.isApproveOrCompleteRequest() || hasApproveRequest;
113 		}
114 		// if final approver route level and no approve request send to exception routing
115 		if (nodeDef.getFinalApprovalInd().booleanValue()) {
116 			// we must have an approve request generated if final approver level.
117 			if (!hasApproveRequest && ! suppressPolicyErrors) {
118 				throw new RuntimeException("No Approve Request generated after final approver");
119 			}
120 		} else if (pastFinalApprover) {
121 			// we can't allow generation of approve requests after final approver. This guys going to exception routing.
122 			if (hasApproveRequest && ! suppressPolicyErrors) {
123 				throw new RuntimeException("Approve Request generated after final approver");
124 			}
125 		}
126 
127 		// increment to next statement
128 		nodeInstance.getNodeState("stmt").setValue(String.valueOf(curStatement + 1));
129 		return !requests.isEmpty();
130 	}
131 
132 	/**
133 	 * @param nodeInstance the current node instance under execution
134 	 * @return the meta-rule statement this node instance should process
135 	 */
136 	protected static int getCurStatement(RouteNodeInstance nodeInstance) {
137 		int statement = 0;
138 		NodeState nodeState = nodeInstance.getNodeState("stmt");
139 		if (nodeState == null) {
140 			nodeState = new NodeState();
141 			nodeState.setKey("stmt");
142 			nodeState.setNodeInstance(nodeInstance);
143 			nodeInstance.addNodeState(nodeState);
144 		}
145 		if (StringUtils.isEmpty(nodeState.getValue())) {
146 			nodeState.setValue("0");
147 		} else {
148 			statement = Integer.parseInt(nodeState.getValue());
149 		}
150 		return statement;
151 	}
152 
153 	@Override
154 	protected RequestFulfillmentCriteria getRequestFulfillmentCriteria(RouteContext routeContext) {
155 		return super.getRequestFulfillmentCriteria(routeContext);
156 	}
157 
158 
159 	// -- copied from request node; a lot of this action request evaluating code should probably go into helper classes or factored into a common subclass
160 
161 	/**
162 	 * 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.
163 	 */
164 	private Object getKey(RouteNodeInstance nodeInstance) {
165 		String id = nodeInstance.getRouteNodeInstanceId();
166 		return (id != null ? (Object) id : (Object) nodeInstance);
167 	}
168 
169 	/**
170 	 * Checks if the document has past the final approver node by walking backward through the previous node instances.
171 	 * Ignores any previous nodes that have been "revoked".
172 	 */
173 	private boolean isPastFinalApprover(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
174 		FinalApproverContext context = new FinalApproverContext();
175 		List revokedNodeInstances = KEWServiceLocator.getRouteNodeService().getRevokedNodeInstances(document);
176 		Set revokedNodeInstanceIds = new HashSet();
177 		for (Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
178 			RouteNodeInstance revokedNodeInstance = (RouteNodeInstance) iterator.next();
179 			revokedNodeInstanceIds.add(revokedNodeInstance.getRouteNodeInstanceId());
180 		}
181 		isPastFinalApprover(nodeInstance.getPreviousNodeInstances(), context, revokedNodeInstanceIds);
182 		return context.isPast;
183 	}
184 
185 	private void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context, Set revokedNodeInstanceIds) {
186 		if (previousNodeInstances != null && !previousNodeInstances.isEmpty()) {
187 			for (Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext();) {
188 				if (context.isPast) {
189 					return;
190 				}
191 				RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
192 				if (context.inspected.contains(getKey(nodeInstance))) {
193 					continue;
194 				} else {
195 					context.inspected.add(getKey(nodeInstance));
196 				}
197 				if (Boolean.TRUE.equals(nodeInstance.getRouteNode().getFinalApprovalInd())) {
198 					// if the node instance has been revoked (by a Return To Previous action for example)
199 					// then we don't want to consider that node when we determine if we are past final
200 					// approval or not
201 					if (!revokedNodeInstanceIds.contains(nodeInstance.getRouteNodeInstanceId())) {
202 						context.isPast = true;
203 					}
204 					return;
205 				}
206 				isPastFinalApprover(nodeInstance.getPreviousNodeInstances(), context, revokedNodeInstanceIds);
207 			}
208 		}
209 	}
210 	public static boolean isSupressingPolicyErrors(RouteContext routeContext) {
211 		Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(SUPPRESS_POLICY_ERRORS_KEY);
212 		if (suppressPolicyErrors == null || ! suppressPolicyErrors) {
213 			return false;
214 		}
215 		return true;
216 	}
217 
218 	private class FinalApproverContext {
219 		public Set inspected = new HashSet();
220 		public boolean isPast = false;
221 	}
222 
223 }