Coverage Report - org.kuali.rice.kew.engine.node.RoleNode
 
Classes in this File Line Coverage Branch Coverage Complexity
RoleNode
0%
0/92
0%
0/82
8.857
RoleNode$RoleRequestSorter
0%
0/13
0%
0/14
8.857
 
 1  
 /*
 2  
  * Copyright 2007-2008 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.Comparator;
 21  
 import java.util.HashMap;
 22  
 import java.util.List;
 23  
 import java.util.Map;
 24  
 
 25  
 import org.apache.commons.lang.StringUtils;
 26  
 import org.apache.log4j.MDC;
 27  
 import org.kuali.rice.kew.actionitem.ActionItem;
 28  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 29  
 import org.kuali.rice.kew.doctype.bo.DocumentType;
 30  
 import org.kuali.rice.kew.engine.RouteContext;
 31  
 import org.kuali.rice.kew.engine.RouteHelper;
 32  
 import org.kuali.rice.kew.exception.ResourceUnavailableException;
 33  
 import org.kuali.rice.kew.exception.RouteManagerException;
 34  
 import org.kuali.rice.kew.exception.WorkflowException;
 35  
 import org.kuali.rice.kew.role.RoleRouteModule;
 36  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 37  
 import org.kuali.rice.kew.routemodule.RouteModule;
 38  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 39  
 import org.kuali.rice.kew.util.ClassDumper;
 40  
 import org.kuali.rice.kew.util.KEWConstants;
 41  
 import org.kuali.rice.kew.util.PerformanceLogger;
 42  
 import org.kuali.rice.kim.bo.impl.KimAttributes;
 43  
 import org.kuali.rice.kim.bo.role.dto.KimResponsibilityInfo;
 44  
 import org.kuali.rice.kim.service.KIMServiceLocator;
 45  
 import org.kuali.rice.kns.util.KNSConstants;
 46  
 
 47  
 /**
 48  
  * A node implementation which provides integration with KIM Roles for routing.
 49  
  * Essentially extends RequestsNode and provides a custom RouteModule
 50  
  * implementation.
 51  
  * 
 52  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 53  
  * 
 54  
  */
 55  0
 public class RoleNode extends RequestsNode {
 56  
 
 57  0
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
 58  
                         .getLogger( RoleNode.class );
 59  
 
 60  
         @Override
 61  
         protected RouteModule getRouteModule(RouteContext context) throws Exception {
 62  0
                 return new RoleRouteModule();
 63  
         }
 64  
         
 65  
         /**
 66  
          * @see org.kuali.rice.kew.engine.node.RequestsNode#processCustom(org.kuali.rice.kew.engine.RouteContext, org.kuali.rice.kew.engine.RouteHelper)
 67  
          */
 68  
         @Override
 69  
         protected boolean processCustom(RouteContext routeContext, RouteHelper routeHelper) throws Exception {
 70  0
                 DocumentRouteHeaderValue document = routeContext.getDocument();
 71  0
                 RouteNodeInstance nodeInstance = routeContext.getNodeInstance();
 72  0
                 RouteNode node = nodeInstance.getRouteNode();
 73  
                 // while no routable actions are activated and there are more
 74  
                 // routeLevels to process
 75  0
                 if ( nodeInstance.isInitial() ) {
 76  0
                         if ( LOG.isDebugEnabled() ) {
 77  0
                                 LOG.debug( "RouteHeader info inside routing loop\n"
 78  
                                                 + ClassDumper.dumpFields( routeContext.getDocument() ) );
 79  0
                                 LOG.debug( "Looking for new actionRequests - routeLevel: "
 80  
                                                 + node.getRouteNodeName() );
 81  
                         }
 82  0
                         boolean suppressPolicyErrors = isSupressingPolicyErrors( routeContext );
 83  0
                         List<ActionRequestValue> requests = getNewActionRequests( routeContext );
 84  
 // Debugging code to force an empty action request                                
 85  
 //                                if ( document.getDocumentType().getName().equals( "SACC" ) ) {
 86  
 //                                        LOG.fatal( "DEBUGGING CODE IN PLACE - SACC DOCUMENT ACTION REQUESTS CLEARED" );
 87  
 //                                        requests.clear();
 88  
 //                                }
 89  
                         // for mandatory routes, requests must be generated
 90  0
                         if ( requests.isEmpty() && !suppressPolicyErrors) {
 91  0
                                 KimResponsibilityInfo resp = getFirstResponsibilityWithMandatoryRouteFlag( document, node );
 92  0
                                 if ( resp != null ) {
 93  0
                                         throw new RouteManagerException( "No requests generated for KIM Responsibility-based mandatory route.\n" +
 94  
                                                         "Document Id:    " + document.getRouteHeaderId() + "\n" +
 95  
                                                         "DocumentType:   " + document.getDocumentType().getName() + "\n" +
 96  
                                                         "Route Node:     " + node.getRouteNodeName() + "\n" + 
 97  
                                                         "Responsibility: " + resp,
 98  
                                                         routeContext );
 99  
                                 }
 100  
                         }
 101  
                         // determine if we have any approve requests for FinalApprover
 102  
                         // checks
 103  0
                         if ( !suppressPolicyErrors ) {                                
 104  0
                                 verifyFinalApprovalRequest( document, requests, nodeInstance, routeContext );
 105  
                         }
 106  
                 }
 107  0
                 return true; // to indicate custom processing performed
 108  
         }
 109  
         
 110  
         /**
 111  
          * Checks for any mandatory route responsibilities for the given document type and node.
 112  
          * 
 113  
          * Stops once it finds a responsibility for the document and node.
 114  
          */        
 115  
         protected KimResponsibilityInfo getFirstResponsibilityWithMandatoryRouteFlag( DocumentRouteHeaderValue document, RouteNode node ) {
 116  
                 // iterate over the document hierarchy
 117  
                 // gather responsibilities - merge based on route level
 118  
                 //Map<String,Boolean>
 119  0
                 Map<String,String> searchCriteria = new HashMap<String,String>();
 120  0
                 searchCriteria.put("template.namespaceCode", KNSConstants.KUALI_RICE_WORKFLOW_NAMESPACE);
 121  0
                 searchCriteria.put("template.name", KEWConstants.DEFAULT_RESPONSIBILITY_TEMPLATE_NAME);
 122  0
                 searchCriteria.put("active", "Y");
 123  0
                 DocumentType docType = document.getDocumentType();
 124  0
                 while ( docType != null ) {
 125  0
                         searchCriteria.put("detailCriteria", getDetailCriteriaString( docType.getName(), node.getRouteNodeName() ) );
 126  
                         try {
 127  0
                                 List<? extends KimResponsibilityInfo> responsibilities = KIMServiceLocator.getResponsibilityService().lookupResponsibilityInfo( searchCriteria, false );
 128  
                                 // once we find a responsibility, stop, since this overrides any parent 
 129  
                                 // responsibilities for this node
 130  0
                                 if ( !responsibilities.isEmpty() ) {
 131  
                                         // if any has required=true - return true
 132  0
                                         for ( KimResponsibilityInfo resp : responsibilities ) {
 133  0
                                                 if ( Boolean.parseBoolean( resp.getDetails().get( KimAttributes.REQUIRED ) ) ) {
 134  0
                                                         return resp;
 135  
                                                 }
 136  
                                         }
 137  0
                                         return null;
 138  
                                 }
 139  0
                         } catch ( Exception ex ) {
 140  0
                                 LOG.error( "Problem looking up responsibilities to check mandatory route.  Criteria: " +searchCriteria, ex );
 141  0
                                 return null;
 142  0
                         }
 143  0
                         docType = docType.getParentDocType();
 144  
                 }
 145  
 
 146  0
                 return null;
 147  
         }
 148  
 
 149  
         protected String getDetailCriteriaString( String documentTypeName, String routeNodeName ) {
 150  0
                 return KimAttributes.DOCUMENT_TYPE_NAME+"="+documentTypeName
 151  
                                 + ","
 152  
                                 + KimAttributes.ROUTE_NODE_NAME+"="+routeNodeName
 153  
 //                                + ","
 154  
 //                                + KimAttributes.REQUIRED+"=true"
 155  
                                 ;
 156  
         }
 157  
         
 158  
         /**
 159  
          * Activates the action requests that are pending at this routelevel of the
 160  
          * document. The requests are processed by priority and then request ID. It
 161  
          * is implicit in the access that the requests are activated according to
 162  
          * the route level above all.
 163  
          * <p>
 164  
          * FYI and acknowledgment requests do not cause the processing to stop. Only
 165  
          * action requests for approval or completion cause the processing to stop
 166  
          * and then only for route level with a serialized activation policy. Only
 167  
          * requests at the current document's current route level are activated.
 168  
          * Inactive requests at a lower level cause a routing exception.
 169  
          * <p>
 170  
          * Exception routing and adhoc routing are processed slightly differently.
 171  
          * 
 172  
          * @return True if the any approval actions were activated.
 173  
          * @throws ResourceUnavailableException
 174  
          * @throws WorkflowException
 175  
          */
 176  
         @SuppressWarnings("unchecked")
 177  
         public boolean activateRequests(RouteContext context, DocumentRouteHeaderValue document,
 178  
                         RouteNodeInstance nodeInstance) throws WorkflowException {
 179  0
                 MDC.put( "docId", document.getRouteHeaderId() );
 180  0
                 PerformanceLogger performanceLogger = new PerformanceLogger( document.getRouteHeaderId() );
 181  0
                 List<ActionItem> generatedActionItems = new ArrayList<ActionItem>();
 182  0
                 List<ActionRequestValue> requests = new ArrayList<ActionRequestValue>();
 183  0
                 if ( context.isSimulation() ) {
 184  0
                         for ( ActionRequestValue ar : context.getDocument().getActionRequests() ) {
 185  
                                 // logic check below duplicates behavior of the
 186  
                                 // ActionRequestService.findPendingRootRequestsByDocIdAtRouteNode(routeHeaderId,
 187  
                                 // routeNodeInstanceId) method
 188  0
                                 if ( ar.getCurrentIndicator()
 189  
                                                 && (KEWConstants.ACTION_REQUEST_INITIALIZED.equals( ar.getStatus() ) || KEWConstants.ACTION_REQUEST_ACTIVATED
 190  
                                                                 .equals( ar.getStatus() ))
 191  
                                                 && ar.getNodeInstance().getRouteNodeInstanceId().equals(
 192  
                                                                 nodeInstance.getRouteNodeInstanceId() )
 193  
                                                 && ar.getParentActionRequest() == null ) {
 194  0
                                         requests.add( ar );
 195  
                                 }
 196  
                         }
 197  0
                         requests.addAll( context.getEngineState().getGeneratedRequests() );
 198  
                 } else {
 199  0
                         requests = KEWServiceLocator.getActionRequestService()
 200  
                                         .findPendingRootRequestsByDocIdAtRouteNode( document.getRouteHeaderId(),
 201  
                                                         nodeInstance.getRouteNodeInstanceId() );
 202  
                 }
 203  0
                 if ( LOG.isDebugEnabled() ) {
 204  0
                         LOG.debug( "Pending Root Requests " + requests.size() );
 205  
                 }
 206  0
                 boolean requestActivated = activateRequestsCustom( context, requests, generatedActionItems,
 207  
                                 document, nodeInstance );
 208  
                 // now let's send notifications, since this code needs to be able to
 209  
                 // activate each request individually, we need
 210  
                 // to collection all action items and then notify after all have been
 211  
                 // generated
 212  0
         notify(context, generatedActionItems, nodeInstance);
 213  
 
 214  0
         performanceLogger.log( "Time to activate requests." );
 215  0
                 return requestActivated;
 216  
         }
 217  
         
 218  0
     protected static class RoleRequestSorter implements Comparator<ActionRequestValue> {
 219  
         public int compare(ActionRequestValue ar1, ActionRequestValue ar2) {
 220  0
                 int result = 0;
 221  
                 // compare descriptions (only if both not null)
 222  0
                 if ( ar1.getResponsibilityDesc() != null && ar2.getResponsibilityDesc() != null ) {
 223  0
                         result = ar1.getResponsibilityDesc().compareTo( ar2.getResponsibilityDesc() );
 224  
                 }
 225  0
             if ( result != 0 ) return result;
 226  
                 // compare priority
 227  0
             result = ar1.getPriority().compareTo(ar2.getPriority());
 228  0
             if ( result != 0 ) return result;
 229  
             // compare action request type
 230  0
             result = ActionRequestValue.compareActionCode(ar1.getActionRequested(), ar2.getActionRequested(), true);
 231  0
             if ( result != 0 ) return result;
 232  
             // compare action request ID
 233  0
             if ( (ar1.getActionRequestId() != null) && (ar2.getActionRequestId() != null) ) {
 234  0
                 result = ar1.getActionRequestId().compareTo(ar2.getActionRequestId());
 235  
             } else {
 236  
                 // if even one action request id is null at this point return then the two are equal
 237  0
                 result = 0;
 238  
             }
 239  0
             return result;
 240  
         }
 241  
     }
 242  0
     protected static final Comparator<ActionRequestValue> ROLE_REQUEST_SORTER = new RoleRequestSorter();
 243  
 
 244  
         
 245  
         protected boolean activateRequestsCustom(RouteContext context,
 246  
                         List<ActionRequestValue> requests, List<ActionItem> generatedActionItems,
 247  
                         DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance)
 248  
                         throws WorkflowException {
 249  0
                 Collections.sort( requests, ROLE_REQUEST_SORTER );
 250  0
                 String activationType = nodeInstance.getRouteNode().getActivationType();
 251  0
                 boolean isParallel = KEWConstants.ROUTE_LEVEL_PARALLEL.equals( activationType );
 252  0
                 boolean requestActivated = false;
 253  0
                 String groupToActivate = null;
 254  0
                 Integer priorityToActivate = null;
 255  0
                 for ( ActionRequestValue request : requests ) {
 256  
                         // if a request has already been activated and we are not parallel routing
 257  
                         // or in the simulator, break out of the loop and exit
 258  0
                         if ( requestActivated
 259  
                                         && !isParallel
 260  
                                         && (!context.isSimulation() || !context.getActivationContext()
 261  
                                                         .isActivateRequests()) ) {
 262  0
                                 break;
 263  
                         }
 264  0
                         if ( request.getParentActionRequest() != null || request.getNodeInstance() == null ) {
 265  
                                 // 1. disregard request if it's not a top-level request
 266  
                                 // 2. disregard request if it's a "future" request and hasn't
 267  
                                 // been attached to a node instance yet
 268  0
                                 continue;
 269  
                         }
 270  0
                         if ( request.isApproveOrCompleteRequest() ) {
 271  0
                                 boolean thisRequestActivated = false;
 272  
                                 // capture the priority and grouping information for this request
 273  
                                 // We only need this for Approval requests since FYI and ACK requests are non-blocking
 274  0
                                 if ( priorityToActivate == null ) {
 275  0
                                          priorityToActivate = request.getPriority();
 276  
                                 }
 277  0
                                 if ( groupToActivate == null ) {
 278  0
                                         groupToActivate = request.getResponsibilityDesc();
 279  
                                 }
 280  
                                 // check that the given request is found in the current group to activate
 281  
                                 // check priority and grouping from the request (stored in the responsibility description)
 282  0
                                 if ( StringUtils.equals( groupToActivate, request.getResponsibilityDesc() )
 283  
                                                 && (
 284  
                                                                 (priorityToActivate != null && request.getPriority() != null && priorityToActivate.equals(request.getPriority()))
 285  
                                                         ||  (priorityToActivate == null && request.getPriority() == null)
 286  
                                                         )
 287  
                                                 ) {
 288  
                                         // if the request is already active, note that we have an active request
 289  
                                         // and move on to the next request
 290  0
                                         if ( request.isActive() ) {
 291  0
                                                 requestActivated = true;
 292  0
                                                 continue;
 293  
                                         }
 294  0
                                         logProcessingMessage( request );
 295  0
                                         if ( LOG.isDebugEnabled() ) {
 296  0
                                                 LOG.debug( "Activating request: " + request );
 297  
                                         }
 298  
                                         // this returns true if any requests were activated as a result of this call
 299  0
                                         thisRequestActivated = activateRequest( context, request, nodeInstance,
 300  
                                                         generatedActionItems );
 301  0
                                         requestActivated |= thisRequestActivated;
 302  
                                 }
 303  
                                 // if this request was not activated and no request has been activated thus far
 304  
                                 // then clear out the grouping and priority filters
 305  
                                 // as this represents a case where the person with the earlier priority
 306  
                                 // did not need to approve for this route level due to taking
 307  
                                 // a prior action
 308  0
                                 if ( !thisRequestActivated && !requestActivated ) {
 309  0
                                         priorityToActivate = null;
 310  0
                                         groupToActivate = null;
 311  
                                 }
 312  0
                         } else {
 313  0
                                 logProcessingMessage( request );
 314  0
                                 if ( LOG.isDebugEnabled() ) {
 315  0
                                         LOG.debug( "Activating request: " + request );
 316  
                                 }
 317  0
                                 requestActivated = activateRequest( context, request, nodeInstance,
 318  
                                                 generatedActionItems )
 319  
                                                 || requestActivated;
 320  
                         }
 321  
                 }
 322  0
                 return requestActivated;
 323  
         }
 324  
 }