Coverage Report - org.kuali.rice.kew.engine.node.IteratedRequestActivationNode
 
Classes in this File Line Coverage Branch Coverage Complexity
IteratedRequestActivationNode
0%
0/87
0%
0/52
3.25
IteratedRequestActivationNode$1
N/A
N/A
3.25
IteratedRequestActivationNode$AllBlockingRequestsCompleteCriteria
0%
0/5
0%
0/8
3.25
IteratedRequestActivationNode$RequestFulfillmentCriteria
N/A
N/A
3.25
IteratedRequestActivationNode$SimulatingCriteria
0%
0/2
N/A
3.25
 
 1  
 /**
 2  
  * Copyright 2005-2011 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 org.apache.log4j.MDC;
 19  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 20  
 import org.kuali.rice.kew.api.exception.WorkflowException;
 21  
 import org.kuali.rice.kew.engine.RouteContext;
 22  
 import org.kuali.rice.kew.engine.RouteHelper;
 23  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 24  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 25  
 import org.kuali.rice.kew.api.KewApiConstants;
 26  
 import org.kuali.rice.kew.util.PerformanceLogger;
 27  
 import org.kuali.rice.kew.util.Utilities;
 28  
 
 29  
 import java.util.ArrayList;
 30  
 import java.util.Collections;
 31  
 import java.util.Iterator;
 32  
 import java.util.List;
 33  
 
 34  
 
 35  
 /**
 36  
  * A node which will iteratively activate any requests pending on it.  Subclasses are responsible for generating the requests
 37  
  * to be activated, and then delegating to superclass {@link #process(RouteContext, RouteHelper)} to activate
 38  
  * those requests.
 39  
  *
 40  
  * This node can be used to serve multiple batches of requests, and can be re-entered multiple times.  The algorithm it implements is:
 41  
  * 
 42  
  * <ol>
 43  
  *   <li>activate any existing requests (requests may have been generated by an external party)
 44  
  *   <li>while request fulfillment criteria indicates that any pending requests have been satisfied
 45  
  *     <ol>
 46  
  *       <li>generate new requests</li>
 47  
  *       <li><b>if no requests were generated, we are done, return and transition</b></li>
 48  
  *       <li>otherwise if requests were generated
 49  
  *         <ol>
 50  
  *           <li>activate requests (depending on activation policy, serial or parallel, <i>NOT ALL GENERATED REQUESTS MAY BE ACTIVATED AT THIS POINT</i>)</li>
 51  
  *           <li>determine a request fulfillment criteria</li>
 52  
  *           <li>continue in loop</li>
 53  
  *         </ol>
 54  
  *       </li>
 55  
  *   </li>
 56  
  *   <li>otherwise if pending requests have not been satisfied, block until invoked again</li>
 57  
  * </ol>
 58  
  * This node transitions/completes when there are no remaining <i>blocking</i> action requests (i.e., no approval or
 59  
  * completion requests).
 60  
  *
 61  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 62  
  */
 63  0
 public class IteratedRequestActivationNode implements SimpleNode {
 64  
 
 65  0
     protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(getClass());
 66  0
     private static long generatedRequestPriority = 0;
 67  
 
 68  
     protected static interface RequestFulfillmentCriteria {
 69  
         public boolean pendingRequestsAreFulfilled(boolean activatedBlockingRequests, RouteContext routeContext);
 70  
     }
 71  
 
 72  0
     private static class AllBlockingRequestsCompleteCriteria implements RequestFulfillmentCriteria {
 73  
         public boolean pendingRequestsAreFulfilled(boolean activatedBlockingRequests, RouteContext routeContext) {
 74  
             // determine whether there are any pending blocking requests
 75  0
             if (activatedBlockingRequests) {
 76  
                 // if we just activated blocking requests then surely there are pending blocking requests
 77  0
                 assert(activatedBlockingRequests == blockingRequestsArePending(routeContext.getDocument(), routeContext.getNodeInstance())) : "Blocking requests were activated but none were subsequently found";
 78  0
                 return false;
 79  
             } else {
 80  
                 // otherwise let's see if there are any pre-existing blocking requests associated with this node instance
 81  0
                 return !blockingRequestsArePending(routeContext.getDocument(), routeContext.getNodeInstance());
 82  
             }
 83  
         }
 84  
     }
 85  
 
 86  0
     private static class SimulatingCriteria implements RequestFulfillmentCriteria {
 87  
         public boolean pendingRequestsAreFulfilled(boolean activatedBlockingRequests, RouteContext routeContext) {
 88  
             // when simulating, never block for requests to be fulfilled
 89  0
             return true;
 90  
         }
 91  
     }
 92  
 
 93  
     public SimpleResult process(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
 94  
         // if previous requests were satisfied (if no requests have been generated yet, this should return true
 95  0
         RequestFulfillmentCriteria criteria = getRequestFulfillmentCriteria(routeContext);
 96  
         // generate new requests until they are not satisfied or none are generated
 97  
         // if none are generated then we should transition immediately
 98  0
         boolean activatedBlockingRequests = activateRequests(routeContext); // activation should always occur as something might have generated unactived requests
 99  0
         boolean initialRequestGeneration = routeContext.getNodeInstance().isInitial();
 100  0
         while (criteria.pendingRequestsAreFulfilled(activatedBlockingRequests, routeContext)) {
 101  0
             boolean newRequestsGenerated = generateNewRequests(initialRequestGeneration, routeContext, routeHelper);
 102  0
             initialRequestGeneration = false;
 103  
 
 104  0
             if (!newRequestsGenerated) {
 105  
                 // if the pending requests were fulfilled
 106  
                 // ...and we didn't generate any new requests
 107  
                 // ...then there are no further requests to activate (XXX: WRONG)
 108  
                 // ...and we have no more processing to do
 109  0
                 return new SimpleResult(true);
 110  
             }
 111  
 
 112  
             // activate any requests that were generated
 113  0
             activatedBlockingRequests = activateRequests(routeContext);
 114  
 
 115  
             // set the request fulfillment criteria for the new set of requests
 116  
             // if we are simulating, always set a criteria that indicates fulfillment
 117  0
             if (routeContext.isSimulation()) {
 118  0
                 criteria = new SimulatingCriteria();
 119  
             } else {
 120  0
                 criteria = getRequestFulfillmentCriteria(routeContext);
 121  
             }
 122  0
         }
 123  
         // if we got here, then for some reason pending requests have not been fulfilled
 124  
         // so wait/block for request fulfillment
 125  0
         return new SimpleResult(false);
 126  
     }
 127  
 
 128  
     /**
 129  
      * Template method that subclasses should override to indicate when activated requests have been fulfilled.
 130  
      * The default implementation is that all pending blocking requests must be fulfilled.
 131  
      * @return a RequestFulfillmentCriteria that indicates when activated requests have been fulfilled.
 132  
      */
 133  
     protected RequestFulfillmentCriteria getRequestFulfillmentCriteria(RouteContext routeContext) {
 134  0
         return new AllBlockingRequestsCompleteCriteria();
 135  
     }
 136  
 
 137  
     /**
 138  
      * Template method that subclasses should override to generates new requests
 139  
      * @param initial whether this is the very first request generation, that is, the first generation in the first invocation of the node
 140  
      * @return whether new requests were generated
 141  
      * @throws Exception 
 142  
      */
 143  
     protected boolean generateNewRequests(boolean initial, RouteContext context, RouteHelper routeHelper) throws WorkflowException, Exception {
 144  0
         return false;
 145  
     }
 146  
 
 147  
     /**
 148  
      * Activates any pending requests and returns whether there are outstanding blocking requests
 149  
      * @param context the RouteContext
 150  
      * @throws org.kuali.rice.kew.api.exception.WorkflowException if anything goes wrong...
 151  
      * @return whether there are outstanding blocking requests
 152  
      */
 153  
     protected boolean activateRequests(RouteContext routeContext) throws WorkflowException {
 154  0
         DocumentRouteHeaderValue document = routeContext.getDocument();
 155  0
         RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
 156  0
         if (routeContext.isSimulation()) {
 157  
             // this seems to indicate whether, when we are simulating, to activate requests...
 158  0
             if (routeContext.getActivationContext().isActivateRequests()) {
 159  0
                 activateRequests(routeContext, document, nodeInstance);
 160  
             }
 161  
             // if we are in simulation, don't block, just transition out
 162  0
             return false;
 163  
         } else {
 164  
             // activate any unactivated pending requests on this node instance
 165  0
             return activateRequests(routeContext, document, nodeInstance);
 166  
         }
 167  
     }
 168  
 
 169  
     /**
 170  
      * @return whether there are any pending requests at the given route node instance which are blocking (i.e., 'approve' or 'complete')
 171  
      */
 172  
     private static boolean blockingRequestsArePending(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
 173  
         // returns blocking requests that are *activated*
 174  0
         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
 175  0
         boolean blockingRequestsArePending = false;
 176  0
         for (ActionRequestValue request: requests) {
 177  0
             if (request.isApproveOrCompleteRequest()) {
 178  0
                 blockingRequestsArePending = true;
 179  0
                 break;
 180  
             }
 181  
         }
 182  0
         return blockingRequestsArePending;
 183  
     }
 184  
 
 185  
     /**
 186  
      * Activates the action requests that are pending at this routelevel of the document. The requests are processed by priority and then request ID.
 187  
      * It is implicit in the access that the requests are activated according to the route level above all.
 188  
      * <p>
 189  
      * FYI and acknowledgement requests do not cause the processing to stop. Only action requests for approval or completion cause the processing to
 190  
      * stop and then only for route level with a serialized activation policy. Only requests at the current document's current route level are activated.
 191  
      * Inactive requests at a lower level cause a routing exception.
 192  
      * <p>
 193  
      * Exception routing and adhoc routing are processed slightly differently.
 194  
      * 
 195  
      * 
 196  
      * @param context the RouteContext
 197  
      * @param document the document we are processing
 198  
      * @param nodeInstance the node instance we are processing
 199  
      * @return True if the any blocking actions requests (approve or complete) were activated.
 200  
      * @throws org.kuali.rice.kew.api.exception.ResourceUnavailableException
 201  
      * @throws org.kuali.rice.kew.api.exception.WorkflowException
 202  
      */
 203  
     private boolean activateRequests(RouteContext context, DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) throws WorkflowException {
 204  0
         MDC.put("docId", document.getDocumentId());
 205  0
         PerformanceLogger performanceLogger = new PerformanceLogger(document.getDocumentId());
 206  0
         List generatedActionItems = new ArrayList();
 207  0
         List<ActionRequestValue> requests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
 208  0
         if (context.isSimulation()) {
 209  0
             requests.addAll(context.getEngineState().getGeneratedRequests());
 210  
         }
 211  
         // this will sort higher priority requests to the front
 212  
         // blocking requests are higher priority, so all blocking requests will be
 213  
         // activated before non-blocking requests
 214  0
         Collections.sort(requests, new Utilities.PrioritySorter());
 215  0
         LOG.info("Pending Root Requests " + requests.size());
 216  0
         String activationType = nodeInstance.getRouteNode().getActivationType();
 217  0
         boolean isParallel = KewApiConstants.ROUTE_LEVEL_PARALLEL.equals(activationType);
 218  0
         boolean activatedApproveRequest = false;
 219  0
         for (Iterator iter = requests.iterator(); iter.hasNext();) {
 220  0
             if (activatedApproveRequest && !isParallel) {
 221  0
                 LOG.info("Already activated an apprve request and serial, so not activating any more");
 222  0
                 break;
 223  
             }
 224  0
             ActionRequestValue request = (ActionRequestValue) iter.next();
 225  0
             LOG.info("ActionRequestValue: " + request);
 226  0
             if (request.getParentActionRequest() != null || request.getNodeInstance() == null) {
 227  
                 // 1. disregard request if it's not a top-level request
 228  
                 // 2. disregard request if it's a "future" request and hasn't been attached to a node instance yet
 229  0
                 continue; 
 230  
             }
 231  0
             if (request.isActive()) {
 232  0
                 activatedApproveRequest = activatedApproveRequest || request.isApproveOrCompleteRequest();
 233  0
                 continue;
 234  
             }
 235  0
             logProcessingMessage(request);   
 236  0
             LOG.info("Activating request. " + request);
 237  0
             activatedApproveRequest = activateRequest(context, request, nodeInstance, generatedActionItems) || activatedApproveRequest;
 238  0
         }
 239  
         // now let's send notifications, since this code needs to be able to activate each request individually, we need
 240  
         // to collection all action items and then notify after all have been generated
 241  0
         if (!context.isSimulation()) {
 242  0
             KEWServiceLocator.getNotificationService().notify(generatedActionItems);
 243  
         }
 244  0
         performanceLogger.log("Time to activate requests.");
 245  0
         return activatedApproveRequest;
 246  
     }
 247  
 
 248  
     private boolean activateRequest(RouteContext context, ActionRequestValue actionRequest, RouteNodeInstance nodeInstance, List generatedActionItems) {
 249  0
         if (actionRequest.isRoleRequest()) {
 250  0
             List actionRequests = KEWServiceLocator.getActionRequestService().findPendingRootRequestsByDocIdAtRouteNode(actionRequest.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
 251  0
             for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
 252  0
                 ActionRequestValue siblingRequest = (ActionRequestValue) iterator.next();
 253  0
                 if (actionRequest.getRoleName().equals(siblingRequest.getRoleName())) {
 254  0
                     generatedActionItems.addAll(KEWServiceLocator.getActionRequestService().activateRequestNoNotification(siblingRequest, context.getActivationContext()));
 255  
                 }
 256  0
             }
 257  
         }
 258  0
         generatedActionItems.addAll(KEWServiceLocator.getActionRequestService().activateRequestNoNotification(actionRequest, context.getActivationContext()));
 259  0
         return actionRequest.isApproveOrCompleteRequest() && ! actionRequest.isDone();
 260  
     }
 261  
     
 262  
     protected void saveActionRequest(RouteContext context, ActionRequestValue actionRequest) {
 263  0
         if (!context.isSimulation()) {
 264  0
             KEWServiceLocator.getActionRequestService().saveActionRequest(actionRequest);
 265  
         } else {
 266  0
             actionRequest.setActionRequestId(String.valueOf(generatedRequestPriority++));
 267  0
             context.getEngineState().getGeneratedRequests().add(actionRequest);    
 268  
         }
 269  
         
 270  0
     }
 271  
     
 272  
     private void logProcessingMessage(ActionRequestValue request) {
 273  
         //if (LOG.isDebugEnabled()) {
 274  0
                 RouteNodeInstance nodeInstance = request.getNodeInstance();
 275  0
             StringBuffer buffer = new StringBuffer();
 276  0
             buffer.append("Processing AR: ").append(request.getActionRequestId()).append("\n");
 277  0
             buffer.append("AR Node Name: ").append(nodeInstance != null ? nodeInstance.getName() : "null").append("\n");
 278  0
             buffer.append("AR RouteLevel: ").append(request.getRouteLevel()).append("\n");
 279  0
             buffer.append("AR Request Code: ").append(request.getActionRequested()).append("\n");
 280  0
             buffer.append("AR Request priority: ").append(request.getPriority()).append("\n");
 281  0
             LOG.info(buffer);
 282  
         //}
 283  0
     }
 284  
             
 285  
 }