001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.engine;
018:
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Iterator;
022: import java.util.LinkedList;
023: import java.util.List;
024:
025: import org.apache.log4j.MDC;
026:
027: import edu.iu.uis.eden.DocumentRouteLevelChange;
028: import edu.iu.uis.eden.DocumentRouteStatusChange;
029: import edu.iu.uis.eden.EdenConstants;
030: import edu.iu.uis.eden.KEWServiceLocator;
031: import edu.iu.uis.eden.actionrequests.ActionRequestValue;
032: import edu.iu.uis.eden.engine.node.Branch;
033: import edu.iu.uis.eden.engine.node.BranchState;
034: import edu.iu.uis.eden.engine.node.Process;
035: import edu.iu.uis.eden.engine.node.ProcessResult;
036: import edu.iu.uis.eden.engine.node.RouteNodeInstance;
037: import edu.iu.uis.eden.engine.node.RouteNodeService;
038: import edu.iu.uis.eden.engine.transition.Transition;
039: import edu.iu.uis.eden.engine.transition.TransitionEngine;
040: import edu.iu.uis.eden.engine.transition.TransitionEngineFactory;
041: import edu.iu.uis.eden.exception.InvalidActionTakenException;
042: import edu.iu.uis.eden.exception.RouteManagerException;
043: import edu.iu.uis.eden.exception.WorkflowException;
044: import edu.iu.uis.eden.postprocessor.PostProcessor;
045: import edu.iu.uis.eden.postprocessor.ProcessDocReport;
046: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
047: import edu.iu.uis.eden.routeheader.RouteHeaderService;
048: import edu.iu.uis.eden.util.PerformanceLogger;
049:
050: /**
051: * The standard and supported implementation of the WorkflowEngine. Runs a processing loop against a given
052: * Document, processing nodes on the document until the document is completed or a node halts the
053: * processing.
054: *
055: * @author ewestfal
056: */
057: public class StandardWorkflowEngine implements WorkflowEngine {
058:
059: protected final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
060: .getLogger(getClass());
061:
062: protected RouteHelper helper = new RouteHelper();
063:
064: public void process(Long documentId, Long nodeInstanceId)
065: throws Exception {
066: if (documentId == null) {
067: throw new IllegalArgumentException(
068: "Cannot process a null document id.");
069: }
070: MDC.put("docID", documentId);
071: boolean success = true;
072: RouteContext context = RouteContext.createNewRouteContext();
073: try {
074: LOG.debug("Aquiring lock on document " + documentId);
075: KEWServiceLocator.getRouteHeaderService().lockRouteHeader(
076: documentId, true);
077: LOG.debug("Aquired lock on document " + documentId);
078: LOG.info("Processing document: " + documentId + " : "
079: + nodeInstanceId);
080: DocumentRouteHeaderValue document = getRouteHeaderService()
081: .getRouteHeader(documentId);
082: if (!document.isRoutable()) {
083: LOG
084: .debug("Document not routable so returning with doing no action");
085: return;
086: }
087: List nodeInstancesToProcess = new LinkedList();
088: if (nodeInstanceId == null) {
089: nodeInstancesToProcess.addAll(getRouteNodeService()
090: .getActiveNodeInstances(documentId));
091: } else {
092: RouteNodeInstance instanceNode = getRouteNodeService()
093: .findRouteNodeInstanceById(nodeInstanceId);
094: if (instanceNode == null) {
095: throw new IllegalArgumentException(
096: "Invalid node instance id: "
097: + nodeInstanceId);
098: }
099: nodeInstancesToProcess.add(instanceNode);
100: }
101:
102: context.setDocument(document);
103:
104: context.setEngineState(new EngineState());
105: ProcessContext processContext = new ProcessContext(true,
106: nodeInstancesToProcess);
107: try {
108: while (!nodeInstancesToProcess.isEmpty()) {
109: context
110: .setNodeInstance((RouteNodeInstance) nodeInstancesToProcess
111: .remove(0));
112: processContext = processNodeInstance(context,
113: helper);
114: if (processContext.isComplete()
115: && !processContext.getNextNodeInstances()
116: .isEmpty()) {
117: nodeInstancesToProcess.addAll(processContext
118: .getNextNodeInstances());
119: }
120: }
121: context.setDocument(nodePostProcess(context));
122: } catch (Exception e) {
123: success = false;
124: // TODO throw a new 'RoutingException' which holds the
125: // RoutingState
126: throw new RouteManagerException(e, context);
127: }
128: } finally {
129: LOG.info((success ? "Successfully processed"
130: : "Failed to process")
131: + " document: "
132: + documentId
133: + " : "
134: + nodeInstanceId);
135: RouteContext.clearCurrentRouteContext();
136: MDC.remove("docID");
137: }
138: }
139:
140: protected ProcessContext processNodeInstance(RouteContext context,
141: RouteHelper helper) throws Exception {
142: RouteNodeInstance nodeInstance = context.getNodeInstance();
143: LOG.debug("Processing node instance: "
144: + nodeInstance.getRouteNode().getRouteNodeName());
145: if (checkAssertions(context)) {
146: // returning an empty context causes the outer loop to terminate
147: return new ProcessContext();
148: }
149: TransitionEngine transitionEngine = TransitionEngineFactory
150: .createTransitionEngine(nodeInstance);
151: ProcessResult processResult = transitionEngine
152: .isComplete(context);
153: nodeInstance.setInitial(false);
154:
155: // if this nodeInstance already has next node instance we don't need to
156: // go to the TE
157: if (processResult.isComplete()) {
158: LOG.debug("Routing node has completed: "
159: + nodeInstance.getRouteNode().getRouteNodeName());
160:
161: context.getEngineState().getCompleteNodeInstances().add(
162: nodeInstance.getRouteNodeInstanceId());
163: List nextNodeCandidates = invokeTransition(context, context
164: .getNodeInstance(), processResult, transitionEngine);
165:
166: // iterate over the next node candidates sending them through the
167: // transition engine's transitionTo method
168: // one at a time for a potential switch. Place the transition
169: // engines result back in the 'actual' next node
170: // list which we put in the next node before doing work.
171: List<RouteNodeInstance> nodesToActivate = new ArrayList<RouteNodeInstance>();
172: if (!nextNodeCandidates.isEmpty()) {
173: nodeInstance.setNextNodeInstances(new ArrayList());
174: for (Iterator nextIt = nextNodeCandidates.iterator(); nextIt
175: .hasNext();) {
176: RouteNodeInstance nextNodeInstance = (RouteNodeInstance) nextIt
177: .next();
178: transitionEngine = TransitionEngineFactory
179: .createTransitionEngine(nextNodeInstance);
180: RouteNodeInstance currentNextNodeInstance = nextNodeInstance;
181: nextNodeInstance = transitionEngine.transitionTo(
182: nextNodeInstance, context);
183: // if the next node has changed, we need to remove our
184: // current node as a next node of the original node
185: if (!currentNextNodeInstance
186: .equals(nextNodeInstance)) {
187: currentNextNodeInstance
188: .getPreviousNodeInstances().remove(
189: nodeInstance);
190: }
191: // before adding next node instance, be sure that it's not
192: // already linked via previous node instances
193: // this is to prevent the engine from setting up references
194: // on nodes that already reference each other.
195: // the primary case being when we are walking over an
196: // already constructed graph of nodes returned from a
197: // dynamic node - probably a more sensible approach would be
198: // to check for the existence of the link and moving on
199: // if it's been established.
200: nextNodeInstance.getPreviousNodeInstances().remove(
201: nodeInstance);
202: nodeInstance.addNextNodeInstance(nextNodeInstance);
203: handleBackwardCompatibility(context,
204: nextNodeInstance);
205: // call the post processor
206: notifyNodeChange(context, nextNodeInstance);
207: nodesToActivate.add(nextNodeInstance);
208: // TODO update document content on context?
209: }
210: }
211: // deactive the current active node
212: nodeInstance.setComplete(true);
213: nodeInstance.setActive(false);
214: // active the nodes we're transitioning into
215: for (RouteNodeInstance nodeToActivate : nodesToActivate) {
216: nodeToActivate.setActive(true);
217: }
218: } else {
219: nodeInstance.setComplete(false);
220: }
221:
222: saveNode(context, nodeInstance);
223: return new ProcessContext(nodeInstance.isComplete(),
224: nodeInstance.getNextNodeInstances());
225: }
226:
227: /**
228: * Checks various assertions regarding the processing of the current node.
229: * If this method returns true, then the node will not be processed.
230: *
231: * This method will throw an exception if it deems that the processing is in
232: * a illegal state.
233: */
234: private boolean checkAssertions(RouteContext context)
235: throws Exception {
236: if (context.getNodeInstance().isComplete()) {
237: LOG.debug("The node has already been completed: "
238: + context.getNodeInstance().getRouteNode()
239: .getRouteNodeName());
240: return true;
241: }
242: if (isRunawayProcessDetected(context.getEngineState())) {
243: // TODO more info in message
244: throw new WorkflowException("Detected runaway process.");
245: }
246: return false;
247: }
248:
249: /**
250: * Invokes the transition and returns the next node instances to transition
251: * to from the current node instance on the route context.
252: *
253: * This is a 3-step process:
254: *
255: * <pre>
256: * 1) If the node instance already has next nodes, return those,
257: * 2) otherwise, invoke the transition engine for the node, if the resulting node instances are not empty, return those,
258: * 3) lastly, if our node is in a process and no next nodes were returned from it's transition engine, invoke the
259: * transition engine of the process node and return the resulting node instances.
260: * </pre>
261: */
262: /*
263: * private List invokeTransition(RouteContext context, RouteNodeInstance
264: * nodeInstance, ProcessResult processResult, TransitionEngine
265: * transitionEngine) throws Exception { List nextNodeInstances =
266: * nodeInstance.getNextNodeInstances(); if (nextNodeInstances.isEmpty()) {
267: * Transition result = transitionEngine.transitionFrom(context,
268: * processResult); nextNodeInstances = result.getNextNodeInstances(); if
269: * (nextNodeInstances.isEmpty() && nodeInstance.isInProcess()) {
270: * transitionEngine =
271: * TransitionEngineFactory.createTransitionEngine(nodeInstance.getProcess());
272: * nextNodeInstances = invokeTransition(context, nodeInstance.getProcess(),
273: * processResult, transitionEngine); } } return nextNodeInstances; }
274: */
275:
276: private List invokeTransition(RouteContext context,
277: RouteNodeInstance nodeInstance,
278: ProcessResult processResult,
279: TransitionEngine transitionEngine) throws Exception {
280: List nextNodeInstances = nodeInstance.getNextNodeInstances();
281: if (nextNodeInstances.isEmpty()) {
282: Transition result = transitionEngine.transitionFrom(
283: context, processResult);
284: nextNodeInstances = result.getNextNodeInstances();
285: if (nextNodeInstances.isEmpty()
286: && nodeInstance.isInProcess()) {
287: transitionEngine = TransitionEngineFactory
288: .createTransitionEngine(nodeInstance
289: .getProcess());
290: context.setNodeInstance(nodeInstance);
291: nextNodeInstances = invokeTransition(context,
292: nodeInstance.getProcess(), processResult,
293: transitionEngine);
294: }
295: }
296: return nextNodeInstances;
297: }
298:
299: /*
300: * private List invokeTransition(RouteContext context, RouteNodeInstance
301: * process, ProcessResult processResult) throws Exception {
302: * RouteNodeInstance nodeInstance = (context.getNodeInstance() ; List
303: * nextNodeInstances = nodeInstance.getNextNodeInstances(); if
304: * (nextNodeInstances.isEmpty()) { TransitionEngine transitionEngine =
305: * TransitionEngineFactory.createTransitionEngine(nodeInstance); Transition
306: * result = transitionEngine.transitionFrom(context, processResult);
307: * nextNodeInstances = result.getNextNodeInstances(); if
308: * (nextNodeInstances.isEmpty() && nodeInstance.isInProcess()) {
309: * transitionEngine =
310: * TransitionEngineFactory.createTransitionEngine(nodeInstance.getProcess());
311: * nextNodeInstances = invokeTransition(context, nodeInstance.getProcess(),
312: * processResult, transitionEngine); } } return nextNodeInstances; }
313: *
314: */private void notifyNodeChange(RouteContext context,
315: RouteNodeInstance nextNodeInstance) {
316: if (!context.isSimulation()) {
317: RouteNodeInstance nodeInstance = context.getNodeInstance();
318: DocumentRouteLevelChange event = new DocumentRouteLevelChange(
319: context.getDocument().getRouteHeaderId(), context
320: .getDocument().getAppDocId(), CompatUtils
321: .getLevelForNode(context.getDocument()
322: .getDocumentType(), context
323: .getNodeInstance().getRouteNode()
324: .getRouteNodeName()), CompatUtils
325: .getLevelForNode(context.getDocument()
326: .getDocumentType(),
327: nextNodeInstance.getRouteNode()
328: .getRouteNodeName()),
329: nodeInstance.getRouteNode().getRouteNodeName(),
330: nextNodeInstance.getRouteNode().getRouteNodeName(),
331: nodeInstance.getRouteNodeInstanceId(),
332: nextNodeInstance.getRouteNodeInstanceId());
333: context.setDocument(notifyPostProcessor(context
334: .getDocument(), nodeInstance, event));
335: }
336: }
337:
338: private void handleBackwardCompatibility(RouteContext context,
339: RouteNodeInstance nextNodeInstance) {
340: context.getDocument().setDocRouteLevel(
341: new Integer(context.getDocument().getDocRouteLevel()
342: .intValue() + 1)); // preserve
343: // route
344: // level
345: // concept
346: // if
347: // possible
348: saveDocument(context);
349: }
350:
351: private void saveDocument(RouteContext context) {
352: if (!context.isSimulation()) {
353: getRouteHeaderService().saveRouteHeader(
354: context.getDocument());
355: }
356: }
357:
358: private void saveBranch(RouteContext context, Branch branch) {
359: if (!context.isSimulation()) {
360: KEWServiceLocator.getRouteNodeService().save(branch);
361: }
362: }
363:
364: protected void saveNode(RouteContext context,
365: RouteNodeInstance nodeInstance) {
366: if (!context.isSimulation()) {
367: getRouteNodeService().save(nodeInstance);
368: } else {
369: // if we are in simulation mode, lets go ahead and assign some id
370: // values to our beans
371: for (Iterator iterator = nodeInstance
372: .getNextNodeInstances().iterator(); iterator
373: .hasNext();) {
374: RouteNodeInstance routeNodeInstance = (RouteNodeInstance) iterator
375: .next();
376: if (routeNodeInstance.getRouteNodeInstanceId() == null) {
377: routeNodeInstance.setRouteNodeInstanceId(context
378: .getEngineState().getNextSimulationId());
379: }
380: }
381: if (nodeInstance.getProcess() != null
382: && nodeInstance.getProcess()
383: .getRouteNodeInstanceId() == null) {
384: nodeInstance.getProcess().setRouteNodeInstanceId(
385: context.getEngineState().getNextSimulationId());
386: }
387: if (nodeInstance.getBranch() != null
388: && nodeInstance.getBranch().getBranchId() == null) {
389: nodeInstance.getBranch().setBranchId(
390: context.getEngineState().getNextSimulationId());
391: }
392: }
393: }
394:
395: // TODO extract this into some sort of component which handles transitioning
396: // document state
397: protected DocumentRouteHeaderValue nodePostProcess(
398: RouteContext context) throws InvalidActionTakenException {
399: DocumentRouteHeaderValue document = context.getDocument();
400: Collection activeNodes = getRouteNodeService()
401: .getActiveNodeInstances(document.getRouteHeaderId());
402: boolean moreNodes = false;
403: for (Iterator iterator = activeNodes.iterator(); iterator
404: .hasNext();) {
405: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
406: .next();
407: moreNodes = moreNodes || !nodeInstance.isComplete();
408: }
409: List pendingRequests = KEWServiceLocator
410: .getActionRequestService().findPendingByDoc(
411: document.getRouteHeaderId());
412: boolean activeApproveRequests = false;
413: boolean activeAckRequests = false;
414: for (Iterator iterator = pendingRequests.iterator(); iterator
415: .hasNext();) {
416: ActionRequestValue request = (ActionRequestValue) iterator
417: .next();
418: activeApproveRequests = request
419: .isApproveOrCompleteRequest()
420: || activeApproveRequests;
421: activeAckRequests = request.isAcknowledgeRequest()
422: || activeAckRequests;
423: }
424: // TODO is the logic for going processed still going to be valid?
425: if (!document.isProcessed()
426: && (!moreNodes || !activeApproveRequests)) {
427: LOG.debug("No more nodes for this document "
428: + document.getRouteHeaderId());
429: // TODO perhaps the policies could also be factored out?
430: checkDefaultApprovalPolicy(document);
431: LOG.debug("Marking document approved");
432: // TODO factor out this magical post processing
433: DocumentRouteStatusChange event = new DocumentRouteStatusChange(
434: document.getRouteHeaderId(),
435: document.getAppDocId(), document
436: .getDocRouteStatus(),
437: EdenConstants.ROUTE_HEADER_APPROVED_CD);
438: document.markDocumentApproved();
439: // saveDocument(context);
440: notifyPostProcessor(context, event);
441:
442: LOG.debug("Marking document processed");
443: event = new DocumentRouteStatusChange(document
444: .getRouteHeaderId(), document.getAppDocId(),
445: document.getDocRouteStatus(),
446: EdenConstants.ROUTE_HEADER_PROCESSED_CD);
447: document.markDocumentProcessed();
448: // saveDocument(context);
449: notifyPostProcessor(context, event);
450: }
451:
452: // if document is processed and no pending action requests put the
453: // document into the finalized state.
454: if (document.isProcessed()) {
455: DocumentRouteStatusChange event = new DocumentRouteStatusChange(
456: document.getRouteHeaderId(),
457: document.getAppDocId(), document
458: .getDocRouteStatus(),
459: EdenConstants.ROUTE_HEADER_FINAL_CD);
460: List actionRequests = KEWServiceLocator
461: .getActionRequestService().findPendingByDoc(
462: document.getRouteHeaderId());
463: if (actionRequests.isEmpty()) {
464: document.markDocumentFinalized();
465: // saveDocument(context);
466: notifyPostProcessor(context, event);
467: } else {
468: boolean markFinalized = true;
469: for (Iterator iter = actionRequests.iterator(); iter
470: .hasNext();) {
471: ActionRequestValue actionRequest = (ActionRequestValue) iter
472: .next();
473: if (EdenConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ
474: .equals(actionRequest.getActionRequested())) {
475: markFinalized = false;
476: }
477: }
478: if (markFinalized) {
479: document.markDocumentFinalized();
480: // saveDocument(context);
481: this .notifyPostProcessor(context, event);
482: }
483: }
484: }
485: saveDocument(context);
486: return document;
487: }
488:
489: /**
490: * Check the default approval policy for the document. If the default
491: * approval policy is no and no approval action requests have been created
492: * then throw an execption so that the document will get thrown into
493: * exception routing.
494: *
495: * @param rh
496: * route header to be checked
497: * @param docType
498: * docType of the routeHeader to be checked.
499: * @throws RouteManagerException
500: */
501: private void checkDefaultApprovalPolicy(
502: DocumentRouteHeaderValue document)
503: throws RouteManagerException {
504: if (!document.getDocumentType().getDefaultApprovePolicy()
505: .getPolicyValue().booleanValue()) {
506: LOG
507: .debug("Checking if any requests have been generated for the document");
508: List requests = KEWServiceLocator.getActionRequestService()
509: .findAllActionRequestsByRouteHeaderId(
510: document.getRouteHeaderId());
511: boolean approved = false;
512: for (Iterator iter = requests.iterator(); iter.hasNext();) {
513: ActionRequestValue actionRequest = (ActionRequestValue) iter
514: .next();
515: if (actionRequest.isApproveOrCompleteRequest()
516: && actionRequest.isDone()) { // &&
517: // !(actionRequest.getRouteMethodName().equals(EdenConstants.ADHOC_ROUTE_MODULE_NAME)
518: // &&
519: // actionRequest.isReviewerUser()
520: // &&
521: // document.getInitiatorWorkflowId().equals(actionRequest.getWorkflowId())))
522: // {
523: LOG
524: .debug("Found at least one processed approve request so document can be approved");
525: approved = true;
526: break;
527: }
528: }
529: if (!approved) {
530: LOG
531: .debug("Document requires at least one request and none are present");
532: // TODO what route method name to pass to this?
533: throw new RouteManagerException(
534: "Document should have generated at least one approval request.");
535: }
536: }
537: }
538:
539: private DocumentRouteHeaderValue notifyPostProcessor(
540: RouteContext context, DocumentRouteStatusChange event) {
541: DocumentRouteHeaderValue document = context.getDocument();
542: if (context.isSimulation()) {
543: return document;
544: }
545: if (hasContactedPostProcessor(context, event)) {
546: return document;
547: }
548: Long routeHeaderId = event.getRouteHeaderId();
549: PerformanceLogger performanceLogger = new PerformanceLogger(
550: routeHeaderId);
551: ProcessDocReport processReport = null;
552: PostProcessor postProc = null;
553: try {
554: postProc = document.getDocumentType().getPostProcessor();// SpringServiceLocator.getExtensionService().getPostProcessor(document.getDocumentType().getPostProcessorName());
555: } catch (Exception e) {
556: LOG.error("Error retrieving PostProcessor for document "
557: + document.getRouteHeaderId(), e);
558: throw new RouteManagerException(
559: "Error retrieving PostProcessor for document "
560: + document.getRouteHeaderId(), e);
561: }
562: try {
563: processReport = postProc.doRouteStatusChange(event);
564: } catch (Exception e) {
565: LOG.error("Error notifying post processor", e);
566: throw new RouteManagerException(
567: EdenConstants.POST_PROCESSOR_FAILURE_MESSAGE, e);
568: } finally {
569: performanceLogger
570: .log("Time to notifyPostProcessor of event "
571: + event.getDocumentEventCode() + ".");
572: }
573:
574: if (!processReport.isSuccess()) {
575: LOG.warn("PostProcessor failed to process document: "
576: + processReport.getMessage());
577: throw new RouteManagerException(
578: EdenConstants.POST_PROCESSOR_FAILURE_MESSAGE
579: + processReport.getMessage());
580: }
581: return document;
582: }
583:
584: /**
585: * Returns true if the post processor has already been contacted about a
586: * PROCESSED or FINAL post processor change. If the post processor has not
587: * been contacted, this method will record on the document that it has been.
588: *
589: * This is because, in certain cases, a document could end up in exception
590: * routing after it has already gone PROCESSED or FINAL (i.e. on Mass Action
591: * processing) and we don't want to re-contact the post processor in these
592: * cases.
593: */
594: private boolean hasContactedPostProcessor(RouteContext context,
595: DocumentRouteStatusChange event) {
596: // get the initial node instance, the root branch is where we will store
597: // the state
598: RouteNodeInstance initialInstance = (RouteNodeInstance) context
599: .getDocument().getInitialRouteNodeInstance(0);
600: Branch rootBranch = initialInstance.getBranch();
601: String key = null;
602: if (EdenConstants.ROUTE_HEADER_PROCESSED_CD.equals(event
603: .getNewRouteStatus())) {
604: key = EdenConstants.POST_PROCESSOR_PROCESSED_KEY;
605: } else if (EdenConstants.ROUTE_HEADER_FINAL_CD.equals(event
606: .getNewRouteStatus())) {
607: key = EdenConstants.POST_PROCESSOR_FINAL_KEY;
608: } else {
609: return false;
610: }
611: BranchState branchState = rootBranch.getBranchState(key);
612: if (branchState == null) {
613: branchState = new BranchState(key, "true");
614: rootBranch.addBranchState(branchState);
615: saveBranch(context, rootBranch);
616: return false;
617: }
618: return "true".equals(branchState.getValue());
619: }
620:
621: /**
622: * TODO in some cases, someone may modify the route header in the post
623: * processor, if we don't save before and reload after we will get an
624: * optimistic lock exception, we need to work on a better solution for this!
625: * TODO get the routeContext in this method - it should be a better object
626: * than the nodeInstance
627: */
628: private DocumentRouteHeaderValue notifyPostProcessor(
629: DocumentRouteHeaderValue document,
630: RouteNodeInstance nodeInstance,
631: DocumentRouteLevelChange event) {
632: getRouteHeaderService().saveRouteHeader(document);
633: ProcessDocReport report = null;
634: try {
635: report = document.getDocumentType().getPostProcessor()
636: .doRouteLevelChange(event);
637: } catch (Exception e) {
638: LOG.warn("Problems contacting PostProcessor", e);
639: throw new RouteManagerException(
640: "Problems contacting PostProcessor: "
641: + e.getMessage());
642: }
643: document = getRouteHeaderService().getRouteHeader(
644: document.getRouteHeaderId());
645: if (!report.isSuccess()) {
646: LOG
647: .error(
648: "PostProcessor rejected route level change::"
649: + report.getMessage(), report
650: .getProcessException());
651: throw new RouteManagerException(
652: "Route Level change failed in post processor::"
653: + report.getMessage());
654: }
655: return document;
656: }
657:
658: /**
659: * This method initializes the document by materializing and activating the
660: * first node instance on the document.
661: */
662: public void initializeDocument(DocumentRouteHeaderValue document) {
663: // we set up a local route context here just so that we are able to
664: // utilize the saveNode method at the end of
665: // this method. Incidentally, this was changed from pulling the existing
666: // context out because it would override
667: // the document in the route context in the case of a document being
668: // initialized for reporting purposes.
669: RouteContext context = new RouteContext();
670: context.setDocument(document);
671: if (context.getEngineState() == null) {
672: context.setEngineState(new EngineState());
673: }
674: Process process = document.getDocumentType()
675: .getPrimaryProcess();
676: if (process == null || process.getInitialRouteNode() == null) {
677: throw new IllegalArgumentException("DocumentType '"
678: + document.getDocumentType().getName()
679: + "' has no primary process configured!");
680: }
681: RouteNodeInstance nodeInstance = helper.getNodeFactory()
682: .createRouteNodeInstance(document.getRouteHeaderId(),
683: process.getInitialRouteNode());
684: nodeInstance.setActive(true);
685: helper.getNodeFactory().createBranch(
686: EdenConstants.PRIMARY_BRANCH_NAME, null, nodeInstance);
687: // TODO we may (probably) need only one of these initial node instances
688: document.getInitialRouteNodeInstances().add(nodeInstance);
689: saveNode(context, nodeInstance);
690: }
691:
692: protected RouteNodeService getRouteNodeService() {
693: return KEWServiceLocator.getRouteNodeService();
694: }
695:
696: private boolean isRunawayProcessDetected(EngineState engineState) {
697: // TODO make this configurable in some way
698: return engineState.getCompleteNodeInstances().size() > 20;
699: }
700:
701: protected RouteHeaderService getRouteHeaderService() {
702: return KEWServiceLocator.getRouteHeaderService();
703: }
704:
705: }
|