View Javadoc

1   /**
2    * Copyright 2005-2012 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.Collections;
20  import java.util.Iterator;
21  import java.util.List;
22  
23  import org.apache.commons.collections.CollectionUtils;
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.log4j.MDC;
26  import org.kuali.rice.kew.actionitem.ActionItem;
27  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
28  import org.kuali.rice.kew.api.action.ActionRequestStatus;
29  import org.kuali.rice.kew.api.exception.WorkflowException;
30  import org.kuali.rice.kew.engine.RouteContext;
31  import org.kuali.rice.kew.engine.RouteHelper;
32  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
33  import org.kuali.rice.kew.service.KEWServiceLocator;
34  import org.kuali.rice.kew.api.KewApiConstants;
35  import org.kuali.rice.kew.util.PerformanceLogger;
36  import org.kuali.rice.kew.util.Utilities;
37  
38  
39  /**
40   * A node which will activate any requests on it, returning true when there are no more requests 
41   * which require activation.
42   * 
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class RequestActivationNode extends RequestActivationNodeBase {
46  
47      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( RequestActivationNode.class );
48      private static long generatedRequestPriority = 0;
49  
50      public SimpleResult process(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
51          DocumentRouteHeaderValue document = routeContext.getDocument();
52          RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
53          if (routeContext.isSimulation()) {
54              if (routeContext.getActivationContext().isActivateRequests()) {
55                  activateRequests(routeContext, document, nodeInstance);
56              }
57              return new SimpleResult(true);
58          } else if (!activateRequests(routeContext, document, nodeInstance) && shouldTransition(document, nodeInstance)) {
59              return new SimpleResult(true);
60          } else {
61              return new SimpleResult(false);
62          }            
63      }
64      
65      public boolean shouldTransition(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
66          List requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
67          boolean shouldTransition = true;
68          for (Iterator iterator = requests.iterator(); iterator.hasNext();) {
69              ActionRequestValue request = (ActionRequestValue) iterator.next();
70              if (request.isApproveOrCompleteRequest()) {
71                  shouldTransition = false;
72                  break;
73              }
74          }
75          return shouldTransition;
76      }
77  
78      /**
79       * Activates the action requests that are pending at this routelevel of the document. The requests are processed by
80       * priority and then request ID. It is implicit in the access that the requests are activated according to the route
81       * level above all.
82       *
83       * <p>FYI and acknowledement requests do not cause the processing to stop. Only action requests for approval or
84       * completion cause the processing to stop and then only for route level with a serialized or priority-parallel
85       * activation policy. Only requests at the current document's current route level are activated. Inactive requests
86       * at a lower level cause a routing exception.</p>
87       *
88       * <p>Exception routing and adhoc routing are processed slightly differently.</p>
89       * 
90       * @return true if the any approval actions were activated.
91       */
92      public boolean activateRequests(RouteContext context, DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) throws WorkflowException {
93          MDC.put("docId", document.getDocumentId());
94          PerformanceLogger performanceLogger = new PerformanceLogger(document.getDocumentId());
95          List<ActionItem> generatedActionItems = new ArrayList<ActionItem>();
96          List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
97          if (context.isSimulation()) {
98          	for (ActionRequestValue ar : context.getDocument().getActionRequests()) {
99          		// logic check below duplicates behavior of the ActionRequestService.findPendingRootRequestsByDocIdAtRouteNode(documentId, routeNodeInstanceId) method
100 				if (ar.getCurrentIndicator()
101 						&& (ActionRequestStatus.INITIALIZED.getCode().equals(ar.getStatus()) || ActionRequestStatus.ACTIVATED.getCode().equals(ar.getStatus()))
102 						&& ar.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())
103 						&& ar.getParentActionRequest() == null) {
104 					requests.add(ar);
105 				}
106 			}
107             requests.addAll(context.getEngineState().getGeneratedRequests());
108         } else {
109             requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
110         }
111         if ( LOG.isDebugEnabled() ) {
112         	LOG.debug("Pending Root Requests " + requests.size());
113         }
114         boolean activatedApproveRequest = activateRequestsCustom( context, requests, generatedActionItems, document, nodeInstance );
115 
116         // now let's send notifications, since this code needs to be able to activate each request individually, we need
117         // to collection all action items and then notify after all have been generated
118         notify(context, generatedActionItems, nodeInstance);
119 
120         performanceLogger.log("Time to activate requests.");
121         return activatedApproveRequest;
122     }
123 
124     protected boolean activateRequestsCustom( RouteContext context, List<ActionRequestValue> requests, List<ActionItem> generatedActionItems, 
125     		DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) throws WorkflowException {
126         Collections.sort(requests, new Utilities.PrioritySorter());
127         String activationType = nodeInstance.getRouteNode().getActivationType();
128         if (StringUtils.isBlank(activationType)) {
129             // not sure if this is really necessary, but preserves behavior prior to introduction of priority-parallel activation
130             activationType = KewApiConstants.ROUTE_LEVEL_SEQUENCE;
131         }
132         boolean isParallel = KewApiConstants.ROUTE_LEVEL_PARALLEL.equals(activationType);
133         boolean isPriorityParallel = KewApiConstants.ROUTE_LEVEL_PRIORITY_PARALLEL.equals(activationType);
134         boolean isSequential = KewApiConstants.ROUTE_LEVEL_SEQUENCE.equals(activationType);
135 
136         boolean activatedApproveRequest = false;
137         if (CollectionUtils.isNotEmpty(requests)) {
138             // if doing priority-parallel
139             int currentPriority = requests.get(0).getPriority();
140             for (ActionRequestValue request : requests ) {
141                 if (request.getParentActionRequest() != null || request.getNodeInstance() == null) {
142                     // 1. disregard request if it's not a top-level request
143                     // 2. disregard request if it's a "future" request and hasn't been attached to a node instance yet
144                     continue;
145                 }
146                 if (activatedApproveRequest && (!context.isSimulation() || !context.getActivationContext().isActivateRequests())) {
147                     if (isSequential || (isPriorityParallel && request.getPriority() != currentPriority)) {
148                         break;
149                     }
150                 }
151                 currentPriority = request.getPriority();
152                 if (request.isActive()) {
153                     activatedApproveRequest = activatedApproveRequest || request.isApproveOrCompleteRequest();
154                     continue;
155                 }
156                 logProcessingMessage(request);
157                 if ( LOG.isDebugEnabled() ) {
158             	    LOG.debug("Activating request: " + request);
159                 }
160                 activatedApproveRequest = activateRequest(context, request, nodeInstance, generatedActionItems) || activatedApproveRequest;
161             }
162         }
163         return activatedApproveRequest;
164     }
165     
166     protected boolean activateRequest(RouteContext context, ActionRequestValue actionRequest, RouteNodeInstance nodeInstance, List generatedActionItems) {
167         if (actionRequest.isRoleRequest()) {
168             List actionRequests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(actionRequest.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
169             for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
170                 ActionRequestValue siblingRequest = (ActionRequestValue) iterator.next();
171                 if (actionRequest.getRoleName().equals(siblingRequest.getRoleName())) {
172                     generatedActionItems.addAll(KEWServiceLocator.getActionRequestService().activateRequestNoNotification(siblingRequest, context.getActivationContext()));
173                 }
174             }
175         }
176         generatedActionItems.addAll(KEWServiceLocator.getActionRequestService().activateRequestNoNotification(actionRequest, context.getActivationContext()));
177         return actionRequest.isApproveOrCompleteRequest() && ! actionRequest.isDone();
178     }
179     
180     protected void saveActionRequest(RouteContext context, ActionRequestValue actionRequest) {
181         if (!context.isSimulation()) {
182             KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
183         } else {
184             actionRequest.setActionRequestId(String.valueOf(generatedRequestPriority++));
185             context.getEngineState().getGeneratedRequests().add(actionRequest);    
186         }
187         
188     }
189     
190     protected void saveDocument(RouteContext context, DocumentRouteHeaderValue document) {
191         if (!context.isSimulation()) {
192             KEWServiceLocator.getRouteHeaderService().saveRouteHeader(document);
193         }
194     }
195 
196     protected void logProcessingMessage(ActionRequestValue request) {
197         if (LOG.isDebugEnabled()) {
198                 RouteNodeInstance nodeInstance = request.getNodeInstance();
199             StringBuffer buffer = new StringBuffer();
200             buffer.append("Processing AR: ").append(request.getActionRequestId()).append("\n");
201             buffer.append("AR Node Name: ").append(nodeInstance != null ? nodeInstance.getName() : "null").append("\n");
202             buffer.append("AR RouteLevel: ").append(request.getRouteLevel()).append("\n");
203             buffer.append("AR Request Code: ").append(request.getActionRequested()).append("\n");
204             buffer.append("AR Request priority: ").append(request.getPriority()).append("\n");
205             LOG.debug(buffer);
206         }
207     }
208             
209 }