Coverage Report - org.kuali.rice.kew.engine.node.RequestsNode
 
Classes in this File Line Coverage Branch Coverage Complexity
RequestsNode
0%
0/89
0%
0/54
5
RequestsNode$FinalApproverContext
0%
0/3
N/A
5
 
 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 java.util.ArrayList;
 20  
 import java.util.HashSet;
 21  
 import java.util.Iterator;
 22  
 import java.util.List;
 23  
 import java.util.Set;
 24  
 
 25  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 26  
 import org.kuali.rice.kew.engine.RouteContext;
 27  
 import org.kuali.rice.kew.engine.RouteHelper;
 28  
 import org.kuali.rice.kew.exception.ResourceUnavailableException;
 29  
 import org.kuali.rice.kew.exception.RouteManagerException;
 30  
 import org.kuali.rice.kew.exception.WorkflowException;
 31  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 32  
 import org.kuali.rice.kew.routemodule.RouteModule;
 33  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 34  
 import org.kuali.rice.kew.util.ClassDumper;
 35  
 
 36  
 /**
 37  
  * A node which generates {@link ActionRequestValue} objects from a
 38  
  * {@link RouteModule}.
 39  
  * 
 40  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 41  
  */
 42  0
 public class RequestsNode extends RequestActivationNode {
 43  
 
 44  0
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
 45  
                         .getLogger( RequestsNode.class );
 46  
 
 47  
         protected static final String SUPPRESS_POLICY_ERRORS_KEY = "_suppressPolicyErrorsRequestActivationNode";
 48  
 
 49  
         public final SimpleResult process(RouteContext routeContext, RouteHelper routeHelper)
 50  
                         throws Exception {
 51  
                 try {
 52  0
                         if ( !processCustom( routeContext, routeHelper ) ) {
 53  0
                                 DocumentRouteHeaderValue document = routeContext.getDocument();
 54  0
                                 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
 55  0
                                 RouteNode node = nodeInstance.getRouteNode();
 56  
                                 // refreshSearchableAttributes(routeContext);
 57  
                                 // while no routable actions are activated and there are more
 58  
                                 // routeLevels to process
 59  0
                                 if ( nodeInstance.isInitial() ) {
 60  0
                                         if ( LOG.isDebugEnabled() ) {
 61  0
                                                 LOG.debug( "RouteHeader info inside routing loop\n"
 62  
                                                                 + ClassDumper.dumpFields( document ) );
 63  0
                                                 LOG.debug( "Looking for new actionRequests - routeLevel: "
 64  
                                                                 + node.getRouteNodeName() );
 65  
                                         }
 66  0
                                         boolean suppressPolicyErrors = isSupressingPolicyErrors( routeContext );
 67  0
                                         List<ActionRequestValue> requests = getNewActionRequests( routeContext );
 68  
                                         // for mandatory routes, requests must be generated
 69  0
                                         if ( (requests.isEmpty()) && node.getMandatoryRouteInd().booleanValue()
 70  
                                                         && !suppressPolicyErrors ) {
 71  0
                                                 LOG.warn( "no requests generated for mandatory route - "
 72  
                                                                 + node.getRouteNodeName() );
 73  0
                                                 throw new RouteManagerException( "No requests generated for mandatory route "
 74  
                                                                 + node.getRouteNodeName() + ":" + node.getRouteMethodName(),
 75  
                                                                 routeContext );
 76  
                                         }
 77  
                                         // determine if we have any approve requests for FinalApprover
 78  
                                         // checks
 79  0
                                         if ( !suppressPolicyErrors ) {                                
 80  0
                                                 verifyFinalApprovalRequest( document, requests, nodeInstance, routeContext );
 81  
                                         }
 82  
                                 }
 83  
                         }
 84  0
                         return super.process( routeContext, routeHelper );
 85  0
                 } catch ( RouteManagerException ex ) {
 86  
                         // just re-throw - no need to wrap
 87  0
                         throw ex;
 88  0
                 } catch ( Exception e ) {
 89  0
                         LOG.error( "Caught exception routing", e );
 90  0
                         throw new RouteManagerException( e.getMessage(), e, routeContext );
 91  
                 }
 92  
         }
 93  
 
 94  
         /** Used by subclasses to replace the functioning of the process method.
 95  
          * 
 96  
          * @return <b>true</b> if custom processing was performed and the base implementation
 97  
          * in {@link #process(RouteContext, RouteHelper)} should be skipped.
 98  
          */
 99  
         protected boolean processCustom(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
 100  0
                 return false;
 101  
         }
 102  
         
 103  
         /**
 104  
          * Verifies the state of the action requests when a final approval action is involved.
 105  
          * 
 106  
          * Throws a RouteManagerException if actions were not generated correctly.
 107  
          */
 108  
         protected void verifyFinalApprovalRequest( DocumentRouteHeaderValue document, List<ActionRequestValue> requests, RouteNodeInstance nodeInstance, RouteContext routeContext ) throws RouteManagerException {
 109  0
                 boolean pastFinalApprover = isPastFinalApprover( document, nodeInstance );
 110  0
                 boolean hasApproveRequest = false;
 111  0
                 for ( ActionRequestValue actionRequest : requests ) {
 112  0
                         if ( actionRequest.isApproveOrCompleteRequest() ) {
 113  0
                                 hasApproveRequest = true;
 114  0
                                 break;
 115  
                         }
 116  
                 }
 117  
                 // if final approver route level and no approve request send to
 118  
                 // exception routing
 119  0
                 if ( nodeInstance.getRouteNode().getFinalApprovalInd().booleanValue() ) {
 120  
                         // we must have an approve request generated if final
 121  
                         // approver level.
 122  0
                         if ( !hasApproveRequest ) {
 123  0
                                 throw new RouteManagerException(
 124  
                                                 "No Approve Request generated after final approver", routeContext );
 125  
                         }
 126  0
                 } else if ( pastFinalApprover ) {
 127  
                         // we can't allow generation of approve requests after final
 128  
                         // approver. This guys going to exception routing.
 129  0
                         if ( hasApproveRequest ) {
 130  0
                                 throw new RouteManagerException(
 131  
                                                 "Approve Request generated after final approver", routeContext );
 132  
                         }
 133  
                 }
 134  0
         }
 135  
 
 136  
 
 137  
         /**
 138  
          * @param routeLevel
 139  
          *            Route level for which the action requests will be generated
 140  
          * @param routeHeader
 141  
          *            route header for which the action requests are generated
 142  
          * @param saveFlag
 143  
          *            if true the new action requests will be saved, if false they
 144  
          *            are not written to the db
 145  
          * @return List of ActionRequests - NOTE they are only written to DB if
 146  
          *         saveFlag is set
 147  
          * @throws WorkflowException
 148  
          * @throws ResourceUnavailableException
 149  
          */
 150  
         public List<ActionRequestValue> getNewActionRequests(RouteContext context) throws Exception {
 151  0
                 RouteNodeInstance nodeInstance = context.getNodeInstance();
 152  0
                 String routeMethodName = nodeInstance.getRouteNode().getRouteMethodName();
 153  0
                 if ( LOG.isDebugEnabled() ) {
 154  0
                         LOG.debug( "Looking for action requests in " + routeMethodName + " : "
 155  
                                         + nodeInstance.getRouteNode().getRouteNodeName() );
 156  
                 }
 157  0
                 List<ActionRequestValue> newRequests = new ArrayList<ActionRequestValue>();
 158  
                 try {
 159  0
                         RouteModule routeModule = getRouteModule( context );
 160  0
                         List<ActionRequestValue> requests = routeModule.findActionRequests( context );
 161  0
                         for ( ActionRequestValue actionRequest : requests ) {
 162  0
                                 if ( LOG.isDebugEnabled() ) {
 163  0
                                         LOG.debug( "Request generated by RouteModule '" + routeModule + "' for node "
 164  
                                                         + nodeInstance + ":" + actionRequest );
 165  
                                 }
 166  0
                                 actionRequest = KEWServiceLocator.getActionRequestService()
 167  
                                                 .initializeActionRequestGraph( actionRequest, context.getDocument(),
 168  
                                                                 nodeInstance );
 169  0
                                 saveActionRequest( context, actionRequest );
 170  0
                                 newRequests.add( actionRequest );
 171  
                         }
 172  0
                 } catch ( WorkflowException ex ) {
 173  0
                         LOG.warn( "Caught WorkflowException during routing", ex );
 174  0
                         throw new RouteManagerException( ex, context );
 175  0
                 }
 176  0
                 return newRequests;
 177  
         }
 178  
 
 179  
         /**
 180  
          * Returns the RouteModule which should handle generating requests for this
 181  
          * RequestsNode.
 182  
          */
 183  
         protected RouteModule getRouteModule(RouteContext context) throws Exception {
 184  0
                 return KEWServiceLocator.getRouteModuleService().findRouteModule(
 185  
                                 context.getNodeInstance().getRouteNode() );
 186  
         }
 187  
 
 188  
         /**
 189  
          * Checks if the document has past the final approver node by walking
 190  
          * backward through the previous node instances. Ignores any previous nodes
 191  
          * that have been "revoked".
 192  
          */
 193  
         protected boolean isPastFinalApprover(DocumentRouteHeaderValue document,
 194  
                         RouteNodeInstance nodeInstance) {
 195  0
                 FinalApproverContext context = new FinalApproverContext();
 196  0
                 List revokedNodeInstances = KEWServiceLocator.getRouteNodeService()
 197  
                                 .getRevokedNodeInstances( document );
 198  0
                 Set revokedNodeInstanceIds = new HashSet();
 199  0
                 for ( Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
 200  0
                         RouteNodeInstance revokedNodeInstance = (RouteNodeInstance)iterator.next();
 201  0
                         revokedNodeInstanceIds.add( revokedNodeInstance.getRouteNodeInstanceId() );
 202  0
                 }
 203  0
                 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
 204  
                                 revokedNodeInstanceIds );
 205  0
                 return context.isPast;
 206  
         }
 207  
 
 208  
         protected void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context,
 209  
                         Set revokedNodeInstanceIds) {
 210  0
                 if ( previousNodeInstances != null && !previousNodeInstances.isEmpty() ) {
 211  0
                         for ( Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext(); ) {
 212  0
                                 if ( context.isPast ) {
 213  0
                                         return;
 214  
                                 }
 215  0
                                 RouteNodeInstance nodeInstance = (RouteNodeInstance)iterator.next();
 216  0
                                 if ( context.inspected.contains( getKey( nodeInstance ) ) ) {
 217  0
                                         continue;
 218  
                                 } else {
 219  0
                                         context.inspected.add( getKey( nodeInstance ) );
 220  
                                 }
 221  0
                                 if ( Boolean.TRUE.equals( nodeInstance.getRouteNode().getFinalApprovalInd() ) ) {
 222  
                                         // if the node instance has been revoked (by a Return To
 223  
                                         // Previous action for example)
 224  
                                         // then we don't want to consider that node when we
 225  
                                         // determine if we are past final
 226  
                                         // approval or not
 227  0
                                         if ( !revokedNodeInstanceIds.contains( nodeInstance.getRouteNodeInstanceId() ) ) {
 228  0
                                                 context.isPast = true;
 229  
                                         }
 230  0
                                         return;
 231  
                                 }
 232  0
                                 isPastFinalApprover( nodeInstance.getPreviousNodeInstances(), context,
 233  
                                                 revokedNodeInstanceIds );
 234  0
                         }
 235  
                 }
 236  0
         }
 237  
 
 238  
         /**
 239  
          * The method will get a key value which can be used for comparison
 240  
          * purposes. If the node instance has a primary key value, it will be
 241  
          * returned. However, if the node instance has not been saved to the
 242  
          * database (i.e. during a simulation) this method will return the node
 243  
          * instance passed in.
 244  
          */
 245  
         protected Object getKey(RouteNodeInstance nodeInstance) {
 246  0
                 Long id = nodeInstance.getRouteNodeInstanceId();
 247  0
                 return (id != null ? (Object)id : (Object)nodeInstance);
 248  
         }
 249  
 
 250  0
         protected class FinalApproverContext {
 251  0
                 public Set inspected = new HashSet();
 252  
 
 253  0
                 public boolean isPast = false;
 254  
         }
 255  
 
 256  
         public static boolean isSupressingPolicyErrors(RouteContext routeContext) {
 257  0
                 Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(
 258  
                                 SUPPRESS_POLICY_ERRORS_KEY );
 259  0
                 if ( suppressPolicyErrors == null || !suppressPolicyErrors ) {
 260  0
                         return false;
 261  
                 }
 262  0
                 return true;
 263  
         }
 264  
 
 265  
         @SuppressWarnings("unchecked")
 266  
         public static void setSupressPolicyErrors(RouteContext routeContext) {
 267  0
                 routeContext.getParameters().put( SUPPRESS_POLICY_ERRORS_KEY, Boolean.TRUE );
 268  0
         }
 269  
 }