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