Coverage Report - org.kuali.rice.kew.engine.node.RequestsNode
 
Classes in this File Line Coverage Branch Coverage Complexity
RequestsNode
0%
0/120
0%
0/64
4.5
RequestsNode$FinalApproverContext
0%
0/3
N/A
4.5
 
 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 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.core.framework.parameter.ParameterService;
 26  
 import org.kuali.rice.core.framework.services.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  
  * A node which generates {@link ActionRequestValue} objects from a
 41  
  * {@link RouteModule}.
 42  
  * 
 43  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 44  
  */
 45  0
 public class RequestsNode extends RequestActivationNode {
 46  
 
 47  0
         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  0
             if (processCustom(routeContext, routeHelper)) {
 56  0
                 return super.process(routeContext, routeHelper);
 57  
             }
 58  0
             RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
 59  0
             boolean isInitial = nodeInstance.isInitial();
 60  0
             int currentIteration = 0;
 61  0
             List<ActionRequestValue> requestsGenerated = new ArrayList<ActionRequestValue>();
 62  
             while (true) {
 63  0
                 detectRunawayProcess(routeContext, currentIteration++);
 64  0
                 if (isInitial) {
 65  0
                     requestsGenerated = generateRequests(routeContext);
 66  
                     // need to set to false because we could iterate more than once here when the node is still in
 67  
                     // "initial" state
 68  0
                     isInitial = false;
 69  
                 }
 70  0
                 SimpleResult simpleResult = super.process(routeContext, routeHelper);
 71  0
                 if (simpleResult.isComplete()) {
 72  0
                     RouteModule routeModule = getRouteModule(routeContext);
 73  0
                     boolean moreRequestsAvailable = routeModule.isMoreRequestsAvailable(routeContext);
 74  0
                     if (!moreRequestsAvailable) {
 75  0
                         applyPoliciesOnExit(requestsGenerated, routeContext);
 76  0
                         return simpleResult;
 77  
                     } else {
 78  0
                         requestsGenerated = generateRequests(routeContext);
 79  
                     }
 80  0
                 } else {
 81  0
                     return simpleResult;
 82  
                 }
 83  0
             }
 84  0
                 } catch ( RouteManagerException ex ) {
 85  
                         // just re-throw - no need to wrap
 86  0
                         throw ex;
 87  0
                 } catch ( Exception e ) {
 88  0
                         LOG.error( "Caught exception routing", e );
 89  0
                         throw new RouteManagerException( e.getMessage(), e, routeContext );
 90  
                 }
 91  
         }
 92  
 
 93  
     protected List<ActionRequestValue> generateRequests(RouteContext routeContext) throws Exception {
 94  0
         DocumentRouteHeaderValue document = routeContext.getDocument();
 95  0
                 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
 96  0
                 RouteNode node = nodeInstance.getRouteNode();
 97  0
         if (LOG.isDebugEnabled()) {
 98  0
             LOG.debug("RouteHeader info inside routing loop\n" + ClassDumper.dumpFields(document));
 99  0
             LOG.debug("Looking for new actionRequests - routeLevel: " + node.getRouteNodeName());
 100  
         }
 101  0
         boolean suppressPolicyErrors = isSupressingPolicyErrors(routeContext);
 102  0
         List<ActionRequestValue> requests = getNewActionRequests(routeContext);
 103  
         // determine if we have any approve requests for FinalApprover checks
 104  0
         if (!suppressPolicyErrors) {
 105  0
             verifyFinalApprovalRequest(document, requests, nodeInstance, routeContext);
 106  
         }
 107  0
         return requests;
 108  
     }
 109  
 
 110  
     /**
 111  
      * Applies policies that should get checked prior to transitioning out of this node.  The default implementation of
 112  
      * this method checks the "mandatory" policy.
 113  
      *
 114  
      * @param requestsGenerated the requests generated on the current iteration of the route module
 115  
      * @param routeContext the current route context
 116  
      */
 117  
     protected void applyPoliciesOnExit(List<ActionRequestValue> requestsGenerated, RouteContext routeContext) {
 118  0
         DocumentRouteHeaderValue document = routeContext.getDocument();
 119  0
         RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
 120  0
         RouteNode node = nodeInstance.getRouteNode();
 121  
         // for mandatory routes, requests must be generated
 122  0
         if (node.isMandatory() && !isSupressingPolicyErrors(routeContext) && CollectionUtils.isEmpty(requestsGenerated)) {
 123  0
             List<ActionRequestValue> actionRequests = KEWServiceLocator.getActionRequestService().findRootRequestsByDocIdAtRouteNode(document.getDocumentId(), nodeInstance.getRouteNodeInstanceId());
 124  0
             if (actionRequests.isEmpty()) {
 125  0
                 LOG.warn("no requests generated for mandatory route - " + node.getRouteNodeName());
 126  0
                 throw new RouteManagerException(
 127  
                     "No requests generated for mandatory route " + node.getRouteNodeName() + ":" + node
 128  
                             .getRouteMethodName(), routeContext);
 129  
             }
 130  
         }
 131  0
     }
 132  
 
 133  
     /** Used by subclasses to replace the functioning of the process method.
 134  
          * 
 135  
          * @return <b>true</b> if custom processing was performed and the base implementation
 136  
          * in {@link #process(RouteContext, RouteHelper)} should be skipped.
 137  
          */
 138  
         protected boolean processCustom(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
 139  0
                 return false;
 140  
         }
 141  
         
 142  
         /**
 143  
          * Verifies the state of the action requests when a final approval action is involved.
 144  
          * 
 145  
          * Throws a RouteManagerException if actions were not generated correctly.
 146  
          */
 147  
         protected void verifyFinalApprovalRequest( DocumentRouteHeaderValue document, List<ActionRequestValue> requests, RouteNodeInstance nodeInstance, RouteContext routeContext ) throws RouteManagerException {
 148  0
                 boolean pastFinalApprover = isPastFinalApprover( document, nodeInstance );
 149  0
                 boolean hasApproveRequest = false;
 150  0
                 for ( ActionRequestValue actionRequest : requests ) {
 151  0
                         if ( actionRequest.isApproveOrCompleteRequest() ) {
 152  0
                                 hasApproveRequest = true;
 153  0
                                 break;
 154  
                         }
 155  
                 }
 156  
                 // if final approver route level and no approve request send to
 157  
                 // exception routing
 158  0
                 if ( nodeInstance.getRouteNode().getFinalApprovalInd().booleanValue() ) {
 159  
                         // we must have an approve request generated if final
 160  
                         // approver level.
 161  0
                         if ( !hasApproveRequest ) {
 162  0
                                 throw new RouteManagerException(
 163  
                                                 "No Approve Request generated after final approver", routeContext );
 164  
                         }
 165  0
                 } else if ( pastFinalApprover ) {
 166  
                         // we can't allow generation of approve requests after final
 167  
                         // approver. This guys going to exception routing.
 168  0
                         if ( hasApproveRequest ) {
 169  0
                                 throw new RouteManagerException(
 170  
                                                 "Approve Request generated after final approver", routeContext );
 171  
                         }
 172  
                 }
 173  0
         }
 174  
 
 175  
         public List<ActionRequestValue> getNewActionRequests(RouteContext context) throws Exception {
 176  0
                 RouteNodeInstance nodeInstance = context.getNodeInstance();
 177  0
                 String routeMethodName = nodeInstance.getRouteNode().getRouteMethodName();
 178  0
                 if ( LOG.isDebugEnabled() ) {
 179  0
                         LOG.debug( "Looking for action requests in " + routeMethodName + " : "
 180  
                                         + nodeInstance.getRouteNode().getRouteNodeName() );
 181  
                 }
 182  0
                 List<ActionRequestValue> newRequests = new ArrayList<ActionRequestValue>();
 183  
                 try {
 184  0
                         RouteModule routeModule = getRouteModule( context );
 185  0
                         List<ActionRequestValue> requests = routeModule.findActionRequests( context );
 186  0
                         for ( ActionRequestValue actionRequest : requests ) {
 187  0
                                 if ( LOG.isDebugEnabled() ) {
 188  0
                                         LOG.debug( "Request generated by RouteModule '" + routeModule + "' for node "
 189  
                                                         + nodeInstance + ":" + actionRequest );
 190  
                                 }
 191  0
                                 actionRequest = KEWServiceLocator.getActionRequestService()
 192  
                                                 .initializeActionRequestGraph( actionRequest, context.getDocument(),
 193  
                                                                 nodeInstance );
 194  0
                                 saveActionRequest( context, actionRequest );
 195  0
                                 newRequests.add( actionRequest );
 196  
                         }
 197  0
                 } catch ( WorkflowException ex ) {
 198  0
                         LOG.warn( "Caught WorkflowException during routing", ex );
 199  0
                         throw new RouteManagerException( ex, context );
 200  0
                 }
 201  0
                 return newRequests;
 202  
         }
 203  
 
 204  
         /**
 205  
          * Returns the RouteModule which should handle generating requests for this
 206  
          * RequestsNode.
 207  
          */
 208  
         protected RouteModule getRouteModule(RouteContext context) throws Exception {
 209  0
                 return KEWServiceLocator.getRouteModuleService().findRouteModule(
 210  
                                 context.getNodeInstance().getRouteNode() );
 211  
         }
 212  
 
 213  
         /**
 214  
          * Checks if the document has past the final approver node by walking
 215  
          * backward through the previous node instances. Ignores any previous nodes
 216  
          * that have been "revoked".
 217  
          */
 218  
         protected boolean isPastFinalApprover(DocumentRouteHeaderValue document,
 219  
                         RouteNodeInstance nodeInstance) {
 220  0
                 FinalApproverContext context = new FinalApproverContext();
 221  0
                 List revokedNodeInstances = KEWServiceLocator.getRouteNodeService()
 222  
                                 .getRevokedNodeInstances( document );
 223  0
                 Set revokedNodeInstanceIds = new HashSet();
 224  0
                 for ( Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
 225  0
                         RouteNodeInstance revokedNodeInstance = (RouteNodeInstance)iterator.next();
 226  0
                         revokedNodeInstanceIds.add( revokedNodeInstance.getRouteNodeInstanceId() );
 227  0
                 }
 228  0
                 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
 229  
                                 revokedNodeInstanceIds );
 230  0
                 return context.isPast;
 231  
         }
 232  
 
 233  
         protected void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context,
 234  
                         Set revokedNodeInstanceIds) {
 235  0
                 if ( previousNodeInstances != null && !previousNodeInstances.isEmpty() ) {
 236  0
                         for ( Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext(); ) {
 237  0
                                 if ( context.isPast ) {
 238  0
                                         return;
 239  
                                 }
 240  0
                                 RouteNodeInstance nodeInstance = (RouteNodeInstance)iterator.next();
 241  0
                                 if ( context.inspected.contains( getKey( nodeInstance ) ) ) {
 242  0
                                         continue;
 243  
                                 } else {
 244  0
                                         context.inspected.add( getKey( nodeInstance ) );
 245  
                                 }
 246  0
                                 if ( Boolean.TRUE.equals( nodeInstance.getRouteNode().getFinalApprovalInd() ) ) {
 247  
                                         // if the node instance has been revoked (by a Return To
 248  
                                         // Previous action for example)
 249  
                                         // then we don't want to consider that node when we
 250  
                                         // determine if we are past final
 251  
                                         // approval or not
 252  0
                                         if ( !revokedNodeInstanceIds.contains( nodeInstance.getRouteNodeInstanceId() ) ) {
 253  0
                                                 context.isPast = true;
 254  
                                         }
 255  0
                                         return;
 256  
                                 }
 257  0
                                 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
 258  
                                                 revokedNodeInstanceIds );
 259  0
                         }
 260  
                 }
 261  0
         }
 262  
 
 263  
         /**
 264  
          * The method will get a key value which can be used for comparison
 265  
          * purposes. If the node instance has a primary key value, it will be
 266  
          * returned. However, if the node instance has not been saved to the
 267  
          * database (i.e. during a simulation) this method will return the node
 268  
          * instance passed in.
 269  
          */
 270  
         protected Object getKey(RouteNodeInstance nodeInstance) {
 271  0
                 String id = nodeInstance.getRouteNodeInstanceId();
 272  0
                 return (id != null ? (Object)id : (Object)nodeInstance);
 273  
         }
 274  
 
 275  
     protected void detectRunawayProcess(RouteContext routeContext, int currentIteration) throws NumberFormatException {
 276  0
             String maxNodesConstant = getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.MAX_NODES_BEFORE_RUNAWAY_PROCESS);
 277  0
             int maxNodes = (org.apache.commons.lang.StringUtils.isEmpty(maxNodesConstant)) ? 50 : Integer.valueOf(maxNodesConstant);
 278  0
             if (currentIteration > maxNodes) {
 279  0
             throw new RouteManagerException("Detected a runaway process within RequestsNode for document with id '" + routeContext.getDocument().getDocumentId() + "' after " + currentIteration + " iterations.");
 280  
         }
 281  0
         }
 282  
 
 283  0
         protected class FinalApproverContext {
 284  0
                 public Set inspected = new HashSet();
 285  
 
 286  0
                 public boolean isPast = false;
 287  
         }
 288  
 
 289  
         public static boolean isSupressingPolicyErrors(RouteContext routeContext) {
 290  0
                 Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(
 291  
                                 SUPPRESS_POLICY_ERRORS_KEY );
 292  0
                 if ( suppressPolicyErrors == null || !suppressPolicyErrors ) {
 293  0
                         return false;
 294  
                 }
 295  0
                 return true;
 296  
         }
 297  
 
 298  
         @SuppressWarnings("unchecked")
 299  
         public static void setSupressPolicyErrors(RouteContext routeContext) {
 300  0
                 routeContext.getParameters().put( SUPPRESS_POLICY_ERRORS_KEY, Boolean.TRUE );
 301  0
         }
 302  
 
 303  
     protected ParameterService getParameterService() {
 304  0
                 return CoreFrameworkServiceLocator.getParameterService();
 305  
         }
 306  
 }