Coverage Report - org.kuali.rice.kew.engine.StandardWorkflowEngine
 
Classes in this File Line Coverage Branch Coverage Complexity
StandardWorkflowEngine
2%
9/331
0%
0/176
5.577
 
 1  
 /*
 2  
  * Copyright 2006-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  
 
 17  
 package org.kuali.rice.kew.engine;
 18  
 
 19  
 import java.util.ArrayList;
 20  
 import java.util.Collection;
 21  
 import java.util.Iterator;
 22  
 import java.util.LinkedList;
 23  
 import java.util.List;
 24  
 
 25  
 import org.apache.log4j.MDC;
 26  
 import org.kuali.rice.core.framework.parameter.ParameterService;
 27  
 import org.kuali.rice.kew.actionrequest.ActionRequestValue;
 28  
 import org.kuali.rice.kew.api.doctype.IllegalDocumentTypeException;
 29  
 import org.kuali.rice.kew.engine.node.Branch;
 30  
 import org.kuali.rice.kew.engine.node.BranchState;
 31  
 import org.kuali.rice.kew.engine.node.Process;
 32  
 import org.kuali.rice.kew.engine.node.ProcessResult;
 33  
 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
 34  
 import org.kuali.rice.kew.engine.node.RouteNodeUtils;
 35  
 import org.kuali.rice.kew.engine.node.service.RouteNodeService;
 36  
 import org.kuali.rice.kew.engine.transition.Transition;
 37  
 import org.kuali.rice.kew.engine.transition.TransitionEngine;
 38  
 import org.kuali.rice.kew.engine.transition.TransitionEngineFactory;
 39  
 import org.kuali.rice.kew.exception.InvalidActionTakenException;
 40  
 import org.kuali.rice.kew.exception.RouteManagerException;
 41  
 import org.kuali.rice.kew.exception.WorkflowException;
 42  
 import org.kuali.rice.kew.postprocessor.AfterProcessEvent;
 43  
 import org.kuali.rice.kew.postprocessor.BeforeProcessEvent;
 44  
 import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
 45  
 import org.kuali.rice.kew.postprocessor.DocumentLockingEvent;
 46  
 import org.kuali.rice.kew.postprocessor.DocumentRouteLevelChange;
 47  
 import org.kuali.rice.kew.postprocessor.DocumentRouteStatusChange;
 48  
 import org.kuali.rice.kew.postprocessor.PostProcessor;
 49  
 import org.kuali.rice.kew.postprocessor.ProcessDocReport;
 50  
 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
 51  
 import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
 52  
 import org.kuali.rice.kew.service.KEWServiceLocator;
 53  
 import org.kuali.rice.kew.util.KEWConstants;
 54  
 import org.kuali.rice.kew.util.PerformanceLogger;
 55  
 import org.kuali.rice.krad.util.KRADConstants;
 56  
 
 57  
 
 58  
 /**
 59  
  * The standard and supported implementation of the WorkflowEngine.  Runs a processing loop against a given
 60  
  * Document, processing nodes on the document until the document is completed or a node halts the
 61  
  * processing.
 62  
  *
 63  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 64  
  */
 65  
 public class StandardWorkflowEngine implements WorkflowEngine {
 66  
 
 67  1
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(StandardWorkflowEngine.class);
 68  
 
 69  5
         protected final RouteHelper helper = new RouteHelper();
 70  
         protected final RouteNodeService routeNodeService;
 71  
     protected final RouteHeaderService routeHeaderService;
 72  
     protected final ParameterService parameterService;
 73  
     protected final OrchestrationConfig config;
 74  
 
 75  
 //    public StandardWorkflowEngine() {}
 76  
 
 77  
         protected StandardWorkflowEngine(RouteNodeService routeNodeService, RouteHeaderService routeHeaderService, 
 78  5
                 ParameterService parameterService, OrchestrationConfig config) {
 79  5
             this.routeNodeService = routeNodeService;
 80  5
             this.routeHeaderService = routeHeaderService;
 81  5
             this.parameterService = parameterService;
 82  5
             this.config = config;
 83  5
         }
 84  
 
 85  
 //        public void setRunPostProcessorLogic(boolean runPostProcessorLogic) {
 86  
 //            this.runPostProcessorLogic = runPostProcessorLogic;
 87  
 //        }
 88  
 
 89  
         public boolean isRunPostProcessorLogic() {
 90  2
             return this.config.isRunPostProcessorLogic();
 91  
         }
 92  
 
 93  
         public void process(String documentId, String nodeInstanceId) throws Exception {
 94  0
                 if (documentId == null) {
 95  0
                         throw new IllegalArgumentException("Cannot process a null document id.");
 96  
                 }
 97  0
                 MDC.put("docId", documentId);
 98  0
                 boolean success = true;
 99  0
                 RouteContext context = RouteContext.createNewRouteContext();
 100  
                 try {
 101  0
                         if ( LOG.isInfoEnabled() ) {
 102  0
                                 LOG.info("Aquiring lock on document " + documentId);
 103  
                         }
 104  0
                         KEWServiceLocator.getRouteHeaderService().lockRouteHeader(documentId, true);
 105  0
                         if ( LOG.isInfoEnabled() ) {
 106  0
                                 LOG.info("Aquired lock on document " + documentId);
 107  
                         }
 108  
 
 109  0
                         DocumentRouteHeaderValue document = getRouteHeaderService().getRouteHeader(documentId);
 110  0
                         context.setDocument(document);
 111  0
                         lockAdditionalDocuments(document);
 112  
 
 113  0
                         if ( LOG.isInfoEnabled() ) {
 114  0
                                 LOG.info("Processing document: " + documentId + " : " + nodeInstanceId);
 115  
                         }
 116  
 
 117  
                         try {
 118  0
                     document = notifyPostProcessorBeforeProcess(document, nodeInstanceId);
 119  0
                     context.setDocument(document);
 120  0
             } catch (Exception e) {
 121  0
                 LOG.warn("Problems contacting PostProcessor before engine process", e);
 122  0
                 throw new RouteManagerException("Problems contacting PostProcessor:  " + e.getMessage());
 123  0
             }
 124  0
             if (!document.isRoutable()) {
 125  0
                                 LOG.debug("Document not routable so returning with doing no action");
 126  
                                 return;
 127  
                         }
 128  0
                         List<RouteNodeInstance> nodeInstancesToProcess = new LinkedList<RouteNodeInstance>();
 129  0
                         if (nodeInstanceId == null) {
 130  
                                 // pulls the node instances from the passed in document
 131  0
                                 nodeInstancesToProcess.addAll(RouteNodeUtils.getActiveNodeInstances(document));
 132  
                         } else {
 133  0
                                 RouteNodeInstance instanceNode = RouteNodeUtils.findRouteNodeInstanceById(nodeInstanceId,document);
 134  0
                                 if (instanceNode == null) {
 135  0
                                         throw new IllegalArgumentException("Invalid node instance id: " + nodeInstanceId);
 136  
                                 }
 137  0
                                 nodeInstancesToProcess.add(instanceNode);
 138  
                         }
 139  
 
 140  0
                         context.setEngineState(new EngineState());
 141  0
                         ProcessContext processContext = new ProcessContext(true, nodeInstancesToProcess);
 142  
                         try {
 143  0
                                 while (!nodeInstancesToProcess.isEmpty()) {
 144  0
                                         context.setNodeInstance((RouteNodeInstance) nodeInstancesToProcess.remove(0));
 145  0
                                         processContext = processNodeInstance(context, helper);
 146  0
                                         if (processContext.isComplete() && !processContext.getNextNodeInstances().isEmpty()) {
 147  0
                                                 nodeInstancesToProcess.addAll(processContext.getNextNodeInstances());
 148  
                                         }
 149  
                                 }
 150  0
                                 context.setDocument(nodePostProcess(context));
 151  0
                         } catch (Exception e) {
 152  0
                                 success = false;
 153  
                                 // TODO throw a new 'RoutingException' which holds the
 154  
                                 // RoutingState
 155  0
                                 throw new RouteManagerException(e, context);
 156  0
                         }
 157  
                 } finally {
 158  0
                         if ( LOG.isInfoEnabled() ) {
 159  0
                                 LOG.info((success ? "Successfully processed" : "Failed to process") + " document: " + documentId + " : " + nodeInstanceId);
 160  
                         }
 161  
                         try {
 162  0
                     notifyPostProcessorAfterProcess(context.getDocument(), nodeInstanceId, success);
 163  0
             } catch (Exception e) {
 164  0
                 LOG.warn("Problems contacting PostProcessor after engine process", e);
 165  0
                 throw new RouteManagerException("Problems contacting PostProcessor:  " + e.getMessage(), context);
 166  0
             }
 167  0
                         RouteContext.clearCurrentRouteContext();
 168  0
                         MDC.remove("docId");
 169  0
                 }
 170  0
         }
 171  
 
 172  
         protected ProcessContext processNodeInstance(RouteContext context, RouteHelper helper) throws Exception {
 173  0
                 RouteNodeInstance nodeInstance = context.getNodeInstance();
 174  0
                 if ( LOG.isDebugEnabled() ) {
 175  0
                         LOG.debug("Processing node instance: " + nodeInstance.getRouteNode().getRouteNodeName());
 176  
                 }
 177  0
                 if (checkAssertions(context)) {
 178  
                         // returning an empty context causes the outer loop to terminate
 179  0
                         return new ProcessContext();
 180  
                 }
 181  0
                 TransitionEngine transitionEngine = TransitionEngineFactory.createTransitionEngine(nodeInstance);
 182  0
                 ProcessResult processResult = transitionEngine.isComplete(context);
 183  0
                 nodeInstance.setInitial(false);
 184  
 
 185  
                 // if this nodeInstance already has next node instance we don't need to
 186  
                 // go to the TE
 187  0
                 if (processResult.isComplete()) {
 188  0
                         if ( LOG.isDebugEnabled() ) {
 189  0
                                 LOG.debug("Routing node has completed: " + nodeInstance.getRouteNode().getRouteNodeName());
 190  
                         }
 191  
 
 192  0
                         context.getEngineState().getCompleteNodeInstances().add(nodeInstance.getRouteNodeInstanceId());
 193  0
                         List nextNodeCandidates = invokeTransition(context, context.getNodeInstance(), processResult, transitionEngine);
 194  
 
 195  
                         // iterate over the next node candidates sending them through the
 196  
                         // transition engine's transitionTo method
 197  
                         // one at a time for a potential switch. Place the transition
 198  
                         // engines result back in the 'actual' next node
 199  
                         // list which we put in the next node before doing work.
 200  0
                         List<RouteNodeInstance> nodesToActivate = new ArrayList<RouteNodeInstance>();
 201  0
                         if (!nextNodeCandidates.isEmpty()) {
 202  
                                 // KULRICE-4274: Hierarchy Routing Node issues
 203  
                                 // No longer change nextNodeInstances in place, instead we create a local and assign our local list below
 204  
                                 // the loop so the post processor doesn't save a RouteNodeInstance in an intermediate state
 205  0
                                 ArrayList<RouteNodeInstance> nextNodeInstances = new ArrayList<RouteNodeInstance>();
 206  
 
 207  0
                                 for (Iterator nextIt = nextNodeCandidates.iterator(); nextIt.hasNext();) {
 208  0
                                         RouteNodeInstance nextNodeInstance = (RouteNodeInstance) nextIt.next();
 209  0
                                         transitionEngine = TransitionEngineFactory.createTransitionEngine(nextNodeInstance);
 210  0
                                         RouteNodeInstance currentNextNodeInstance = nextNodeInstance;
 211  0
                                         nextNodeInstance = transitionEngine.transitionTo(nextNodeInstance, context);
 212  
                                         // if the next node has changed, we need to remove our
 213  
                                         // current node as a next node of the original node
 214  0
                                         if (!currentNextNodeInstance.equals(nextNodeInstance)) {
 215  0
                                                 currentNextNodeInstance.getPreviousNodeInstances().remove(nodeInstance);
 216  
                                         }
 217  
                                         // before adding next node instance, be sure that it's not
 218  
                                         // already linked via previous node instances
 219  
                                         // this is to prevent the engine from setting up references
 220  
                                         // on nodes that already reference each other.
 221  
                                         // the primary case being when we are walking over an
 222  
                                         // already constructed graph of nodes returned from a
 223  
                                         // dynamic node - probably a more sensible approach would be
 224  
                                         // to check for the existence of the link and moving on
 225  
                                         // if it's been established.
 226  0
                                         nextNodeInstance.getPreviousNodeInstances().remove(nodeInstance);
 227  0
                                         nextNodeInstances.add(nextNodeInstance);
 228  0
                                         handleBackwardCompatibility(context, nextNodeInstance);
 229  
                                         // call the post processor
 230  0
                                         notifyNodeChange(context, nextNodeInstance);
 231  0
                                         nodesToActivate.add(nextNodeInstance);
 232  
                                          // TODO update document content on context?
 233  0
                                  }
 234  
                                  // assign our local list here so the post processor doesn't save a RouteNodeInstance in an intermediate state
 235  0
                                 for (RouteNodeInstance nextNodeInstance : nextNodeInstances) {
 236  0
                                         nodeInstance.addNextNodeInstance(nextNodeInstance);
 237  
                                 }
 238  
                          }
 239  
  
 240  
                          // deactive the current active node
 241  0
                         nodeInstance.setComplete(true);
 242  0
                         nodeInstance.setActive(false);
 243  
                         // active the nodes we're transitioning into
 244  0
                         for (RouteNodeInstance nodeToActivate : nodesToActivate) {
 245  0
                                 nodeToActivate.setActive(true);
 246  
                         }
 247  0
                 } else {
 248  0
                     nodeInstance.setComplete(false);
 249  
         }
 250  
 
 251  0
                 saveNode(context, nodeInstance);
 252  0
                 return new ProcessContext(nodeInstance.isComplete(), nodeInstance.getNextNodeInstances());
 253  
         }
 254  
 
 255  
         /**
 256  
          * Checks various assertions regarding the processing of the current node.
 257  
          * If this method returns true, then the node will not be processed.
 258  
          *
 259  
          * This method will throw an exception if it deems that the processing is in
 260  
          * a illegal state.
 261  
          */
 262  
         private boolean checkAssertions(RouteContext context) throws Exception {
 263  0
                 if (context.getNodeInstance().isComplete()) {
 264  0
                         if ( LOG.isDebugEnabled() ) {
 265  0
                                 LOG.debug("The node has already been completed: " + context.getNodeInstance().getRouteNode().getRouteNodeName());
 266  
                         }
 267  0
                         return true;
 268  
                 }
 269  0
                 if (isRunawayProcessDetected(context.getEngineState())) {
 270  
 //                         TODO more info in message
 271  0
                         throw new WorkflowException("Detected runaway process.");
 272  
                 }
 273  0
                 return false;
 274  
         }
 275  
 
 276  
         /**
 277  
          * Invokes the transition and returns the next node instances to transition
 278  
          * to from the current node instance on the route context.
 279  
          *
 280  
          * This is a 3-step process:
 281  
          *
 282  
          * <pre>
 283  
          *  1) If the node instance already has next nodes, return those,
 284  
          *  2) otherwise, invoke the transition engine for the node, if the resulting node instances are not empty, return those,
 285  
          *  3) lastly, if our node is in a process and no next nodes were returned from it's transition engine, invoke the
 286  
          *     transition engine of the process node and return the resulting node instances.
 287  
          * </pre>
 288  
          */
 289  
         /*
 290  
          * private List invokeTransition(RouteContext context, RouteNodeInstance
 291  
          * nodeInstance, ProcessResult processResult, TransitionEngine
 292  
          * transitionEngine) throws Exception { List nextNodeInstances =
 293  
          * nodeInstance.getNextNodeInstances(); if (nextNodeInstances.isEmpty()) {
 294  
          * Transition result = transitionEngine.transitionFrom(context,
 295  
          * processResult); nextNodeInstances = result.getNextNodeInstances(); if
 296  
          * (nextNodeInstances.isEmpty() && nodeInstance.isInProcess()) {
 297  
          * transitionEngine =
 298  
          * TransitionEngineFactory.createTransitionEngine(nodeInstance.getProcess());
 299  
          * nextNodeInstances = invokeTransition(context, nodeInstance.getProcess(),
 300  
          * processResult, transitionEngine); } } return nextNodeInstances; }
 301  
          */
 302  
 
 303  
         private List invokeTransition(RouteContext context, RouteNodeInstance nodeInstance, ProcessResult processResult, TransitionEngine transitionEngine) throws Exception {
 304  0
                 List nextNodeInstances = nodeInstance.getNextNodeInstances();
 305  0
                 if (nextNodeInstances.isEmpty()) {
 306  0
                         Transition result = transitionEngine.transitionFrom(context, processResult);
 307  0
                         nextNodeInstances = result.getNextNodeInstances();
 308  0
                         if (nextNodeInstances.isEmpty() && nodeInstance.isInProcess()) {
 309  0
                                 transitionEngine = TransitionEngineFactory.createTransitionEngine(nodeInstance.getProcess());
 310  0
                                 context.setNodeInstance(nodeInstance);
 311  0
                                 nextNodeInstances = invokeTransition(context, nodeInstance.getProcess(), processResult, transitionEngine);
 312  
                         }
 313  
                 }
 314  0
                 return nextNodeInstances;
 315  
         }
 316  
 
 317  
         /*
 318  
          * private List invokeTransition(RouteContext context, RouteNodeInstance
 319  
          * process, ProcessResult processResult) throws Exception {
 320  
          * RouteNodeInstance nodeInstance = (context.getNodeInstance() ; List
 321  
          * nextNodeInstances = nodeInstance.getNextNodeInstances(); if
 322  
          * (nextNodeInstances.isEmpty()) { TransitionEngine transitionEngine =
 323  
          * TransitionEngineFactory.createTransitionEngine(nodeInstance); Transition
 324  
          * result = transitionEngine.transitionFrom(context, processResult);
 325  
          * nextNodeInstances = result.getNextNodeInstances(); if
 326  
          * (nextNodeInstances.isEmpty() && nodeInstance.isInProcess()) {
 327  
          * transitionEngine =
 328  
          * TransitionEngineFactory.createTransitionEngine(nodeInstance.getProcess());
 329  
          * nextNodeInstances = invokeTransition(context, nodeInstance.getProcess(),
 330  
          * processResult, transitionEngine); } } return nextNodeInstances; }
 331  
          *
 332  
          */private void notifyNodeChange(RouteContext context, RouteNodeInstance nextNodeInstance) throws Exception {
 333  0
                 if (!context.isSimulation()) {
 334  0
                         RouteNodeInstance nodeInstance = context.getNodeInstance();
 335  
                         // if application document status transition has been defined, update the status
 336  0
                         String nextStatus = nodeInstance.getRouteNode().getNextDocStatus();
 337  0
                         if (nextStatus != null && nextStatus.length() > 0){
 338  0
                                 context.getDocument().updateAppDocStatus(nextStatus);
 339  
                         }
 340  
 
 341  0
                         DocumentRouteLevelChange event = new DocumentRouteLevelChange(context.getDocument().getDocumentId(), context.getDocument().getAppDocId(), CompatUtils.getLevelForNode(context.getDocument().getDocumentType(), context.getNodeInstance()
 342  
                                         .getRouteNode().getRouteNodeName()), CompatUtils.getLevelForNode(context.getDocument().getDocumentType(), nextNodeInstance.getRouteNode().getRouteNodeName()), nodeInstance.getRouteNode().getRouteNodeName(), nextNodeInstance
 343  
                                         .getRouteNode().getRouteNodeName(), nodeInstance.getRouteNodeInstanceId(), nextNodeInstance.getRouteNodeInstanceId());
 344  0
                         context.setDocument(notifyPostProcessor(context.getDocument(), nodeInstance, event));
 345  
                 }
 346  0
         }
 347  
 
 348  
         private void handleBackwardCompatibility(RouteContext context, RouteNodeInstance nextNodeInstance) {
 349  0
                 context.getDocument().setDocRouteLevel(new Integer(context.getDocument().getDocRouteLevel().intValue() + 1)); // preserve
 350  
                                                                                                                                                                                                                                                 // route
 351  
                                                                                                                                                                                                                                                 // level
 352  
                                                                                                                                                                                                                                                 // concept
 353  
                                                                                                                                                                                                                                                 // if
 354  
                                                                                                                                                                                                                                                 // possible
 355  0
                 saveDocument(context);
 356  0
         }
 357  
 
 358  
         private void saveDocument(RouteContext context) {
 359  0
                 if (!context.isSimulation()) {
 360  0
                         getRouteHeaderService().saveRouteHeader(context.getDocument());
 361  
                 }
 362  0
         }
 363  
 
 364  
         private void saveBranch(RouteContext context, Branch branch) {
 365  0
                 if (!context.isSimulation()) {
 366  0
                         KEWServiceLocator.getRouteNodeService().save(branch);
 367  
                 }
 368  0
         }
 369  
 
 370  
         protected void saveNode(RouteContext context, RouteNodeInstance nodeInstance) {
 371  0
                 if (!context.isSimulation()) {
 372  0
                         getRouteNodeService().save(nodeInstance);
 373  
                 } else {
 374  
                         // if we are in simulation mode, lets go ahead and assign some id
 375  
                         // values to our beans
 376  0
                         for (Iterator<RouteNodeInstance> iterator = nodeInstance.getNextNodeInstances().iterator(); iterator.hasNext();) {
 377  0
                                 RouteNodeInstance routeNodeInstance = (RouteNodeInstance) iterator.next();
 378  0
                                 if (routeNodeInstance.getRouteNodeInstanceId() == null) {
 379  0
                                         routeNodeInstance.setRouteNodeInstanceId(context.getEngineState().getNextSimulationId());
 380  
                                 }
 381  0
                         }
 382  0
                         if (nodeInstance.getProcess() != null && nodeInstance.getProcess().getRouteNodeInstanceId() == null) {
 383  0
                                 nodeInstance.getProcess().setRouteNodeInstanceId(context.getEngineState().getNextSimulationId());
 384  
                         }
 385  0
                         if (nodeInstance.getBranch() != null && nodeInstance.getBranch().getBranchId() == null) {
 386  0
                                 nodeInstance.getBranch().setBranchId(context.getEngineState().getNextSimulationId());
 387  
                         }
 388  
                 }
 389  0
         }
 390  
 
 391  
         // TODO extract this into some sort of component which handles transitioning
 392  
         // document state
 393  
         protected DocumentRouteHeaderValue nodePostProcess(RouteContext context) throws InvalidActionTakenException {
 394  0
                 DocumentRouteHeaderValue document = context.getDocument();
 395  0
                 Collection<RouteNodeInstance> activeNodes = getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
 396  0
                 boolean moreNodes = false;
 397  0
                 for (Iterator<RouteNodeInstance> iterator = activeNodes.iterator(); iterator.hasNext();) {
 398  0
                         RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator.next();
 399  0
                         moreNodes = moreNodes || !nodeInstance.isComplete();
 400  0
                 }
 401  0
                 List pendingRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
 402  0
                 boolean activeApproveRequests = false;
 403  0
                 boolean activeAckRequests = false;
 404  0
                 for (Iterator iterator = pendingRequests.iterator(); iterator.hasNext();) {
 405  0
                         ActionRequestValue request = (ActionRequestValue) iterator.next();
 406  0
                         activeApproveRequests = request.isApproveOrCompleteRequest() || activeApproveRequests;
 407  0
                         activeAckRequests = request.isAcknowledgeRequest() || activeAckRequests;
 408  0
                 }
 409  
                 // TODO is the logic for going processed still going to be valid?
 410  0
                 if (!document.isProcessed() && (!moreNodes || !activeApproveRequests)) {
 411  0
                         if ( LOG.isDebugEnabled() ) {
 412  0
                                 LOG.debug("No more nodes for this document " + document.getDocumentId());
 413  
                         }
 414  
                         // TODO perhaps the policies could also be factored out?
 415  0
                         checkDefaultApprovalPolicy(document);
 416  
 
 417  0
                         LOG.debug("Marking document processed");
 418  0
                         DocumentRouteStatusChange event = new DocumentRouteStatusChange(document.getDocumentId(), document.getAppDocId(), document.getDocRouteStatus(), KEWConstants.ROUTE_HEADER_PROCESSED_CD);
 419  0
                         document.markDocumentProcessed();
 420  
                         // saveDocument(context);
 421  0
                         notifyPostProcessor(context, event);
 422  
                 }
 423  
 
 424  
                 // if document is processed and no pending action requests put the
 425  
                 // document into the finalized state.
 426  0
                 if (document.isProcessed()) {
 427  0
                         DocumentRouteStatusChange event = new DocumentRouteStatusChange(document.getDocumentId(), document.getAppDocId(), document.getDocRouteStatus(), KEWConstants.ROUTE_HEADER_FINAL_CD);
 428  0
                         List actionRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
 429  0
                         if (actionRequests.isEmpty()) {
 430  0
                                 document.markDocumentFinalized();
 431  
                                 // saveDocument(context);
 432  0
                                 notifyPostProcessor(context, event);
 433  
                         } else {
 434  0
                                 boolean markFinalized = true;
 435  0
                                 for (Iterator iter = actionRequests.iterator(); iter.hasNext();) {
 436  0
                                         ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
 437  0
                                         if (KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(actionRequest.getActionRequested())) {
 438  0
                                                 markFinalized = false;
 439  
                                         }
 440  0
                                 }
 441  0
                                 if (markFinalized) {
 442  0
                                         document.markDocumentFinalized();
 443  
                                         // saveDocument(context);
 444  0
                                         this.notifyPostProcessor(context, event);
 445  
                                 }
 446  
                         }
 447  
                 }
 448  0
                 saveDocument(context);
 449  0
                 return document;
 450  
         }
 451  
 
 452  
         /**
 453  
          * Check the default approval policy for the document. If the default
 454  
          * approval policy is no and no approval action requests have been created
 455  
          * then throw an execption so that the document will get thrown into
 456  
          * exception routing.
 457  
          *
 458  
          * @param rh
 459  
          *            route header to be checked
 460  
          * @param docType
 461  
          *            docType of the routeHeader to be checked.
 462  
          * @throws RouteManagerException
 463  
          */
 464  
         private void checkDefaultApprovalPolicy(DocumentRouteHeaderValue document) throws RouteManagerException {
 465  0
                 if (!document.getDocumentType().getDefaultApprovePolicy().getPolicyValue().booleanValue()) {
 466  0
                         LOG.debug("Checking if any requests have been generated for the document");
 467  0
                         List requests = KEWServiceLocator.getActionRequestService().findAllActionRequestsByDocumentId(document.getDocumentId());
 468  0
                         boolean approved = false;
 469  0
                         for (Iterator iter = requests.iterator(); iter.hasNext();) {
 470  0
                                 ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
 471  0
                                 if (actionRequest.isApproveOrCompleteRequest() && actionRequest.isDone()) { // &&
 472  
                                                                                                                                                                                         // !(actionRequest.getRouteMethodName().equals(KEWConstants.ADHOC_ROUTE_MODULE_NAME)
 473  
                                                                                                                                                                                         // &&
 474  
                                                                                                                                                                                         // actionRequest.isReviewerUser()
 475  
                                                                                                                                                                                         // &&
 476  
                                                                                                                                                                                         // document.getInitiatorWorkflowId().equals(actionRequest.getWorkflowId())))
 477  
                                                                                                                                                                                         // {
 478  0
                                         LOG.debug("Found at least one processed approve request so document can be approved");
 479  0
                                         approved = true;
 480  0
                                         break;
 481  
                                 }
 482  0
                         }
 483  0
                         if (!approved) {
 484  0
                                 LOG.debug("Document requires at least one request and none are present");
 485  
                                 // TODO what route method name to pass to this?
 486  0
                                 throw new RouteManagerException("Document should have generated at least one approval request.");
 487  
                         }
 488  
                 }
 489  0
         }
 490  
 
 491  
         private DocumentRouteHeaderValue notifyPostProcessor(RouteContext context, DocumentRouteStatusChange event) {
 492  0
                 DocumentRouteHeaderValue document = context.getDocument();
 493  0
                 if (context.isSimulation()) {
 494  0
                         return document;
 495  
                 }
 496  0
                 if (hasContactedPostProcessor(context, event)) {
 497  0
                         return document;
 498  
                 }
 499  0
                 String documentId = event.getDocumentId();
 500  0
                 PerformanceLogger performanceLogger = new PerformanceLogger(documentId);
 501  0
                 ProcessDocReport processReport = null;
 502  0
                 PostProcessor postProc = null;
 503  
         try {
 504  
             // use the document's post processor unless specified by the runPostProcessorLogic not to
 505  0
             if (!isRunPostProcessorLogic()) {
 506  0
                 postProc = new DefaultPostProcessor();
 507  
             } else {
 508  0
                 postProc = document.getDocumentType().getPostProcessor();
 509  
             }
 510  0
         } catch (Exception e) {
 511  0
             LOG.error("Error retrieving PostProcessor for document " + document.getDocumentId(), e);
 512  0
             throw new RouteManagerException("Error retrieving PostProcessor for document " + document.getDocumentId(), e);
 513  0
         }
 514  
                 try {
 515  0
                         processReport = postProc.doRouteStatusChange(event);
 516  0
                 } catch (Exception e) {
 517  0
                         LOG.error("Error notifying post processor", e);
 518  0
                         throw new RouteManagerException(KEWConstants.POST_PROCESSOR_FAILURE_MESSAGE, e);
 519  
                 } finally {
 520  0
                         performanceLogger.log("Time to notifyPostProcessor of event " + event.getDocumentEventCode() + ".");
 521  0
                 }
 522  
 
 523  0
                 if (!processReport.isSuccess()) {
 524  0
                         LOG.warn("PostProcessor failed to process document: " + processReport.getMessage());
 525  0
                         throw new RouteManagerException(KEWConstants.POST_PROCESSOR_FAILURE_MESSAGE + processReport.getMessage());
 526  
                 }
 527  0
                 return document;
 528  
         }
 529  
 
 530  
         /**
 531  
          * Returns true if the post processor has already been contacted about a
 532  
          * PROCESSED or FINAL post processor change. If the post processor has not
 533  
          * been contacted, this method will record on the document that it has been.
 534  
          *
 535  
          * This is because, in certain cases, a document could end up in exception
 536  
          * routing after it has already gone PROCESSED or FINAL (i.e. on Mass Action
 537  
          * processing) and we don't want to re-contact the post processor in these
 538  
          * cases.
 539  
          */
 540  
         private boolean hasContactedPostProcessor(RouteContext context, DocumentRouteStatusChange event) {
 541  
                 // get the initial node instance, the root branch is where we will store
 542  
                 // the state
 543  0
                 Branch rootBranch = context.getDocument().getRootBranch();
 544  0
                 String key = null;
 545  0
                 if (KEWConstants.ROUTE_HEADER_PROCESSED_CD.equals(event.getNewRouteStatus())) {
 546  0
                         key = KEWConstants.POST_PROCESSOR_PROCESSED_KEY;
 547  0
                 } else if (KEWConstants.ROUTE_HEADER_FINAL_CD.equals(event.getNewRouteStatus())) {
 548  0
                         key = KEWConstants.POST_PROCESSOR_FINAL_KEY;
 549  
                 } else {
 550  0
                         return false;
 551  
                 }
 552  0
                 BranchState branchState = null;
 553  0
                 if (rootBranch != null) {
 554  0
                     branchState = rootBranch.getBranchState(key);
 555  
                 } else {
 556  0
                     return false;
 557  
                 }
 558  0
                 if (branchState == null) {
 559  0
                         branchState = new BranchState();
 560  0
                         branchState.setKey(key);
 561  0
                         branchState.setValue("true");
 562  0
                         rootBranch.addBranchState(branchState);
 563  0
                         saveBranch(context, rootBranch);
 564  0
                         return false;
 565  
                 }
 566  0
                 return "true".equals(branchState.getValue());
 567  
         }
 568  
 
 569  
         /**
 570  
          * TODO in some cases, someone may modify the route header in the post
 571  
          * processor, if we don't save before and reload after we will get an
 572  
          * optimistic lock exception, we need to work on a better solution for this!
 573  
          * TODO get the routeContext in this method - it should be a better object
 574  
          * than the nodeInstance
 575  
          */
 576  
         private DocumentRouteHeaderValue notifyPostProcessor(DocumentRouteHeaderValue document, RouteNodeInstance nodeInstance, DocumentRouteLevelChange event) {
 577  0
                 getRouteHeaderService().saveRouteHeader(document);
 578  0
                 ProcessDocReport report = null;
 579  
                 try {
 580  0
                 PostProcessor postProcessor = null;
 581  
                 // use the document's post processor unless specified by the runPostProcessorLogic not to
 582  0
                 if (!isRunPostProcessorLogic()) {
 583  0
                     postProcessor = new DefaultPostProcessor();
 584  
                 } else {
 585  0
                     postProcessor = document.getDocumentType().getPostProcessor();
 586  
                 }
 587  0
                         report = postProcessor.doRouteLevelChange(event);
 588  0
                 } catch (Exception e) {
 589  0
                         LOG.warn("Problems contacting PostProcessor", e);
 590  0
                         throw new RouteManagerException("Problems contacting PostProcessor:  " + e.getMessage());
 591  0
                 }
 592  0
                 document = getRouteHeaderService().getRouteHeader(document.getDocumentId());
 593  0
                 if (!report.isSuccess()) {
 594  0
                         LOG.error("PostProcessor rejected route level change::" + report.getMessage(), report.getProcessException());
 595  0
                         throw new RouteManagerException("Route Level change failed in post processor::" + report.getMessage());
 596  
                 }
 597  0
                 return document;
 598  
         }
 599  
 
 600  
     /**
 601  
      * TODO get the routeContext in this method - it should be a better object
 602  
      * than the nodeInstance
 603  
      */
 604  
         private DocumentRouteHeaderValue notifyPostProcessorBeforeProcess(DocumentRouteHeaderValue document, String nodeInstanceId) {
 605  0
             return notifyPostProcessorBeforeProcess(document, nodeInstanceId, new BeforeProcessEvent(document.getDocumentId(),document.getAppDocId(),nodeInstanceId));
 606  
         }
 607  
 
 608  
     /**
 609  
      * TODO get the routeContext in this method - it should be a better object
 610  
      * than the nodeInstance
 611  
      */
 612  
     private DocumentRouteHeaderValue notifyPostProcessorBeforeProcess(DocumentRouteHeaderValue document, String nodeInstanceId, BeforeProcessEvent event) {
 613  0
         ProcessDocReport report = null;
 614  
         try {
 615  0
             PostProcessor postProcessor = null;
 616  
             // use the document's post processor unless specified by the runPostProcessorLogic not to
 617  0
             if (!isRunPostProcessorLogic()) {
 618  0
                 postProcessor = new DefaultPostProcessor();
 619  
             } else {
 620  0
                 postProcessor = document.getDocumentType().getPostProcessor();
 621  
             }
 622  0
             report = postProcessor.beforeProcess(event);
 623  0
         } catch (Exception e) {
 624  0
             LOG.warn("Problems contacting PostProcessor", e);
 625  0
             throw new RouteManagerException("Problems contacting PostProcessor:  " + e.getMessage());
 626  0
         }
 627  0
         document = getRouteHeaderService().getRouteHeader(document.getDocumentId());
 628  0
         if (!report.isSuccess()) {
 629  0
             LOG.error("PostProcessor rejected route level change::" + report.getMessage(), report.getProcessException());
 630  0
             throw new RouteManagerException("Route Level change failed in post processor::" + report.getMessage());
 631  
         }
 632  0
         return document;
 633  
     }
 634  
 
 635  
     protected void lockAdditionalDocuments(DocumentRouteHeaderValue document) throws Exception {
 636  0
                 DocumentLockingEvent lockingEvent = new DocumentLockingEvent(document.getDocumentId(), document.getAppDocId());
 637  
                 // TODO this shows up in a few places and could totally be extracted to a method
 638  0
                 PostProcessor postProcessor = null;
 639  
         // use the document's post processor unless specified by the runPostProcessorLogic not to
 640  0
         if (!isRunPostProcessorLogic()) {
 641  0
             postProcessor = new DefaultPostProcessor();
 642  
         } else {
 643  0
             postProcessor = document.getDocumentType().getPostProcessor();
 644  
         }
 645  0
         List<String> documentIdsToLock = postProcessor.getDocumentIdsToLock(lockingEvent);
 646  0
         if (documentIdsToLock != null && !documentIdsToLock.isEmpty()) {
 647  0
                 for (String documentId : documentIdsToLock) {
 648  0
                         if ( LOG.isInfoEnabled() ) {
 649  0
                                     LOG.info("Aquiring additional lock on document " + documentId);
 650  
                             }
 651  0
                         getRouteHeaderService().lockRouteHeader(documentId, true);
 652  0
                         if ( LOG.isInfoEnabled() ) {
 653  0
                                 LOG.info("Aquired lock on document " + documentId);
 654  
                         }
 655  
                 }
 656  
         }
 657  0
         }
 658  
 
 659  
     /**
 660  
      * TODO get the routeContext in this method - it should be a better object
 661  
      * than the nodeInstance
 662  
      */
 663  
     private DocumentRouteHeaderValue notifyPostProcessorAfterProcess(DocumentRouteHeaderValue document, String nodeInstanceId, boolean successfullyProcessed) {
 664  0
             if (document == null) {
 665  
                     // this could happen if we failed to acquire the lock on the document
 666  0
                     return null;
 667  
             }
 668  0
         return notifyPostProcessorAfterProcess(document, nodeInstanceId, new AfterProcessEvent(document.getDocumentId(),document.getAppDocId(),nodeInstanceId,successfullyProcessed));
 669  
     }
 670  
 
 671  
     /**
 672  
      * TODO get the routeContext in this method - it should be a better object
 673  
      * than the nodeInstance
 674  
      */
 675  
     private DocumentRouteHeaderValue notifyPostProcessorAfterProcess(DocumentRouteHeaderValue document, String nodeInstanceId, AfterProcessEvent event) {
 676  0
         ProcessDocReport report = null;
 677  
         try {
 678  0
             PostProcessor postProcessor = null;
 679  
             // use the document's post processor unless specified by the runPostProcessorLogic not to
 680  0
             if (!isRunPostProcessorLogic()) {
 681  0
                 postProcessor = new DefaultPostProcessor();
 682  
             } else {
 683  0
                 postProcessor = document.getDocumentType().getPostProcessor();
 684  
             }
 685  0
             report = postProcessor.afterProcess(event);
 686  0
         } catch (Exception e) {
 687  0
             LOG.warn("Problems contacting PostProcessor", e);
 688  0
             throw new RouteManagerException("Problems contacting PostProcessor:  " + e.getMessage());
 689  0
         }
 690  0
         document = getRouteHeaderService().getRouteHeader(document.getDocumentId());
 691  0
         if (!report.isSuccess()) {
 692  0
             LOG.error("PostProcessor rejected route level change::" + report.getMessage(), report.getProcessException());
 693  0
             throw new RouteManagerException("Route Level change failed in post processor::" + report.getMessage());
 694  
         }
 695  0
         return document;
 696  
     }
 697  
 
 698  
         /**
 699  
          * This method initializes the document by materializing and activating the
 700  
          * first node instance on the document.
 701  
          */
 702  
         public void initializeDocument(DocumentRouteHeaderValue document) {
 703  
                 // we set up a local route context here just so that we are able to
 704  
                 // utilize the saveNode method at the end of
 705  
                 // this method. Incidentally, this was changed from pulling the existing
 706  
                 // context out because it would override
 707  
                 // the document in the route context in the case of a document being
 708  
                 // initialized for reporting purposes.
 709  0
                 RouteContext context = new RouteContext();
 710  0
                 context.setDocument(document);
 711  0
                 if (context.getEngineState() == null) {
 712  0
                         context.setEngineState(new EngineState());
 713  
                 }
 714  0
                 Process process = document.getDocumentType().getPrimaryProcess();
 715  0
                 if (process == null || process.getInitialRouteNode() == null) {
 716  0
                     if (process == null) {
 717  0
                         throw new IllegalDocumentTypeException("DocumentType '" + document.getDocumentType().getName() + "' has no primary process configured!");
 718  
                     }
 719  0
                         return;
 720  
                 }
 721  0
                 RouteNodeInstance nodeInstance = helper.getNodeFactory().createRouteNodeInstance(document.getDocumentId(), process.getInitialRouteNode());
 722  0
                 nodeInstance.setActive(true);
 723  0
                 helper.getNodeFactory().createBranch(KEWConstants.PRIMARY_BRANCH_NAME, null, nodeInstance);
 724  0
                 document.getInitialRouteNodeInstances().add(nodeInstance);
 725  0
                 saveNode(context, nodeInstance);
 726  0
         }
 727  
 
 728  
     private boolean isRunawayProcessDetected(EngineState engineState) throws NumberFormatException {
 729  0
             String maxNodesConstant = getParameterService().getParameterValueAsString(KEWConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KEWConstants.MAX_NODES_BEFORE_RUNAWAY_PROCESS);
 730  0
             int maxNodes = (org.apache.commons.lang.StringUtils.isEmpty(maxNodesConstant)) ? 50 : Integer.valueOf(maxNodesConstant);
 731  0
             return engineState.getCompleteNodeInstances().size() > maxNodes;
 732  
         }
 733  
 
 734  
     protected RouteNodeService getRouteNodeService() {
 735  0
                 return routeNodeService;
 736  
         }
 737  
 
 738  
         protected RouteHeaderService getRouteHeaderService() {
 739  0
                 return routeHeaderService;
 740  
         }
 741  
 
 742  
     protected ParameterService getParameterService() {
 743  0
                 return parameterService;
 744  
         }
 745  
 
 746  
 //    public void setRouteNodeService(RouteNodeService routeNodeService) {
 747  
 //        this.routeNodeService = routeNodeService;
 748  
 //    }
 749  
 //
 750  
 //    public void setRouteHeaderService(RouteHeaderService routeHeaderService) {
 751  
 //        this.routeHeaderService = routeHeaderService;
 752  
 //    }
 753  
 //
 754  
 //    public void setParameterService(ParameterService parameterService) {
 755  
 //        this.parameterService = parameterService;
 756  
 //    }
 757  
 }