View Javadoc

1   /*
2    * Copyright 2006-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.actions;
17  
18  import org.apache.commons.lang.ArrayUtils;
19  import org.junit.Test;
20  import org.kuali.rice.kew.actionitem.ActionItem;
21  import org.kuali.rice.kew.api.KewApiConstants;
22  import org.kuali.rice.kew.api.WorkflowDocument;
23  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
24  import org.kuali.rice.kew.api.action.ActionRequest;
25  import org.kuali.rice.kew.api.action.ActionType;
26  import org.kuali.rice.kew.api.action.InvalidActionTakenException;
27  import org.kuali.rice.kew.framework.postprocessor.*;
28  import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
29  import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
30  import org.kuali.rice.kew.service.KEWServiceLocator;
31  import org.kuali.rice.kew.test.KEWTestCase;
32  import org.kuali.rice.kim.api.KimConstants;
33  import org.kuali.rice.kim.api.common.template.Template;
34  import org.kuali.rice.kim.api.permission.Permission;
35  import org.kuali.rice.kim.api.role.Role;
36  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
37  
38  import java.util.Collection;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  
43  import static org.junit.Assert.*;
44  
45  public class RecallActionTest extends KEWTestCase {
46      /**
47       * test postprocessor for testing afterActionTaken hook
48       */
49      public static class RecallTestPostProcessor extends DefaultPostProcessor {
50          public static ActionType afterActionTakenType;
51          public static ActionTakenEvent afterActionTakenEvent;
52          @Override
53          public ProcessDocReport afterActionTaken(ActionType performed, ActionTakenEvent event) throws Exception {
54              afterActionTakenType = performed;
55              afterActionTakenEvent = event;
56              return super.afterActionTaken(performed, event);
57          }
58      }
59  
60      private static final String RECALL_TEST_DOC = "RecallTest";
61      private static final String RECALL_NOTIFY_TEST_DOC = "RecallWithPrevNotifyTest";
62      private static final String RECALL_NO_PENDING_NOTIFY_TEST_DOC = "RecallWithoutPendingNotifyTest";
63      private static final String RECALL_NOTIFY_THIRDPARTY_TEST_DOC = "RecallWithThirdPartyNotifyTest";
64  
65      private String EWESTFAL = null;
66      private String JHOPF = null;
67      private String RKIRKEND = null;
68      private String NATJOHNS = null;
69      private String BMCGOUGH = null;
70  
71      protected void loadTestData() throws Exception {
72          loadXmlFile("ActionsConfig.xml");
73      }
74  
75      @Override
76      protected void setUpAfterDataLoad() throws Exception {
77          super.setUpAfterDataLoad();
78          EWESTFAL = getPrincipalIdForName("ewestfal");
79          JHOPF = getPrincipalIdForName("jhopf");
80          RKIRKEND = getPrincipalIdForName("rkirkend");
81          NATJOHNS = getPrincipalIdForName("natjohns");
82          BMCGOUGH = getPrincipalIdForName("bmcgough");
83  
84          RecallTestPostProcessor.afterActionTakenType = null;
85          RecallTestPostProcessor.afterActionTakenEvent = null;
86      }
87  
88      protected void assertAfterActionTakenCalled(ActionType performed, ActionType taken) {
89          assertEquals(performed, RecallTestPostProcessor.afterActionTakenType);
90          assertNotNull(RecallTestPostProcessor.afterActionTakenEvent);
91          assertEquals(taken, RecallTestPostProcessor.afterActionTakenEvent.getActionTaken().getActionTaken());
92      }
93  
94      @Test(expected=InvalidActionTakenException.class) public void testCantRecallUnroutedDoc() {
95          WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
96          document.recall("recalling", true);
97      }
98  
99      @Test public void testRecallAsInitiatorBeforeAnyApprovals() throws Exception {
100         WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
101         document.route("");
102 
103         document.recall("recalling", true);
104 
105         assertTrue("Document should be recalled", document.isRecalled());
106         assertAfterActionTakenCalled(ActionType.RECALL, ActionType.RECALL);
107 
108         //verify that the document is truly dead - no more action requests or action items.
109 
110         List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
111         assertEquals("Should not have any active requests", 0, requests.size());
112 
113         Collection<ActionItem> actionItems = KEWServiceLocator.getActionListService().findByDocumentId(document.getDocumentId());
114         assertEquals("Should not have any action items", 0, actionItems.size());
115     }
116 
117     @Test public void testRecallAsInitiatorAfterSingleApproval() throws Exception {
118         WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
119         document.route("");
120 
121         document = WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId());
122         document.approve("");
123 
124         document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
125         document.recall("recalling", true);
126 
127         assertTrue("Document should be recalled", document.isRecalled());
128         assertAfterActionTakenCalled(ActionType.RECALL, ActionType.RECALL);
129 
130         //verify that the document is truly dead - no more action requests or action items.
131 
132         List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
133         assertEquals("Should not have any active requests", 0, requests.size());
134 
135         Collection<ActionItem> actionItems = KEWServiceLocator.getActionListService().findByDocumentId(document.getDocumentId());
136         assertEquals("Should not have any action items", 0, actionItems.size());
137 
138         // can't recall recalled doc
139         assertFalse(document.getValidActions().getValidActions().contains(ActionType.RECALL));
140     }
141 
142     @Test(expected=InvalidActionTakenException.class)
143     public void testRecallInvalidWhenProcessed() throws Exception {
144         WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
145         document.route("");
146 
147         for (String user: new String[] { JHOPF, EWESTFAL, RKIRKEND, NATJOHNS, BMCGOUGH }) {
148             document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
149             document.approve("");
150         }
151 
152         document.refresh();
153         assertTrue("Document should be processed", document.isProcessed());
154         assertTrue("Document should be approved", document.isApproved());
155         assertFalse("Document should not be final", document.isFinal());
156 
157         document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
158         document.recall("recalling when processed should fail", true);
159     }
160 
161     @Test(expected=InvalidActionTakenException.class)
162     public void testRecallInvalidWhenFinal() throws Exception {
163         WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
164         document.route("");
165 
166         for (String user: new String[] { JHOPF, EWESTFAL, RKIRKEND, NATJOHNS, BMCGOUGH }) {
167             document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
168             document.approve("");
169         }
170         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
171         document.acknowledge("");
172 
173         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jthomas"), document.getDocumentId());
174         document.fyi();
175 
176         for (ActionRequest a: document.getRootActionRequests()) {
177             System.err.println(a);
178             if (a.isAcknowledgeRequest() || a.isFyiRequest()) {
179                 System.err.println(a.getPrincipalId());
180                 System.err.println(KimApiServiceLocator.getIdentityService().getPrincipal(a.getPrincipalId()).getPrincipalName());
181             }
182         }
183 
184         assertFalse("Document should not be processed", document.isProcessed());
185         assertTrue("Document should be approved", document.isApproved());
186         assertTrue("Document should be final", document.isFinal());
187 
188         document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
189         document.recall("recalling when processed should fail", true);
190     }
191 
192     @Test public void testRecallToActionListAsInitiatorBeforeAnyApprovals() throws Exception {
193         WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
194         document.route("");
195 
196         document.recall("recalling", false);
197 
198         assertTrue("Document should be saved", document.isSaved());
199         assertEquals(1, document.getCurrentNodeNames().size());
200         assertTrue(document.getCurrentNodeNames().contains("AdHoc"));
201         assertAfterActionTakenCalled(ActionType.RECALL, ActionType.COMPLETE);
202 
203         // initiator has completion request
204         assertTrue(document.isCompletionRequested());
205         // can't recall saved doc
206         assertFalse(document.getValidActions().getValidActions().contains(ActionType.RECALL));
207 
208         // first approver has FYI
209         assertTrue(WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isFYIRequested());
210 
211         document.complete("completing");
212 
213         assertTrue("Document should be enroute", document.isEnroute());
214 
215         assertTrue(WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isApprovalRequested());
216     }
217 
218     private static final String PERM_APP_DOC_STATUS = "recallable by admins";
219     private static final String ROUTE_NODE = "NotifyFirst";
220     private static final String ROUTE_STATUS = "R";
221 
222     protected Permission createRecallPermission(String docType, String appDocStatus, String routeNode, String routeStatus) {
223         return createPermissionForTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION + " for test case", docType, appDocStatus, routeNode, routeStatus);
224     }
225 
226     protected Permission createRouteDocumentPermission(String docType, String appDocStatus, String routeNode, String routeStatus) {
227         return createPermissionForTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.ROUTE_PERMISSION, KewApiConstants.KEW_NAMESPACE, KewApiConstants.ROUTE_PERMISSION + " for test case", docType, appDocStatus, routeNode, routeStatus);
228     }
229 
230     protected Permission createPermissionForTemplate(String template_ns, String template_name, String permission_ns, String permission_name, String docType, String appDocStatus, String routeNode, String routeStatus) {
231         Template permTmpl = KimApiServiceLocator.getPermissionService().findPermTemplateByNamespaceCodeAndName(template_ns, template_name);
232         assertNotNull(permTmpl);
233         Permission.Builder permission = Permission.Builder.create(permission_ns, permission_name);
234         permission.setDescription(permission_name);
235         permission.setTemplate(Template.Builder.create(permTmpl));
236         Map<String, String> attrs = new HashMap<String, String>();
237         attrs.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, docType);
238         attrs.put(KimConstants.AttributeConstants.APP_DOC_STATUS, appDocStatus);
239         attrs.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, routeNode);
240         attrs.put(KimConstants.AttributeConstants.ROUTE_STATUS_CODE, routeStatus);
241         permission.setActive(true);
242         permission.setAttributes(attrs);
243 
244         // save the permission and check that's it's wired up correctly
245         Permission perm = KimApiServiceLocator.getPermissionService().createPermission(permission.build());
246         assertEquals(perm.getTemplate().getId(), permTmpl.getId());
247         int num = 1;
248         if (appDocStatus != null) num++;
249         if (routeNode != null) num++;
250         if (routeStatus != null) num++;
251         assertEquals(num, perm.getAttributes().size());
252         assertEquals(docType, perm.getAttributes().get(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME));
253         assertEquals(appDocStatus, perm.getAttributes().get(KimConstants.AttributeConstants.APP_DOC_STATUS));
254         assertEquals(routeNode, perm.getAttributes().get(KimConstants.AttributeConstants.ROUTE_NODE_NAME));
255         assertEquals(routeStatus, perm.getAttributes().get(KimConstants.AttributeConstants.ROUTE_STATUS_CODE));
256 
257         return perm;
258     }
259 
260     // disable the existing Recall Permission assigned to Initiator Role for test purposes
261     protected void disableInitiatorRecallPermission() {
262         Permission p = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName("KR-WKFLW", "Recall Document");
263         Permission.Builder pb = Permission.Builder.create(p);
264         pb.setActive(false);
265         KimApiServiceLocator.getPermissionService().updatePermission(pb.build());
266     }
267 
268     /**
269      * Tests that a new permission can be configured with the Recall Permission template and that matching works correctly
270      * against the new permission
271      */
272     @Test public void testRecallPermissionMatching() {
273         disableInitiatorRecallPermission();
274         createRecallPermission(RECALL_TEST_DOC, PERM_APP_DOC_STATUS, ROUTE_NODE, ROUTE_STATUS);
275 
276         Map<String, String> details = new HashMap<String, String>();
277         details.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, RECALL_TEST_DOC);
278         details.put(KimConstants.AttributeConstants.APP_DOC_STATUS, PERM_APP_DOC_STATUS);
279         details.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, ROUTE_NODE);
280         details.put(KimConstants.AttributeConstants.ROUTE_STATUS_CODE, ROUTE_STATUS);
281 
282         // test all single field mismatches
283         for (Map.Entry<String, String> entry: details.entrySet()) {
284             Map<String, String> testDetails = new HashMap<String, String>(details);
285             // change a single detail to a non-matching value
286             testDetails.put(entry.getKey(), entry.getValue() + " BOGUS ");
287             assertFalse("non-matching " + entry.getKey() + " detail should cause template to not match", KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, testDetails));
288         }
289 
290         assertTrue("template should match details", KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, details));
291     }
292 
293     @Test public void testRecallPermissionTemplate() throws Exception {
294         WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
295         document.route("");
296 
297         // nope, technical admins can't recall
298         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
299         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
300 
301         // create a recall permission for the RECALL_TEST_DOC doctype
302         Permission perm = createRecallPermission(RECALL_TEST_DOC, PERM_APP_DOC_STATUS, ROUTE_NODE, ROUTE_STATUS);
303 
304         // assign the permission to Technical Administrator role
305         Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
306         KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), techadmin.getId());
307 
308         // our recall permission is assigned to the technical admin role
309 
310         // but the doc will not match...
311         document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_NOTIFY_TEST_DOC);
312         document.route(PERM_APP_DOC_STATUS);
313         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
314         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
315 
316         // .. the app doc status will not match...
317         document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
318         document.route("");
319         // technical admins can't recall since the app doc status is not correct
320         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
321         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
322 
323         // ... the node will not match ...
324         document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
325         document.route("");
326         WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).approve(""); // approve past notifyfirstnode
327         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
328         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
329 
330         // ... the doc status will not match (not recallable anyway) ...
331         document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
332         document.route("");
333         document.cancel("cancelled");
334         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
335         assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
336 
337         // everything should match
338         document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
339         document.setApplicationDocumentStatus(PERM_APP_DOC_STATUS);
340         document.route("");
341         // now technical admins can recall by virtue of having the recall permission on this doc
342         assertTrue(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
343         assertTrue(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
344     }
345 
346     @Test public void testRecallToActionListAsInitiatorAfterApprovals() throws Exception {
347         this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_TEST_DOC);
348     }
349 
350     @Test public void testRecallToActionListAsInitiatorWithNotificationAfterApprovals() throws Exception {
351         this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NOTIFY_TEST_DOC);
352     }
353 
354     @Test public void testRecallToActionListAsInitiatorWithoutPendingNotificationAfterApprovals() throws Exception {
355         this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NO_PENDING_NOTIFY_TEST_DOC);
356     }
357 
358     @Test public void testRecallToActionListAsInitiatorWithThirdPartyNotificationAfterApprovals() throws Exception {
359         this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NOTIFY_THIRDPARTY_TEST_DOC);
360     }
361 
362     /**
363      * Tests that the document is returned to the *recaller*'s action list, not the original initiator
364      * @throws Exception
365      */
366     @Test public void testRecallToActionListAsThirdParty() throws Exception {
367         Permission perm = createRecallPermission(RECALL_TEST_DOC, null, null, null);
368         // assign the permission to Technical Administrator role
369         Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
370         KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), techadmin.getId());
371         // recall as 'admin' user
372         testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC);
373     }
374 
375     // the three tests below test permutations of recall permission and derived role assignment
376     protected void assignRoutePermissionToTechAdmin() {
377         // assign Route Document permission to the Technical Administrator role
378         Permission routePerm = createRouteDocumentPermission(RECALL_TEST_DOC, null, null, null);
379         Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
380         KimApiServiceLocator.getRoleService().assignPermissionToRole(routePerm.getId(), techadmin.getId());
381     }
382     protected void assignRecallPermissionToDocumentRouters() {
383         // assign Recall permission to the Document Router derived role
384         Permission recallPerm = createRecallPermission(RECALL_TEST_DOC, null, null, null);
385         Role documentRouterDerivedRole = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-WKFLW", "Document Router");
386         KimApiServiceLocator.getRoleService().assignPermissionToRole(recallPerm.getId(), documentRouterDerivedRole.getId());
387     }
388     /**
389      * Tests that simply assigning the Route Document permission to the Technical Admin role *without* assigning the
390      * Recall permission to the Document Router derived role, is NOT sufficient to enable recall.
391      */
392     @Test public void testRoutePermissionAssignmentInsufficientForRouterToRecallDoc() throws Exception {
393         assignRoutePermissionToTechAdmin();
394         // recall as 'admin' (Tech Admin) user
395         testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC, false);
396     }
397     /**
398      * Tests that simply assigning the recall permission to the Document Router derived role *without* assigning the
399      * Route Document permission to the Technical Admin role, is NOT sufficient to enable recall.
400      */
401     @Test public void testRecallPermissionAssignmentInsufficientForRouterToRecallDoc() throws Exception {
402         assignRecallPermissionToDocumentRouters();
403         // recall as 'admin' (Tech Admin) user
404         testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC, false);
405     }
406     /**
407      * Tests that we can use the Route Document derived role to assign Recall permission to document routers.
408      */
409     @Test public void testRecallToActionListAsRouterDerivedRole() throws Exception {
410         // assign both! derived role works its magic
411         assignRoutePermissionToTechAdmin();
412         assignRecallPermissionToDocumentRouters();
413         // recall as 'admin' user (Tech Admin) user
414         testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC);
415     }
416 
417     protected void testRecallToActionListAsInitiatorAfterApprovals(String doctype) {
418         testRecallToActionListAfterApprovals(EWESTFAL, EWESTFAL, doctype);
419     }
420 
421     // Implements various permutations of recalls - with and without doctype policies/notifications of various sorts
422     // and as initiator or a third party recaller
423     protected void testRecallToActionListAfterApprovals(String initiator, String recaller, String doctype) {
424         testRecallToActionListAfterApprovals(initiator, recaller, doctype, true);
425     }
426     protected void testRecallToActionListAfterApprovals(String initiator, String recaller, String doctype, boolean expect_recall_success) {
427         boolean notifyPreviousRecipients = !RECALL_TEST_DOC.equals(doctype);
428         boolean notifyPendingRecipients = !RECALL_NO_PENDING_NOTIFY_TEST_DOC.equals(doctype);
429         String[] thirdPartiesNotified = RECALL_NOTIFY_THIRDPARTY_TEST_DOC.equals(doctype) ? new String[] { "quickstart", "admin" } : new String[] {};
430         
431         WorkflowDocument document = WorkflowDocumentFactory.createDocument(initiator, doctype);
432         document.route("");
433 
434         WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).approve("");
435         WorkflowDocumentFactory.loadDocument(initiator, document.getDocumentId()).approve("");
436         WorkflowDocumentFactory.loadDocument(RKIRKEND, document.getDocumentId()).approve("");
437 
438         document = WorkflowDocumentFactory.loadDocument(recaller, document.getDocumentId());
439         System.err.println(document.getValidActions().getValidActions());
440         if (expect_recall_success) {
441             assertTrue("recaller '" + recaller + "' should be able to RECALL", document.getValidActions().getValidActions().contains(ActionType.RECALL));
442         } else {
443             assertFalse("recaller '" + recaller + "' should NOT be able to RECALL", document.getValidActions().getValidActions().contains(ActionType.RECALL));
444             return;
445         }
446         document.recall("recalling", false);
447 
448         assertTrue("Document should be saved", document.isSaved());
449         assertAfterActionTakenCalled(ActionType.RECALL, ActionType.COMPLETE);
450 
451         // the recaller has a completion request
452         assertTrue(document.isCompletionRequested());
453         
454         // pending approver has FYI
455         assertEquals(notifyPendingRecipients, WorkflowDocumentFactory.loadDocument(NATJOHNS, document.getDocumentId()).isFYIRequested());
456         // third approver has FYI
457         assertEquals(notifyPreviousRecipients, WorkflowDocumentFactory.loadDocument(RKIRKEND, document.getDocumentId()).isFYIRequested());
458         // second approver does not have FYI - approver is initiator, FYI is skipped
459         assertFalse(WorkflowDocumentFactory.loadDocument(initiator, document.getDocumentId()).isFYIRequested());
460         // first approver has FYI
461         assertEquals(notifyPreviousRecipients, WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isFYIRequested());
462 
463         if (!ArrayUtils.isEmpty(thirdPartiesNotified)) {
464             for (String recipient: thirdPartiesNotified) {
465                 assertTrue("Expected FYI to be sent to: " + recipient, WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(recipient), document.getDocumentId()).isFYIRequested());
466             }
467         }
468         
469         // omit JHOPF, and see if FYI is subsumed by approval request
470         for (String user: new String[] { RKIRKEND, NATJOHNS }) {
471             WorkflowDocumentFactory.loadDocument(user, document.getDocumentId()).fyi();
472         }
473 
474         document.complete("completing");
475 
476         assertTrue("Document should be enroute", document.isEnroute());
477 
478         // generation of approval requests nullify FYIs (?)
479         // if JHOPF had an FYI, he doesn't any longer
480         for (String user: new String[] { JHOPF, RKIRKEND, NATJOHNS }) {
481             document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
482             assertFalse(getPrincipalNameForId(user) + " should not have an FYI", document.isFYIRequested());
483         }
484 
485         // submit all approvals
486         for (String user: new String[] { JHOPF, initiator, RKIRKEND, NATJOHNS, BMCGOUGH }) {
487             document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
488             assertTrue(getPrincipalNameForId(user) + " should have approval request", document.isApprovalRequested());
489             document.approve("approving");
490         }
491 
492         // 2 acks outstanding, we're PROCESSED
493         assertTrue("Document should be processed", document.isProcessed());
494         assertTrue("Document should be approved", document.isApproved());
495 
496         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
497         document.acknowledge("");
498 
499         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jthomas"), document.getDocumentId());
500         document.fyi();
501 
502         assertTrue("Document should be approved", document.isApproved());
503         assertTrue("Document should be final", document.isFinal());
504     }
505 }