001: /*
002: * Copyright 2005-2007 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.actions;
018:
019: import java.util.ArrayList;
020: import java.util.Iterator;
021: import java.util.List;
022:
023: import org.junit.Ignore;
024: import org.junit.Test;
025: import org.kuali.workflow.test.WorkflowTestCase;
026:
027: import edu.iu.uis.eden.DocumentRouteLevelChange;
028: import edu.iu.uis.eden.EdenConstants;
029: import edu.iu.uis.eden.KEWServiceLocator;
030: import edu.iu.uis.eden.actionrequests.ActionRequestValue;
031: import edu.iu.uis.eden.clientapp.WorkflowDocument;
032: import edu.iu.uis.eden.clientapp.vo.NetworkIdVO;
033: import edu.iu.uis.eden.doctype.DocumentType;
034: import edu.iu.uis.eden.doctype.dao.DocumentTypeDAO;
035: import edu.iu.uis.eden.engine.node.RouteNodeInstance;
036: import edu.iu.uis.eden.postprocessor.DefaultPostProcessor;
037: import edu.iu.uis.eden.postprocessor.ProcessDocReport;
038: import edu.iu.uis.eden.test.TestUtilities;
039: import edu.iu.uis.eden.util.Utilities;
040:
041: public class ReturnToPreviousNodeActionTest extends WorkflowTestCase {
042:
043: @Test
044: public void testReturnToPreviousSequential() throws Exception {
045:
046: WorkflowDocument document = new WorkflowDocument(
047: new NetworkIdVO("ewestfal"),
048: SequentialSetup.DOCUMENT_TYPE_NAME);
049: document.routeDocument("");
050:
051: // approve the document to the third node (workflow document 2)
052: document = new WorkflowDocument(new NetworkIdVO("bmcgough"),
053: document.getRouteHeaderId());
054: assertTrue("bmcgough should have approve.", document
055: .isApprovalRequested());
056: document.approve("");
057: document = new WorkflowDocument(new NetworkIdVO("rkirkend"),
058: document.getRouteHeaderId());
059: assertTrue("rkirkend should have approve.", document
060: .isApprovalRequested());
061: document.approve("");
062:
063: // we should now be at workflow document 2 node with request to pmckown
064: assertEquals("Should be at WorkflowDocument2.",
065: SequentialSetup.WORKFLOW_DOCUMENT_2_NODE, document
066: .getNodeNames()[0]);
067: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
068: document.getRouteHeaderId());
069: assertTrue("Document should be enroute.", document
070: .stateIsEnroute());
071: assertTrue("pmckown should have approve.", document
072: .isApprovalRequested());
073:
074: // now return the document to the AdHoc node
075: document.returnToPreviousNode("", SequentialSetup.ADHOC_NODE);
076:
077: // there should now be 1 requests, an APPROVE to the initiator, since pmckown took the "return" action, he will not get
078: // an FYI generated to him
079: List actionRequests = KEWServiceLocator
080: .getActionRequestService()
081: .findAllActionRequestsByRouteHeaderId(
082: document.getRouteHeaderId());
083: boolean isApproveToEwestfal = false;
084: for (Iterator iterator = actionRequests.iterator(); iterator
085: .hasNext();) {
086: ActionRequestValue request = (ActionRequestValue) iterator
087: .next();
088: if (request.getWorkflowUser().getAuthenticationUserId()
089: .getId().equals("ewestfal")) {
090: assertEquals("Should be approve request.",
091: EdenConstants.ACTION_REQUEST_APPROVE_REQ,
092: request.getActionRequested());
093: isApproveToEwestfal = true;
094: }
095: }
096: assertTrue(isApproveToEwestfal);
097: assertEquals("Should be 1 requests.", 1, actionRequests.size());
098:
099: // Route a new document, and test the notification requests
100: document = new WorkflowDocument(new NetworkIdVO("ewestfal"),
101: SequentialSetup.DOCUMENT_TYPE_NAME);
102: document.routeDocument("");
103: // there should now be 2 requests, one to rkirkend and one to bmcgough
104: actionRequests = KEWServiceLocator.getActionRequestService()
105: .findAllActionRequestsByRouteHeaderId(
106: document.getRouteHeaderId());
107: assertEquals("There should be 2 requests.", 2, actionRequests
108: .size());
109:
110: // now return to the current node we are on, effectively refreshing it, initiate this action as rkirkend so that bmcgough gets an FYI
111: document = new WorkflowDocument(new NetworkIdVO("rkirkend"),
112: document.getRouteHeaderId());
113: RouteNodeInstance preReturnNodeInstance = (RouteNodeInstance) KEWServiceLocator
114: .getRouteNodeService().getActiveNodeInstances(
115: document.getRouteHeaderId()).iterator().next();
116: document.returnToPreviousNode("",
117: SequentialSetup.WORKFLOW_DOCUMENT_NODE);
118: preReturnNodeInstance = KEWServiceLocator.getRouteNodeService()
119: .findRouteNodeInstanceById(
120: preReturnNodeInstance.getRouteNodeInstanceId());
121: RouteNodeInstance postReturnNodeInstance = (RouteNodeInstance) KEWServiceLocator
122: .getRouteNodeService().getActiveNodeInstances(
123: document.getRouteHeaderId()).iterator().next();
124:
125: // check the nodes
126: assertFalse("Node instances should be different.",
127: preReturnNodeInstance.getRouteNodeInstanceId()
128: .equals(
129: postReturnNodeInstance
130: .getRouteNodeInstanceId()));
131: assertEquals("Route nodes should be equal.",
132: preReturnNodeInstance.getRouteNode().getRouteNodeId(),
133: postReturnNodeInstance.getRouteNode().getRouteNodeId());
134: // check the relationship between the nodes
135: assertEquals("Should have 1 next node.", 1,
136: preReturnNodeInstance.getNextNodeInstances().size());
137: assertEquals("Should have 1 previous node.", 1,
138: postReturnNodeInstance.getPreviousNodeInstances()
139: .size());
140: assertEquals("Should have 0 next node.", 0,
141: postReturnNodeInstance.getNextNodeInstances().size());
142: assertEquals("pre node's next node should be the post node.",
143: postReturnNodeInstance.getRouteNodeInstanceId(),
144: ((RouteNodeInstance) preReturnNodeInstance
145: .getNextNodeInstances().iterator().next())
146: .getRouteNodeInstanceId());
147: assertEquals(
148: "post node's previous node should be the pre node.",
149: preReturnNodeInstance.getRouteNodeInstanceId(),
150: ((RouteNodeInstance) postReturnNodeInstance
151: .getPreviousNodeInstances().iterator().next())
152: .getRouteNodeInstanceId());
153:
154: // there should now be 3 requests, a new approve to rkirkend and bmcgough and an FYI
155: actionRequests = KEWServiceLocator.getActionRequestService()
156: .findAllActionRequestsByRouteHeaderId(
157: document.getRouteHeaderId());
158: assertEquals("There should be 3 requests.", 3, actionRequests
159: .size());
160: boolean isApproveToRkirkend = false;
161: boolean isApproveToBmcgough = false;
162: boolean isFyiToBmcgough = false;
163: for (Iterator iterator = actionRequests.iterator(); iterator
164: .hasNext();) {
165: ActionRequestValue request = (ActionRequestValue) iterator
166: .next();
167: String netId = request.getWorkflowUser()
168: .getAuthenticationUserId().getId();
169: if (netId.equals("rkirkend")) {
170: assertEquals("Should be approve request.",
171: EdenConstants.ACTION_REQUEST_APPROVE_REQ,
172: request.getActionRequested());
173: isApproveToRkirkend = true;
174: } else if (netId.equals("bmcgough")) {
175: if (request.getActionRequested().equals(
176: EdenConstants.ACTION_REQUEST_APPROVE_REQ)) {
177: isApproveToBmcgough = true;
178: } else if (request.getActionRequested().equals(
179: EdenConstants.ACTION_REQUEST_FYI_REQ)) {
180: isFyiToBmcgough = true;
181: }
182:
183: }
184: }
185: assertTrue(isApproveToRkirkend);
186: assertTrue(isApproveToBmcgough);
187: assertTrue(isFyiToBmcgough);
188:
189: }
190:
191: /**
192: * our routing all ignore previous:
193: *
194: * M
195: * S
196: * B1 B2 B3
197: * B1.1 B2.1
198: * J
199: * M.1
200: * M.2
201: * M.3
202: *
203: * M-WorkflowDocument-bmcgough(A)/rkrirked(A)
204: *
205: * S-Split
206: *
207: * B1- WorkflowDocument2-B1 -pmckown(A)
208: * B1.1- WorkflowDocument3-B1 -jitrue(A)
209: *
210: * B2- WorkflowDocument3-B2 -jitrue(A)
211: * B2.1- WorkflowDocument2-B2 -pmckown(A)
212: *
213: * B3- WorkflowDocument4-B3 -jthomas(A)
214: *
215: * J-Join
216: *
217: * M.1- WorkflowDocumentFinal -xqi(A)
218: * M.2- Acknowledge1 -temay(K)
219: * M.3- Acknowledge2 -jhopf(K)
220: */
221: @Test
222: public void testReturnToPreviousParallel() throws Exception {
223: // set up our own post processor
224: DocumentType docType = KEWServiceLocator
225: .getDocumentTypeService().findByName(
226: ParallelSetup.DOCUMENT_TYPE_NAME);
227: docType
228: .setPostProcessorName(ReturnToPreviousPostProcessor.class
229: .getName());
230: //side step the normal validation of the service
231: ((DocumentTypeDAO) KEWServiceLocator
232: .getService("enDocumentTypeDAO")).save(docType);
233:
234: // route a document
235: WorkflowDocument document = new WorkflowDocument(
236: new NetworkIdVO("ewestfal"),
237: ParallelSetup.DOCUMENT_TYPE_NAME);
238: document.routeDocument("");
239:
240: //M branch
241: document = new WorkflowDocument(new NetworkIdVO("bmcgough"),
242: document.getRouteHeaderId());
243: document.approve("");
244: document = new WorkflowDocument(new NetworkIdVO("rkirkend"),
245: document.getRouteHeaderId());
246: document.approve("");
247:
248: assertAtNodes(document.getRouteHeaderId(), new String[] {
249: ParallelSetup.WORKFLOW_DOCUMENT_2_B1_NODE,
250: ParallelSetup.WORKFLOW_DOCUMENT_3_B2_NODE,
251: ParallelSetup.WORKFLOW_DOCUMENT_4_B3_NODE });
252: assertInBranches(document.getRouteHeaderId(), new String[] {
253: "B1", "B2", "B3" });
254: assertAllBranchesSameParent(document.getRouteHeaderId(),
255: "PRIMARY");
256:
257: // assert that the proper parties have the document
258: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
259: document.getRouteHeaderId());
260: assertTrue(document.isApprovalRequested());
261: document = new WorkflowDocument(new NetworkIdVO("jitrue"),
262: document.getRouteHeaderId());
263: assertTrue(document.isApprovalRequested());
264: document = new WorkflowDocument(new NetworkIdVO("jthomas"),
265: document.getRouteHeaderId());
266: assertTrue(document.isApprovalRequested());
267:
268: // Approve as pmckown on B1 to transition to next node in B1, this is where we'll return from
269: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
270: document.getRouteHeaderId());
271: document.approve("");
272:
273: // now assert that the document is in the proper state
274: assertAtNodes(document.getRouteHeaderId(), new String[] {
275: ParallelSetup.WORKFLOW_DOCUMENT_3_B1_NODE,
276: ParallelSetup.WORKFLOW_DOCUMENT_3_B2_NODE,
277: ParallelSetup.WORKFLOW_DOCUMENT_4_B3_NODE });
278: assertInBranches(document.getRouteHeaderId(), new String[] {
279: "B1", "B2", "B3" });
280: assertAllBranchesSameParent(document.getRouteHeaderId(),
281: "PRIMARY");
282:
283: // check the post processor and make sure it has the proper number of transitions
284: assertEquals(
285: "Post processor should have transitioned 6 times.", 6,
286: ReturnToPreviousPostProcessor.getRouteLevelChanges()
287: .size());
288: ReturnToPreviousPostProcessor.clearRouteLevelChanges();
289:
290: // verify that pmckown no longer has approve request
291: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
292: document.getRouteHeaderId());
293: assertFalse("pmckown should not have an approve request",
294: document.isApprovalRequested());
295:
296: //rollback from B2.1 to B2
297: document = new WorkflowDocument(new NetworkIdVO("jitrue"),
298: document.getRouteHeaderId());
299: assertTrue(document.isApprovalRequested());
300: document.returnToPreviousNode("",
301: ParallelSetup.WORKFLOW_DOCUMENT_2_B1_NODE);
302:
303: // now pmckown shold have the document again
304: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
305: document.getRouteHeaderId());
306: assertTrue("Document should be back to pmckown", document
307: .isApprovalRequested());
308:
309: // check that the post processor was notified properly on the rollback
310: assertEquals("Post processor should have been notified.", 1,
311: ReturnToPreviousPostProcessor.getRouteLevelChanges()
312: .size());
313: DocumentRouteLevelChange levelChangeEvent = (DocumentRouteLevelChange) ReturnToPreviousPostProcessor
314: .getRouteLevelChanges().get(0);
315: assertEquals("New node should be WorkflowDocument2-B1",
316: ParallelSetup.WORKFLOW_DOCUMENT_2_B1_NODE,
317: levelChangeEvent.getNewNodeName());
318: assertEquals("Old node should be WorkflowDocument3-B1",
319: ParallelSetup.WORKFLOW_DOCUMENT_3_B1_NODE,
320: levelChangeEvent.getOldNodeName());
321:
322: }
323:
324: private void assertAtNodes(Long documentId, String[] nodeNames) {
325: List activeNodeInstances = KEWServiceLocator
326: .getRouteNodeService().getActiveNodeInstances(
327: documentId);
328: assertEquals("There should be " + nodeNames.length
329: + " active nodes.", nodeNames.length,
330: activeNodeInstances.size());
331: for (int index = 0; index < nodeNames.length; index++) {
332: String nodeName = nodeNames[index];
333: boolean foundNode = false;
334: for (Iterator iterator = activeNodeInstances.iterator(); iterator
335: .hasNext();) {
336: RouteNodeInstance activeNodeInstance = (RouteNodeInstance) iterator
337: .next();
338: if (activeNodeInstance.getName().equals(nodeName)) {
339: foundNode = true;
340: break;
341: }
342: }
343: assertTrue("Document is not currently at node " + nodeName,
344: foundNode);
345: }
346: }
347:
348: private void assertInBranches(Long documentId, String[] branchNames) {
349: // let's look at where we are and make sure we are in the correct branches and that all 3 branches have
350: // the same parent branch. This is currently a limitation of the ReturnToPreviousNodeAction in that it
351: // will only allow a return if all currently executing branches have the same parent.
352: List nodeInstances = KEWServiceLocator.getRouteNodeService()
353: .getActiveNodeInstances(documentId);
354: boolean foundB1 = false;
355: boolean foundB2 = false;
356: boolean foundB3 = false;
357: for (Iterator iterator = nodeInstances.iterator(); iterator
358: .hasNext();) {
359: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
360: .next();
361: if ("B1".equals(nodeInstance.getBranch().getName()))
362: foundB1 = true;
363: if ("B2".equals(nodeInstance.getBranch().getName()))
364: foundB2 = true;
365: if ("B3".equals(nodeInstance.getBranch().getName()))
366: foundB3 = true;
367: }
368: assertTrue("Not in Branch B1.", foundB1);
369: assertTrue("Not in Branch B2.", foundB2);
370: assertTrue("Not in Branch B3.", foundB3);
371: }
372:
373: private void assertAllBranchesSameParent(Long documentId,
374: String parentBranchName) {
375: List nodeInstances = KEWServiceLocator.getRouteNodeService()
376: .getActiveNodeInstances(documentId);
377: for (Iterator iterator = nodeInstances.iterator(); iterator
378: .hasNext();) {
379: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
380: .next();
381: String branchName = (nodeInstance.getBranch()
382: .getParentBranch() == null ? null : nodeInstance
383: .getBranch().getParentBranch().getName());
384: assertTrue("Parent branch should be '" + parentBranchName
385: + "'.", Utilities.equals(parentBranchName,
386: branchName));
387: }
388: }
389:
390: @Ignore("This test needs to be implemented!")
391: @Test
392: public void testReturnToPreviousParallelErrors() throws Exception {
393: // TODO implement this test!
394: }
395:
396: @Ignore("This test needs to be implemented!")
397: @Test
398: public void testReturnToPreviousSequentialErrors() throws Exception {
399: // TODO implement this test!
400: }
401:
402: /**
403: * This test was implemented to address issue KULWF-495.
404: *
405: * Effectively, we want to ensure that we can return from a Final Approval node and then pass back
406: * through it without blowing the Final Approval Policy and sending the document into exception routing.
407: */
408: @Test
409: public void testReturnToPreviousFromFinalNode() throws Exception {
410: // the BlanketApproveMandatoryNodeTest document type defined in ActionsConfig.xml defines WorkflowDocument2
411: // as a final approval node.
412: WorkflowDocument document = new WorkflowDocument(
413: new NetworkIdVO("ewestfal"),
414: "BlanketApproveMandatoryNodeTest");
415: // blanket approve to the final approver node
416: document.blanketApprove("", "WorkflowDocument2");
417:
418: // the document should now be routed to Sir pmckown
419: assertTrue("Document should be enroute.", document
420: .stateIsEnroute());
421: TestUtilities.assertAtNode(
422: "Should be at ye old WorkflowDocument2 node.",
423: document, "WorkflowDocument2");
424: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
425: document.getRouteHeaderId());
426: assertTrue("Document should be to pmckown.", document
427: .isApprovalRequested());
428: List activeNodeInstances = KEWServiceLocator
429: .getRouteNodeService().getActiveNodeInstances(
430: document.getRouteHeaderId());
431: assertEquals(1, activeNodeInstances.size());
432: RouteNodeInstance nodeInstance = (RouteNodeInstance) activeNodeInstances
433: .get(0);
434: assertTrue(
435: "Active node instance should be a final approval node.",
436: nodeInstance.getRouteNode().getFinalApprovalInd()
437: .booleanValue());
438:
439: // return back to initial node, ewestfal should end up with a complete request
440: document.returnToPreviousNode("", "AdHoc");
441: assertTrue("Document should be enroute.", document
442: .stateIsEnroute());
443: TestUtilities.assertAtNode("We should be at the AdHoc node.",
444: document, "AdHoc");
445: document = new WorkflowDocument(new NetworkIdVO("ewestfal"),
446: document.getRouteHeaderId());
447:
448: // now blanket approve back to WorkflowDocument2 again, before the bug fix this is where the policy failure would happen
449: document.blanketApprove("", "WorkflowDocument2");
450: assertTrue("Document should be enroute.", document
451: .stateIsEnroute());
452: TestUtilities.assertAtNode(
453: "Should be at ye old WorkflowDocument2 node.",
454: document, "WorkflowDocument2");
455: document = new WorkflowDocument(new NetworkIdVO("pmckown"),
456: document.getRouteHeaderId());
457: assertTrue("Document should be to pmckown.", document
458: .isApprovalRequested());
459:
460: // now return it back one node to WorkflowDocument, this should send approve requests to bmcgough and rkirkend
461: document.returnToPreviousNode("", "WorkflowDocument");
462: assertTrue("Document should be enroute.", document
463: .stateIsEnroute());
464: TestUtilities.assertAtNode(
465: "Should be at ye old WorkflowDocument node.", document,
466: "WorkflowDocument");
467: document = new WorkflowDocument(new NetworkIdVO("bmcgough"),
468: document.getRouteHeaderId());
469: assertTrue("Bmcgough should have an approve.", document
470: .isApprovalRequested());
471: document = new WorkflowDocument(new NetworkIdVO("rkirkend"),
472: document.getRouteHeaderId());
473: assertTrue("Rkirkend should have an approve.", document
474: .isApprovalRequested());
475:
476: // now blanket approve it to the end and the document should be processed
477: document.blanketApprove("");
478: assertTrue("Document should be processed.", document
479: .stateIsProcessed());
480: }
481:
482: protected void loadTestData() throws Exception {
483: loadXmlFile("ActionsConfig.xml");
484: }
485:
486: private class SequentialSetup {
487:
488: public static final String DOCUMENT_TYPE_NAME = "BlanketApproveSequentialTest";
489: public static final String ADHOC_NODE = "AdHoc";
490: public static final String WORKFLOW_DOCUMENT_NODE = "WorkflowDocument";
491: public static final String WORKFLOW_DOCUMENT_2_NODE = "WorkflowDocument2";
492: public static final String ACKNOWLEDGE_1_NODE = "Acknowledge1";
493: public static final String ACKNOWLEDGE_2_NODE = "Acknowledge2";
494:
495: }
496:
497: private class ParallelSetup {
498:
499: public static final String DOCUMENT_TYPE_NAME = "BlanketApproveParallelTest";
500: public static final String ADHOC_NODE = "AdHoc";
501: public static final String WORKFLOW_DOCUMENT_NODE = "WorkflowDocument";
502: public static final String WORKFLOW_DOCUMENT_2_B1_NODE = "WorkflowDocument2-B1";
503: public static final String WORKFLOW_DOCUMENT_2_B2_NODE = "WorkflowDocument2-B2";
504: public static final String WORKFLOW_DOCUMENT_3_B1_NODE = "WorkflowDocument3-B1";
505: public static final String WORKFLOW_DOCUMENT_3_B2_NODE = "WorkflowDocument3-B2";
506: public static final String WORKFLOW_DOCUMENT_4_B3_NODE = "WorkflowDocument4-B3";
507: public static final String ACKNOWLEDGE_1_NODE = "Acknowledge1";
508: public static final String ACKNOWLEDGE_2_NODE = "Acknowledge2";
509: public static final String JOIN_NODE = "Join";
510: public static final String SPLIT_NODE = "Split";
511:
512: }
513:
514: public static class ReturnToPreviousPostProcessor extends
515: DefaultPostProcessor {
516:
517: private static List routeLevelChanges = new ArrayList();
518:
519: public ProcessDocReport doRouteLevelChange(
520: DocumentRouteLevelChange levelChangeEvent)
521: throws Exception {
522: routeLevelChanges.add(levelChangeEvent);
523: return new ProcessDocReport(true);
524: }
525:
526: public static List getRouteLevelChanges() {
527: return routeLevelChanges;
528: }
529:
530: public static void clearRouteLevelChanges() {
531: routeLevelChanges.clear();
532: }
533:
534: }
535:
536: }
|