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