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