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