1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.Set;
23
24 import org.apache.commons.collections.CollectionUtils;
25 import org.apache.commons.lang.ObjectUtils;
26 import org.kuali.rice.coreservice.framework.parameter.ParameterService;
27 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
28 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
29 import org.kuali.rice.kew.engine.RouteContext;
30 import org.kuali.rice.kew.engine.RouteHelper;
31 import org.kuali.rice.kew.exception.RouteManagerException;
32 import org.kuali.rice.kew.api.exception.WorkflowException;
33 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
34 import org.kuali.rice.kew.routemodule.RouteModule;
35 import org.kuali.rice.kew.service.KEWServiceLocator;
36 import org.kuali.rice.kew.util.ClassDumper;
37 import org.kuali.rice.kew.api.KewApiConstants;
38 import org.kuali.rice.krad.util.KRADConstants;
39
40
41
42
43
44
45
46 public class RequestsNode extends RequestActivationNode {
47
48 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
49 .getLogger( RequestsNode.class );
50
51 protected static final String SUPPRESS_POLICY_ERRORS_KEY = "_suppressPolicyErrorsRequestActivationNode";
52
53 public final SimpleResult process(RouteContext routeContext, RouteHelper routeHelper)
54 throws Exception {
55 try {
56 if (processCustom(routeContext, routeHelper)) {
57 return super.process(routeContext, routeHelper);
58 }
59 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
60 boolean isInitial = nodeInstance.isInitial();
61 int currentIteration = 0;
62 List<ActionRequestValue> requestsGenerated = new ArrayList<ActionRequestValue>();
63 while (true) {
64 detectRunawayProcess(routeContext, currentIteration++);
65 if (isInitial) {
66 requestsGenerated = generateRequests(routeContext);
67
68
69 isInitial = false;
70 }
71 SimpleResult simpleResult = super.process(routeContext, routeHelper);
72 if (simpleResult.isComplete()) {
73 RouteModule routeModule = getRouteModule(routeContext);
74 boolean moreRequestsAvailable = routeModule.isMoreRequestsAvailable(routeContext);
75 if (!moreRequestsAvailable) {
76 applyPoliciesOnExit(requestsGenerated, routeContext);
77 return simpleResult;
78 } else {
79 requestsGenerated = generateRequests(routeContext);
80 }
81 } else {
82 return simpleResult;
83 }
84 }
85 } catch ( RouteManagerException ex ) {
86
87 throw ex;
88 } catch ( Exception e ) {
89 LOG.error( "Caught exception routing", e );
90 throw new RouteManagerException( e.getMessage(), e, routeContext );
91 }
92 }
93
94 protected List<ActionRequestValue> generateRequests(RouteContext routeContext) throws Exception {
95 DocumentRouteHeaderValue document = routeContext.getDocument();
96 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
97 RouteNode node = nodeInstance.getRouteNode();
98 if (LOG.isDebugEnabled()) {
99 LOG.debug("RouteHeader info inside routing loop\n" + ClassDumper.dumpFields(document));
100 LOG.debug("Looking for new actionRequests - routeLevel: " + node.getRouteNodeName());
101 }
102 boolean suppressPolicyErrors = isSuppressingPolicyErrors(routeContext);
103 List<ActionRequestValue> requests = getNewActionRequests(routeContext);
104
105 if (!suppressPolicyErrors) {
106 verifyFinalApprovalRequest(document, requests, nodeInstance, routeContext);
107 }
108 return requests;
109 }
110
111
112
113
114
115
116
117
118 protected void applyPoliciesOnExit(List<ActionRequestValue> requestsGenerated, RouteContext routeContext) {
119 DocumentRouteHeaderValue document = routeContext.getDocument();
120 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
121 RouteNode node = nodeInstance.getRouteNode();
122
123 if (node.isMandatory() && !isSuppressingPolicyErrors(routeContext) && CollectionUtils.isEmpty(requestsGenerated)) {
124 List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
125 if (actionRequests.isEmpty()) {
126 LOG.warn("no requests generated for mandatory route - " + node.getRouteNodeName());
127 throw new RouteManagerException(
128 "No requests generated for mandatory route " + node.getRouteNodeName() + ":" + node
129 .getRouteMethodName(), routeContext);
130 }
131 }
132 }
133
134
135
136
137
138
139 protected boolean processCustom(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
140 return false;
141 }
142
143
144
145
146
147
148 protected void verifyFinalApprovalRequest( DocumentRouteHeaderValue document, List<ActionRequestValue> requests, RouteNodeInstance nodeInstance, RouteContext routeContext ) throws RouteManagerException {
149 boolean pastFinalApprover = isPastFinalApprover( document, nodeInstance );
150 boolean hasApproveRequest = false;
151 for ( ActionRequestValue actionRequest : requests ) {
152 if ( actionRequest.isApproveOrCompleteRequest() ) {
153 hasApproveRequest = true;
154 break;
155 }
156 }
157
158
159 if ( nodeInstance.getRouteNode().getFinalApprovalInd().booleanValue() ) {
160
161
162 if ( !hasApproveRequest ) {
163 throw new RouteManagerException(
164 "No Approve Request generated after final approver", routeContext );
165 }
166 } else if ( pastFinalApprover ) {
167
168
169 if ( hasApproveRequest ) {
170 throw new RouteManagerException(
171 "Approve Request generated after final approver", routeContext );
172 }
173 }
174 }
175
176 public List<ActionRequestValue> getNewActionRequests(RouteContext context) throws Exception {
177 RouteNodeInstance nodeInstance = context.getNodeInstance();
178 String routeMethodName = nodeInstance.getRouteNode().getRouteMethodName();
179 if ( LOG.isDebugEnabled() ) {
180 LOG.debug( "Looking for action requests in " + routeMethodName + " : "
181 + nodeInstance.getRouteNode().getRouteNodeName() );
182 }
183 List<ActionRequestValue> newRequests = new ArrayList<ActionRequestValue>();
184 try {
185 RouteModule routeModule = getRouteModule( context );
186 List<ActionRequestValue> requests = routeModule.findActionRequests( context );
187
188 requests = KEWServiceLocator.getActionRequestService().getRootRequests(requests);
189 List<ActionRequestValue> uniqueRequests = new ArrayList<ActionRequestValue>();
190 for ( ActionRequestValue actionRequest : requests ) {
191 boolean duplicateFound = false;
192 for (ActionRequestValue uniqueRequest: uniqueRequests ) {
193 if (isDuplicateActionRequestDetected(uniqueRequest, actionRequest)) {
194 duplicateFound = true;
195 break;
196 }
197 }
198 if (!duplicateFound) {
199 uniqueRequests.add(actionRequest);
200 duplicateFound = false;
201 }
202 }
203 for ( ActionRequestValue actionRequest : uniqueRequests ) {
204 if ( LOG.isDebugEnabled() ) {
205 LOG.debug( "Request generated by RouteModule '" + routeModule + "' for node "
206 + nodeInstance + ":" + actionRequest );
207 }
208 actionRequest = KEWServiceLocator.getActionRequestService()
209 .initializeActionRequestGraph( actionRequest, context.getDocument(),
210 nodeInstance );
211 actionRequest = saveActionRequest( context, actionRequest );
212 newRequests.add( actionRequest );
213 }
214 } catch ( WorkflowException ex ) {
215 LOG.warn( "Caught WorkflowException during routing", ex );
216 throw new RouteManagerException( ex, context );
217 }
218 return newRequests;
219 }
220 private boolean isDuplicateActionRequestDetected(ActionRequestValue actionRequest, ActionRequestValue actionRequestToCompare) {
221 if ( (ObjectUtils.equals(actionRequest.getActionRequested(), actionRequestToCompare.getActionRequested())) &&
222 (ObjectUtils.equals(actionRequest.getPrincipalId(), actionRequestToCompare.getPrincipalId())) &&
223 (ObjectUtils.equals(actionRequest.getStatus(), actionRequestToCompare.getStatus())) &&
224 (ObjectUtils.equals(actionRequest.getResponsibilityId(), actionRequestToCompare.getResponsibilityId())) &&
225 (ObjectUtils.equals(actionRequest.getGroupId(), actionRequestToCompare.getGroupId())) &&
226 (ObjectUtils.equals(actionRequest.getPriority(), actionRequestToCompare.getPriority())) &&
227 (ObjectUtils.equals(actionRequest.getRouteLevel(), actionRequestToCompare.getRouteLevel())) &&
228 (ObjectUtils.equals(actionRequest.getResponsibilityDesc(), actionRequestToCompare.getResponsibilityDesc())) &&
229 (ObjectUtils.equals(actionRequest.getAnnotation(), actionRequestToCompare.getAnnotation())) &&
230 (ObjectUtils.equals(actionRequest.getForceAction(), actionRequestToCompare.getForceAction())) &&
231 (ObjectUtils.equals(actionRequest.getQualifiedRoleName(), actionRequestToCompare.getQualifiedRoleName())) &&
232 (ObjectUtils.equals(actionRequest.getRoleName(), actionRequestToCompare.getRoleName())) &&
233 (ObjectUtils.equals(actionRequest.getApprovePolicy(), actionRequestToCompare.getApprovePolicy())) &&
234 (ObjectUtils.equals(actionRequest.getCurrentIndicator(), actionRequestToCompare.getCurrentIndicator())) &&
235 (ObjectUtils.equals(actionRequest.getNodeInstance(), actionRequestToCompare.getNodeInstance())) &&
236 (ObjectUtils.equals(actionRequest.getActionTaken(), actionRequestToCompare.getActionTaken())) &&
237 (ObjectUtils.equals(actionRequest.getDelegationType(), actionRequestToCompare.getDelegationType())) &&
238 (ObjectUtils.equals(actionRequest.getRuleBaseValuesId(), actionRequestToCompare.getRuleBaseValuesId())) &&
239 (ObjectUtils.equals(actionRequest.getDisplayStatus(), actionRequestToCompare.getDisplayStatus())) &&
240 (ObjectUtils.equals(actionRequest.getQualifiedRoleNameLabel(), actionRequestToCompare.getQualifiedRoleNameLabel())) &&
241 (ObjectUtils.equals(actionRequest.getParentActionRequestId(), actionRequestToCompare.getParentActionRequestId())) &&
242 (ObjectUtils.equals(actionRequest.getDocVersion(), actionRequestToCompare.getDocVersion())) &&
243 (ObjectUtils.equals(actionRequest.getActionTakenId(), actionRequestToCompare.getActionTakenId())) &&
244 (ObjectUtils.equals(actionRequest.getDocumentId(), actionRequestToCompare.getDocumentId())) ) {
245 return true;
246 } else {
247 return false;
248 }
249 }
250
251
252
253
254
255 protected RouteModule getRouteModule(RouteContext context) throws Exception {
256 return KEWServiceLocator.getRouteModuleService().findRouteModule(
257 context.getNodeInstance().getRouteNode() );
258 }
259
260
261
262
263
264
265 protected boolean isPastFinalApprover(DocumentRouteHeaderValue document,
266 RouteNodeInstance nodeInstance) {
267 FinalApproverContext context = new FinalApproverContext();
268 List revokedNodeInstances = KEWServiceLocator.getRouteNodeService()
269 .getRevokedNodeInstances( document );
270 Set revokedNodeInstanceIds = new HashSet();
271 for ( Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
272 RouteNodeInstance revokedNodeInstance = (RouteNodeInstance)iterator.next();
273 revokedNodeInstanceIds.add( revokedNodeInstance.getRouteNodeInstanceId() );
274 }
275 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
276 revokedNodeInstanceIds );
277 return context.isPast;
278 }
279
280 protected void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context,
281 Set revokedNodeInstanceIds) {
282 if ( previousNodeInstances != null && !previousNodeInstances.isEmpty() ) {
283 for ( Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext(); ) {
284 if ( context.isPast ) {
285 return;
286 }
287 RouteNodeInstance nodeInstance = (RouteNodeInstance)iterator.next();
288 if ( context.inspected.contains( getKey( nodeInstance ) ) ) {
289 continue;
290 } else {
291 context.inspected.add( getKey( nodeInstance ) );
292 }
293 if ( Boolean.TRUE.equals( nodeInstance.getRouteNode().getFinalApprovalInd() ) ) {
294
295
296
297
298
299 if ( !revokedNodeInstanceIds.contains( nodeInstance.getRouteNodeInstanceId() ) ) {
300 context.isPast = true;
301 }
302 return;
303 }
304 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
305 revokedNodeInstanceIds );
306 }
307 }
308 }
309
310
311
312
313
314
315
316
317 protected Object getKey(RouteNodeInstance nodeInstance) {
318 String id = nodeInstance.getRouteNodeInstanceId();
319 return (id != null ? (Object)id : (Object)nodeInstance);
320 }
321
322 protected void detectRunawayProcess(RouteContext routeContext, int currentIteration) throws NumberFormatException {
323 String maxNodesConstant = getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.MAX_NODES_BEFORE_RUNAWAY_PROCESS);
324 int maxNodes = (org.apache.commons.lang.StringUtils.isEmpty(maxNodesConstant)) ? 50 : Integer.valueOf(maxNodesConstant);
325 if (currentIteration > maxNodes) {
326 throw new RouteManagerException("Detected a runaway process within RequestsNode for document with id '" + routeContext.getDocument().getDocumentId() + "' after " + currentIteration + " iterations.");
327 }
328 }
329
330 protected class FinalApproverContext {
331 public Set inspected = new HashSet();
332
333 public boolean isPast = false;
334 }
335
336 public static boolean isSuppressingPolicyErrors(RouteContext routeContext) {
337 Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(
338 SUPPRESS_POLICY_ERRORS_KEY );
339 if ( suppressPolicyErrors == null || !suppressPolicyErrors ) {
340 return false;
341 }
342 return true;
343 }
344
345 @SuppressWarnings("unchecked")
346 public static void setSuppressPolicyErrors(RouteContext routeContext) {
347 routeContext.getParameters().put( SUPPRESS_POLICY_ERRORS_KEY, Boolean.TRUE );
348 }
349
350 protected ParameterService getParameterService() {
351 return CoreFrameworkServiceLocator.getParameterService();
352 }
353 }