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