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
187 requests = KEWServiceLocator.getActionRequestService().getRootRequests(requests);
188 for ( ActionRequestValue actionRequest : requests ) {
189 if ( LOG.isDebugEnabled() ) {
190 LOG.debug( "Request generated by RouteModule '" + routeModule + "' for node "
191 + nodeInstance + ":" + actionRequest );
192 }
193 actionRequest = KEWServiceLocator.getActionRequestService()
194 .initializeActionRequestGraph( actionRequest, context.getDocument(),
195 nodeInstance );
196 actionRequest = saveActionRequest( context, actionRequest );
197 newRequests.add( actionRequest );
198 }
199 } catch ( WorkflowException ex ) {
200 LOG.warn( "Caught WorkflowException during routing", ex );
201 throw new RouteManagerException( ex, context );
202 }
203 return newRequests;
204 }
205
206
207
208
209
210 protected RouteModule getRouteModule(RouteContext context) throws Exception {
211 return KEWServiceLocator.getRouteModuleService().findRouteModule(
212 context.getNodeInstance().getRouteNode() );
213 }
214
215
216
217
218
219
220 protected boolean isPastFinalApprover(DocumentRouteHeaderValue document,
221 RouteNodeInstance nodeInstance) {
222 FinalApproverContext context = new FinalApproverContext();
223 List revokedNodeInstances = KEWServiceLocator.getRouteNodeService()
224 .getRevokedNodeInstances( document );
225 Set revokedNodeInstanceIds = new HashSet();
226 for ( Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
227 RouteNodeInstance revokedNodeInstance = (RouteNodeInstance)iterator.next();
228 revokedNodeInstanceIds.add( revokedNodeInstance.getRouteNodeInstanceId() );
229 }
230 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
231 revokedNodeInstanceIds );
232 return context.isPast;
233 }
234
235 protected void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context,
236 Set revokedNodeInstanceIds) {
237 if ( previousNodeInstances != null && !previousNodeInstances.isEmpty() ) {
238 for ( Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext(); ) {
239 if ( context.isPast ) {
240 return;
241 }
242 RouteNodeInstance nodeInstance = (RouteNodeInstance)iterator.next();
243 if ( context.inspected.contains( getKey( nodeInstance ) ) ) {
244 continue;
245 } else {
246 context.inspected.add( getKey( nodeInstance ) );
247 }
248 if ( Boolean.TRUE.equals( nodeInstance.getRouteNode().getFinalApprovalInd() ) ) {
249
250
251
252
253
254 if ( !revokedNodeInstanceIds.contains( nodeInstance.getRouteNodeInstanceId() ) ) {
255 context.isPast = true;
256 }
257 return;
258 }
259 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
260 revokedNodeInstanceIds );
261 }
262 }
263 }
264
265
266
267
268
269
270
271
272 protected Object getKey(RouteNodeInstance nodeInstance) {
273 String id = nodeInstance.getRouteNodeInstanceId();
274 return (id != null ? (Object)id : (Object)nodeInstance);
275 }
276
277 protected void detectRunawayProcess(RouteContext routeContext, int currentIteration) throws NumberFormatException {
278 String maxNodesConstant = getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.MAX_NODES_BEFORE_RUNAWAY_PROCESS);
279 int maxNodes = (org.apache.commons.lang.StringUtils.isEmpty(maxNodesConstant)) ? 50 : Integer.valueOf(maxNodesConstant);
280 if (currentIteration > maxNodes) {
281 throw new RouteManagerException("Detected a runaway process within RequestsNode for document with id '" + routeContext.getDocument().getDocumentId() + "' after " + currentIteration + " iterations.");
282 }
283 }
284
285 protected class FinalApproverContext {
286 public Set inspected = new HashSet();
287
288 public boolean isPast = false;
289 }
290
291 public static boolean isSuppressingPolicyErrors(RouteContext routeContext) {
292 Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(
293 SUPPRESS_POLICY_ERRORS_KEY );
294 if ( suppressPolicyErrors == null || !suppressPolicyErrors ) {
295 return false;
296 }
297 return true;
298 }
299
300 @SuppressWarnings("unchecked")
301 public static void setSuppressPolicyErrors(RouteContext routeContext) {
302 routeContext.getParameters().put( SUPPRESS_POLICY_ERRORS_KEY, Boolean.TRUE );
303 }
304
305 protected ParameterService getParameterService() {
306 return CoreFrameworkServiceLocator.getParameterService();
307 }
308 }