Coverage Report - org.kuali.rice.kew.engine.node.KRAMetaRuleNode
 
Classes in this File Line Coverage Branch Coverage Complexity
KRAMetaRuleNode
0%
0/91
0%
0/60
5.111
KRAMetaRuleNode$1
N/A
N/A
5.111
KRAMetaRuleNode$FinalApproverContext
0%
0/3
N/A
5.111
 
 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 java.util.ArrayList;
 19  
 import java.util.HashSet;
 20  
 import java.util.Iterator;
 21  
 import java.util.List;
 22  
 import java.util.Map;
 23  
 import java.util.Set;
 24  
 
 25  
 import org.apache.commons.lang.StringUtils;
 26  
 import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
 27  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 28  
 import org.kuali.rice.kew.api.exception.WorkflowException;
 29  
 import org.kuali.rice.kew.api.rule.Rule;
 30  
 import org.kuali.rice.kew.engine.RouteContext;
 31  
 import org.kuali.rice.kew.engine.RouteHelper;
 32  
 import org.kuali.rice.kew.exception.RouteManagerException;
 33  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 34  
 import org.kuali.rice.kew.rule.FlexRM;
 35  
 import org.kuali.rice.kew.rule.KRAMetaRuleEngine;
 36  
 import org.kuali.rice.kew.rule.RuleExpressionResult;
 37  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 38  
 import org.kuali.rice.kew.util.Utilities;
 39  
 
 40  
 
 41  
 /**
 42  
  * Node that implements a KRAMetaRule, with multiple request/response phases
 43  
  *
 44  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 45  
  */
 46  0
 public class KRAMetaRuleNode extends IteratedRequestActivationNode {
 47  0
         private static String SUPPRESS_POLICY_ERRORS_KEY = "_suppressPolicyErrorsRequestActivationNode";
 48  
 
 49  
         protected List<ActionRequestValue> generateUninitializedRequests(ActionRequestFactory arFactory, RouteContext context, RuleExpressionResult result) throws WorkflowException {
 50  0
                 FlexRM flexRM = new FlexRM();
 51  0
                 flexRM.makeActionRequests(arFactory, result.getResponsibilities(), context, Rule.Builder.create(result.getRule().getDefinition()).build(), context.getDocument(), null, null);
 52  0
                 return new ArrayList<ActionRequestValue>(arFactory.getRequestGraphs());
 53  
         }
 54  
 
 55  
         protected List<ActionRequestValue> initializeRequests(List<ActionRequestValue> requests, RouteContext context) {
 56  
                 // RequestNode.getNewActionRequests
 57  0
                 List<ActionRequestValue> newRequests = new ArrayList<ActionRequestValue>();
 58  0
                 for (Iterator iterator = requests.iterator(); iterator.hasNext();) {
 59  0
                         ActionRequestValue actionRequest = (ActionRequestValue) iterator.next();
 60  0
                         actionRequest = KEWServiceLocator.getActionRequestService().initializeActionRequestGraph(actionRequest, context.getDocument(), context.getNodeInstance());
 61  0
                         saveActionRequest(context, actionRequest);
 62  0
                         newRequests.add(actionRequest);
 63  0
                 }
 64  0
                 return newRequests;
 65  
         }
 66  
 
 67  
         @Override
 68  
         protected boolean generateNewRequests(boolean initial, RouteContext context, RouteHelper routeHelper)
 69  
         throws WorkflowException, Exception {
 70  0
                 RouteNodeInstance nodeInstance = context.getNodeInstance();
 71  0
                 RouteNode nodeDef = nodeInstance.getRouteNode();
 72  
 
 73  
                 // get the expression
 74  0
                 Map<String, String> cfg = Utilities.getKeyValueCollectionAsMap(nodeDef.getConfigParams());
 75  0
                 String expression = cfg.get("expression");
 76  0
                 if (StringUtils.isEmpty(expression)) {
 77  0
                         throw new WorkflowException("No meta-rule expression supplied in node " + nodeDef);
 78  
                 }
 79  0
                 KRAMetaRuleEngine engine = new KRAMetaRuleEngine(expression);
 80  
 
 81  
                 // get the current statement
 82  0
                 int curStatement = getCurStatement(nodeInstance);
 83  
 
 84  0
                 engine.setCurStatement(curStatement);
 85  
 
 86  0
                 if (engine.isDone()) {
 87  0
                         return false;
 88  
                 }
 89  
 
 90  
                 // generate next round of action requests
 91  0
                 RuleExpressionResult result = engine.processSingleStatement(context);
 92  0
                 if (!result.isSuccess()) {
 93  0
                         return false;
 94  
                 }
 95  
 
 96  0
                 boolean suppressPolicyErrors = isSupressingPolicyErrors(context);
 97  0
                 boolean pastFinalApprover = isPastFinalApprover(context.getDocument(), nodeInstance);
 98  
 
 99  
                 // actionRequests.addAll(makeActionRequests(context, rule, routeHeader, null, null));
 100  
                 // copied from parts of FlexRM and RequestsNode
 101  0
                 ActionRequestFactory arFactory = new ActionRequestFactory(context.getDocument(), context.getNodeInstance());
 102  0
                 List<ActionRequestValue> requests = generateUninitializedRequests(arFactory, context, result);
 103  0
                 requests = initializeRequests(requests, context);
 104  
                 // RequestNode
 105  0
                 if ((requests.isEmpty()) && nodeDef.getMandatoryRouteInd().booleanValue() && ! suppressPolicyErrors) {
 106  0
                         LOG.warn("no requests generated for mandatory route - " + nodeDef.getRouteNodeName());
 107  0
                         throw new RouteManagerException("No requests generated for mandatory route " + nodeDef.getRouteNodeName() + ":" + nodeDef.getRouteMethodName(), context);
 108  
                 }
 109  
                 // determine if we have any approve requests for FinalApprover checks
 110  0
                 boolean hasApproveRequest = false;
 111  0
                 for (Iterator iter = requests.iterator(); iter.hasNext();) {
 112  0
                         ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
 113  0
                         hasApproveRequest = actionRequest.isApproveOrCompleteRequest() || hasApproveRequest;
 114  0
                 }
 115  
                 // if final approver route level and no approve request send to exception routing
 116  0
                 if (nodeDef.getFinalApprovalInd().booleanValue()) {
 117  
                         // we must have an approve request generated if final approver level.
 118  0
                         if (!hasApproveRequest && ! suppressPolicyErrors) {
 119  0
                                 throw new RuntimeException("No Approve Request generated after final approver");
 120  
                         }
 121  0
                 } else if (pastFinalApprover) {
 122  
                         // we can't allow generation of approve requests after final approver. This guys going to exception routing.
 123  0
                         if (hasApproveRequest && ! suppressPolicyErrors) {
 124  0
                                 throw new RuntimeException("Approve Request generated after final approver");
 125  
                         }
 126  
                 }
 127  
 
 128  
                 // increment to next statement
 129  0
                 nodeInstance.getNodeState("stmt").setValue(String.valueOf(curStatement + 1));
 130  0
                 return !requests.isEmpty();
 131  
         }
 132  
 
 133  
         /**
 134  
          * @param nodeInstance the current node instance under execution
 135  
          * @return the meta-rule statement this node instance should process
 136  
          */
 137  
         protected static int getCurStatement(RouteNodeInstance nodeInstance) {
 138  0
                 int statement = 0;
 139  0
                 NodeState nodeState = nodeInstance.getNodeState("stmt");
 140  0
                 if (nodeState == null) {
 141  0
                         nodeState = new NodeState();
 142  0
                         nodeState.setKey("stmt");
 143  0
                         nodeState.setNodeInstance(nodeInstance);
 144  0
                         nodeInstance.addNodeState(nodeState);
 145  
                 }
 146  0
                 if (StringUtils.isEmpty(nodeState.getValue())) {
 147  0
                         nodeState.setValue("0");
 148  
                 } else {
 149  0
                         statement = Integer.parseInt(nodeState.getValue());
 150  
                 }
 151  0
                 return statement;
 152  
         }
 153  
 
 154  
         @Override
 155  
         protected RequestFulfillmentCriteria getRequestFulfillmentCriteria(RouteContext routeContext) {
 156  0
                 return super.getRequestFulfillmentCriteria(routeContext);
 157  
         }
 158  
 
 159  
 
 160  
         // -- copied from request node; a lot of this action request evaluating code should probably go into helper classes or factored into a common subclass
 161  
 
 162  
         /**
 163  
          * The method will get a key value which can be used for comparison purposes. If the node instance has a primary key value, it will be returned. However, if the node instance has not been saved to the database (i.e. during a simulation) this method will return the node instance passed in.
 164  
          */
 165  
         private Object getKey(RouteNodeInstance nodeInstance) {
 166  0
                 String id = nodeInstance.getRouteNodeInstanceId();
 167  0
                 return (id != null ? (Object) id : (Object) nodeInstance);
 168  
         }
 169  
 
 170  
         /**
 171  
          * Checks if the document has past the final approver node by walking backward through the previous node instances.
 172  
          * Ignores any previous nodes that have been "revoked".
 173  
          */
 174  
         private boolean isPastFinalApprover(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance) {
 175  0
                 FinalApproverContext context = new FinalApproverContext();
 176  0
                 List revokedNodeInstances = KEWServiceLocator.getRouteNodeService().getRevokedNodeInstances(document);
 177  0
                 Set revokedNodeInstanceIds = new HashSet();
 178  0
                 for (Iterator iterator = revokedNodeInstances.iterator(); iterator.hasNext(); ) {
 179  0
                         RouteNodeInstance revokedNodeInstance = (RouteNodeInstance) iterator.next();
 180  0
                         revokedNodeInstanceIds.add(revokedNodeInstance.getRouteNodeInstanceId());
 181  0
                 }
 182  0
                 isPastFinalApprover(nodeInstance.getPreviousNodeInstances(), context, revokedNodeInstanceIds);
 183  0
                 return context.isPast;
 184  
         }
 185  
 
 186  
         private void isPastFinalApprover(List previousNodeInstances, FinalApproverContext context, Set revokedNodeInstanceIds) {
 187  0
                 if (previousNodeInstances != null && !previousNodeInstances.isEmpty()) {
 188  0
                         for (Iterator iterator = previousNodeInstances.iterator(); iterator.hasNext();) {
 189  0
                                 if (context.isPast) {
 190  0
                                         return;
 191  
                                 }
 192  0
                                 RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
 193  0
                                 if (context.inspected.contains(getKey(nodeInstance))) {
 194  0
                                         continue;
 195  
                                 } else {
 196  0
                                         context.inspected.add(getKey(nodeInstance));
 197  
                                 }
 198  0
                                 if (Boolean.TRUE.equals(nodeInstance.getRouteNode().getFinalApprovalInd())) {
 199  
                                         // if the node instance has been revoked (by a Return To Previous action for example)
 200  
                                         // then we don't want to consider that node when we determine if we are past final
 201  
                                         // approval or not
 202  0
                                         if (!revokedNodeInstanceIds.contains(nodeInstance.getRouteNodeInstanceId())) {
 203  0
                                                 context.isPast = true;
 204  
                                         }
 205  0
                                         return;
 206  
                                 }
 207  0
                                 isPastFinalApprover(nodeInstance.getPreviousNodeInstances(), context, revokedNodeInstanceIds);
 208  0
                         }
 209  
                 }
 210  0
         }
 211  
         public static boolean isSupressingPolicyErrors(RouteContext routeContext) {
 212  0
                 Boolean suppressPolicyErrors = (Boolean)routeContext.getParameters().get(SUPPRESS_POLICY_ERRORS_KEY);
 213  0
                 if (suppressPolicyErrors == null || ! suppressPolicyErrors) {
 214  0
                         return false;
 215  
                 }
 216  0
                 return true;
 217  
         }
 218  
 
 219  0
         private class FinalApproverContext {
 220  0
                 public Set inspected = new HashSet();
 221  0
                 public boolean isPast = false;
 222  
         }
 223  
 
 224  
 }