View Javadoc
1   /**
2    * Copyright 2005-2014 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   * A node which will activate any requests on it, returning true when there are no more requests which require
40   * activation.
41   *
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class RequestActivationNode extends RequestActivationNodeBase {
45  
46      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RequestActivationNode.class);
47      private static long generatedRequestPriority = 0;
48  
49      @Override
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,
59                  nodeInstance)) {
60              return new SimpleResult(true);
61          } else {
62              return new SimpleResult(false);
63          }
64      }
65  
66      /**
67       * Returns true if this node has completed it's work and should transition to the next node.
68       *
69       * <p>This implementation will return true if there are no remaining pending approve or complete action requests at
70       * the given node instance. Subclasses can override this method to customize the behavior of how this determination
71       * is made.</p>
72       *
73       * @param document the document the is being processed
74       * @param nodeInstance the current node instance that is being processed
75       * @return true if this node has completed it's work, false otherwise
76       */
77      protected boolean shouldTransition(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
78          List<ActionRequestValue> requests =
79                  KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(
80                          document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
81          boolean shouldTransition = true;
82          for (ActionRequestValue request : requests) {
83              if (request.isApproveOrCompleteRequest()) {
84                  shouldTransition = false;
85                  break;
86              }
87          }
88          return shouldTransition;
89      }
90  
91      /**
92       * Activates the action requests that are pending at this routelevel of the
93       * document. The requests are processed by priority and then request ID. It
94       * is implicit in the access that the requests are activated according to
95       * the route level above all.
96       * <p>
97       * FYI and acknowledgment requests do not cause the processing to stop. Only
98       * action requests for approval or completion cause the processing to stop
99       * and then only for route level with a serialized activation policy. Only
100      * requests at the current document's current route level are activated.
101      * Inactive requests at a lower level cause a routing exception.
102      * <p>
103      * Exception routing and adhoc routing are processed slightly differently.
104      *
105      * @return True if the any approval actions were activated.
106      * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
107      * @throws WorkflowException
108      */
109     public boolean activateRequests(RouteContext context, DocumentRouteHeaderValue document,
110             RouteNodeInstance nodeInstance) throws WorkflowException {
111         MDC.put("docId", document.getDocumentId());
112         PerformanceLogger performanceLogger = new PerformanceLogger(document.getDocumentId());
113         List<ActionItem> generatedActionItems = new ArrayList<ActionItem>();
114         List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
115         if (context.isSimulation()) {
116             for (ActionRequestValue ar : context.getDocument().getActionRequests()) {
117                 // TODO logic check below duplicates behavior of the ActionRequestService.findPendingRootRequestsByDocIdAtRouteNode(documentId, routeNodeInstanceId) method
118                 if (ar.getCurrentIndicator()
119                         && (ActionRequestStatus.INITIALIZED.getCode().equals(ar.getStatus())
120                         || ActionRequestStatus.ACTIVATED.getCode().equals(ar.getStatus()))
121                         && ar.getNodeInstance().getRouteNodeInstanceId().equals(nodeInstance.getRouteNodeInstanceId())
122                         && ar.getParentActionRequest() == null) {
123                     requests.add(ar);
124                 }
125             }
126             requests.addAll(context.getEngineState().getGeneratedRequests());
127         } else {
128             requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(
129                     document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
130         }
131         if (LOG.isDebugEnabled()) {
132             LOG.debug("Pending Root Requests " + requests.size());
133         }
134         boolean activatedApproveRequest = activateRequestsCustom(context, requests, generatedActionItems, document,
135                 nodeInstance);
136 
137         // now let's send notifications, since this code needs to be able to activate each request individually, we need
138         // to collection all action items and then notify after all have been generated
139         notify(context, generatedActionItems, nodeInstance);
140 
141         performanceLogger.log("Time to activate requests.");
142         return activatedApproveRequest;
143     }
144 
145     protected boolean activateRequestsCustom(RouteContext context, List<ActionRequestValue> requests,
146             List<ActionItem> generatedActionItems, DocumentRouteHeaderValue document,
147             RouteNodeInstance nodeInstance) throws WorkflowException {
148         // make a copy of the list so that we can sort it
149         requests = new ArrayList<ActionRequestValue>(requests);
150         Collections.sort(requests, new Utilities.PrioritySorter());
151         String activationType = nodeInstance.getRouteNode().getActivationType();
152         if (StringUtils.isBlank(activationType)) {
153             // not sure if this is really necessary, but preserves behavior prior to introduction of priority-parallel activation
154             activationType = KewApiConstants.ROUTE_LEVEL_SEQUENCE;
155         }
156         boolean isParallel = KewApiConstants.ROUTE_LEVEL_PARALLEL.equals(activationType);
157         boolean isPriorityParallel = KewApiConstants.ROUTE_LEVEL_PRIORITY_PARALLEL.equals(activationType);
158         boolean isSequential = KewApiConstants.ROUTE_LEVEL_SEQUENCE.equals(activationType);
159 
160         boolean activatedApproveRequest = false;
161         if (CollectionUtils.isNotEmpty(requests)) {
162             // if doing priority-parallel
163             int currentPriority = requests.get(0).getPriority();
164             for (ActionRequestValue request : requests) {
165                 if (request.getParentActionRequest() != null || request.getNodeInstance() == null) {
166                     // 1. disregard request if it's not a top-level request
167                     // 2. disregard request if it's a "future" request and hasn't been attached to a node instance yet
168                     continue;
169                 }
170                 if (activatedApproveRequest && (!context.isSimulation() || !context.getActivationContext()
171                         .isActivateRequests())) {
172                     if (isSequential || (isPriorityParallel && request.getPriority() != currentPriority)) {
173                         break;
174                     }
175                 }
176                 currentPriority = request.getPriority();
177                 if (request.isActive()) {
178                     activatedApproveRequest = activatedApproveRequest || request.isApproveOrCompleteRequest();
179                     continue;
180                 }
181                 logProcessingMessage(request);
182                 if (LOG.isDebugEnabled()) {
183                     LOG.debug("Activating request: " + request);
184                 }
185                 activatedApproveRequest = activateRequest(context, request, nodeInstance, generatedActionItems)
186                         || activatedApproveRequest;
187             }
188         }
189         return activatedApproveRequest;
190     }
191 
192     protected boolean activateRequest(RouteContext context, ActionRequestValue actionRequest,
193             RouteNodeInstance nodeInstance, List<ActionItem> generatedActionItems) {
194         if (actionRequest.isRoleRequest()) {
195             List<ActionRequestValue> actionRequests =
196                     KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(
197                             actionRequest.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
198             for (ActionRequestValue siblingRequest : actionRequests) {
199                 if (actionRequest.getRoleName().equals(siblingRequest.getRoleName())) {
200                     KEWServiceLocator.getActionRequestService().activateRequestNoNotification(siblingRequest,
201                             context.getActivationContext());
202                     // the generated action items can be found in the activation context
203                     generatedActionItems.addAll(context.getActivationContext().getGeneratedActionItems());
204                 }
205             }
206         }
207         actionRequest = KEWServiceLocator.getActionRequestService().activateRequestNoNotification(actionRequest,
208                 context.getActivationContext());
209         // the generated action items can be found in the activation context
210         generatedActionItems.addAll(context.getActivationContext().getGeneratedActionItems());
211         return actionRequest.isApproveOrCompleteRequest() && !actionRequest.isDone();
212     }
213 
214     protected ActionRequestValue saveActionRequest(RouteContext context, ActionRequestValue actionRequest) {
215         if (!context.isSimulation()) {
216             return KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
217         } else {
218             actionRequest.setActionRequestId(String.valueOf(generatedRequestPriority++));
219             context.getEngineState().getGeneratedRequests().add(actionRequest);
220             return actionRequest;
221         }
222 
223     }
224 
225     protected DocumentRouteHeaderValue saveDocument(RouteContext context, DocumentRouteHeaderValue document) {
226         if (!context.isSimulation()) {
227             document = KEWServiceLocator.getRouteHeaderService().saveRouteHeader(document);
228             context.setDocument(document);
229         }
230         return document;
231     }
232 
233     protected void logProcessingMessage(ActionRequestValue request) {
234         if (LOG.isDebugEnabled()) {
235             RouteNodeInstance nodeInstance = request.getNodeInstance();
236             StringBuffer buffer = new StringBuffer();
237             buffer.append("Processing AR: ").append(request.getActionRequestId()).append("\n");
238             buffer.append("AR Node Name: ").append(nodeInstance != null ? nodeInstance.getName() : "null").append("\n");
239             buffer.append("AR RouteLevel: ").append(request.getRouteLevel()).append("\n");
240             buffer.append("AR Request Code: ").append(request.getActionRequested()).append("\n");
241             buffer.append("AR Request priority: ").append(request.getPriority()).append("\n");
242             LOG.debug(buffer);
243         }
244     }
245 
246 }