1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.kew.actions;
17
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import junit.framework.Assert;
30
31 import org.apache.commons.lang.ArrayUtils;
32 import org.junit.Test;
33 import org.kuali.rice.core.api.util.RiceKeyConstants;
34 import org.kuali.rice.coreservice.api.parameter.Parameter;
35 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
36 import org.kuali.rice.kew.actionitem.ActionItem;
37 import org.kuali.rice.kew.api.KewApiConstants;
38 import org.kuali.rice.kew.api.KewApiServiceLocator;
39 import org.kuali.rice.kew.api.WorkflowDocument;
40 import org.kuali.rice.kew.api.WorkflowDocumentFactory;
41 import org.kuali.rice.kew.api.action.ActionRequest;
42 import org.kuali.rice.kew.api.action.ActionRequestType;
43 import org.kuali.rice.kew.api.action.ActionType;
44 import org.kuali.rice.kew.api.action.InvalidActionTakenException;
45 import org.kuali.rice.kew.doctype.bo.DocumentType;
46 import org.kuali.rice.kew.doctype.service.impl.KimDocumentTypeAuthorizer;
47 import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
48 import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport;
49 import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
50 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
51 import org.kuali.rice.kew.service.KEWServiceLocator;
52 import org.kuali.rice.kew.test.KEWTestCase;
53 import org.kuali.rice.kim.api.KimConstants;
54 import org.kuali.rice.kim.api.common.attribute.KimAttribute;
55 import org.kuali.rice.kim.api.common.template.Template;
56 import org.kuali.rice.kim.api.permission.Permission;
57 import org.kuali.rice.kim.api.role.Role;
58 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
59 import org.kuali.rice.kim.api.type.KimType;
60 import org.kuali.rice.kim.api.type.KimTypeAttribute;
61 import org.kuali.rice.kim.impl.common.attribute.KimAttributeBo;
62 import org.kuali.rice.kim.impl.type.KimTypeAttributeBo;
63 import org.kuali.rice.krad.data.KradDataServiceLocator;
64 import org.kuali.rice.krad.util.ErrorMessage;
65 import org.kuali.rice.krad.util.GlobalVariables;
66 import org.kuali.rice.kim.impl.type.KimTypeBo;
67 import org.kuali.rice.krad.service.KRADServiceLocator;
68 import org.kuali.rice.krad.util.KRADConstants;
69 import org.kuali.rice.test.BaselineTestCase;
70
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.HashMap;
74 import java.util.List;
75 import java.util.Map;
76
77 import static org.junit.Assert.*;
78
79 public class RecallActionTest extends KEWTestCase {
80
81
82
83 public static class RecallTestPostProcessor extends DefaultPostProcessor {
84 public static ActionType afterActionTakenType;
85 public static ActionTakenEvent afterActionTakenEvent;
86 @Override
87 public ProcessDocReport afterActionTaken(ActionType performed, ActionTakenEvent event) throws Exception {
88 afterActionTakenType = performed;
89 afterActionTakenEvent = event;
90 return super.afterActionTaken(performed, event);
91 }
92 }
93
94 public static class RecallTestDocumentTypeAuthorizer extends KimDocumentTypeAuthorizer {
95 public static String CUSTOM_RECALL_KIM_TYPE_NAME = "Dynamic Type";
96 public static String CUSTOM_RECALL_QUALIFIER_NAME = "Dynamic Qualifier";
97 public static String CUSTOM_RECALL_QUALIFIER_VALUE = "Dynamic Qualifier Value";
98
99 public static String CUSTOM_RECALL_DETAIL_NAME = KimConstants.AttributeConstants.APP_DOC_STATUS;
100 public static String CUSTOM_RECALL_DETAIL_VALUE = "Dynamic Recall Permission Detail Value";
101
102 public static boolean buildPermissionDetailsInvoked = false;
103 public static boolean buildRoleQualifiersInvoked= false;
104
105 @Override
106 protected Map<String, String> buildDocumentTypePermissionDetails(DocumentType documentType, String documentStatus, String actionRequestedCode, String routeNodeName) {
107 buildPermissionDetailsInvoked = true;
108 Map<String, String> details = super.buildDocumentTypePermissionDetails(documentType, documentStatus, actionRequestedCode, routeNodeName);
109 details.put(CUSTOM_RECALL_DETAIL_NAME, CUSTOM_RECALL_DETAIL_VALUE);
110 return details;
111 }
112
113 @Override
114 protected Map<String, String> buildDocumentRoleQualifiers(DocumentRouteHeaderValue document, String routeNodeName) {
115 buildRoleQualifiersInvoked = true;
116 Map<String, String> qualifiers = super.buildDocumentRoleQualifiers(document, routeNodeName);
117 qualifiers.put(CUSTOM_RECALL_QUALIFIER_NAME, CUSTOM_RECALL_QUALIFIER_VALUE);
118 return qualifiers;
119 }
120 }
121
122 private static final String RECALL_TEST_DOC = "RecallTest";
123 private static final String RECALL_TEST_RESTRICTED_DOC = "RecallTestRestricted";
124 private static final String RECALL_TEST_NOROUTING_DOC = "RecallTestNoRouting";
125 private static final String RECALL_TEST_ONLYADHOC_DOC = "RecallTestOnlyAdhoc";
126 private static final String RECALL_NOTIFY_TEST_DOC = "RecallWithPrevNotifyTest";
127 private static final String RECALL_NO_PENDING_NOTIFY_TEST_DOC = "RecallWithoutPendingNotifyTest";
128 private static final String RECALL_NOTIFY_THIRDPARTY_TEST_DOC = "RecallWithThirdPartyNotifyTest";
129
130 private String EWESTFAL = null;
131 private String JHOPF = null;
132 private String RKIRKEND = null;
133 private String NATJOHNS = null;
134 private String BMCGOUGH = null;
135
136 @Override
137 protected void loadTestData() throws Exception {
138 loadXmlFile("ActionsConfig.xml");
139 }
140
141 @Override
142 protected void setUpAfterDataLoad() throws Exception {
143 super.setUpAfterDataLoad();
144 EWESTFAL = getPrincipalIdForName("ewestfal");
145 JHOPF = getPrincipalIdForName("jhopf");
146 RKIRKEND = getPrincipalIdForName("rkirkend");
147 NATJOHNS = getPrincipalIdForName("natjohns");
148 BMCGOUGH = getPrincipalIdForName("bmcgough");
149
150 RecallTestPostProcessor.afterActionTakenType = null;
151 RecallTestPostProcessor.afterActionTakenEvent = null;
152 }
153
154 protected void assertAfterActionTakenCalled(ActionType performed, ActionType taken) {
155 assertEquals(performed, RecallTestPostProcessor.afterActionTakenType);
156 assertNotNull(RecallTestPostProcessor.afterActionTakenEvent);
157 assertEquals(taken, RecallTestPostProcessor.afterActionTakenEvent.getActionTaken().getActionTaken());
158 }
159
160 @Test(expected=InvalidActionTakenException.class) public void testCantRecallUnroutedDoc() {
161 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
162 document.recall("recalling", true);
163 }
164
165 @Test public void testRecallAsInitiatorBeforeAnyApprovals() throws Exception {
166 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
167 document.route("");
168
169 document.recall("recalling", true);
170
171 assertTrue("Document should be recalled", document.isRecalled());
172 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.RECALL);
173
174
175
176 List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
177 assertEquals("Should not have any active requests", 0, requests.size());
178
179 Collection<ActionItem> actionItems = KEWServiceLocator.getActionListService().findByDocumentId(document.getDocumentId());
180 assertEquals("Should not have any action items", 0, actionItems.size());
181 }
182
183 @Test public void testRecallValidActionsTaken() throws Exception {
184
185 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_RESTRICTED_DOC);
186 document.route("routing");
187 document.recall("recalling", true);
188
189
190 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_RESTRICTED_DOC);
191 document.saveDocument("saving");
192 document.route("routing");
193 document.recall("recalling", true);
194 }
195
196 @Test
197 public void testRecallInvalidActionsTaken() throws Exception {
198 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_RESTRICTED_DOC);
199 document.route("");
200
201 document = WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId());
202 document.approve("");
203
204 try {
205 document.recall("recalling", true);
206 fail("Recall should NOT have succeeded. Expected InvalidActionTakenException due to invalid 'APROVE' prior action taken.");
207 } catch (InvalidActionTakenException iate) {
208 assertTrue(iate.getMessage().contains("Invalid prior action taken: 'APPROVE'"));
209 }
210 }
211
212 @Test public void testRecallOnlyAdhocRouting() throws Exception {
213 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_ONLYADHOC_DOC);
214
215 document.adHocToPrincipal(ActionRequestType.APPROVE, "adhoc approve to JHOPF", JHOPF, "adhocing to prevent finalization", true);
216 document.route("routing");
217 try {
218 document.recall("recalling", true);
219 fail("Recall should NOT have succeeded. Expected InvalidActionTakenException due to absence of non-adhoc route nodes.");
220 } catch (InvalidActionTakenException iate) {
221 assertTrue(iate.getMessage().contains("No non-adhoc route nodes defined"));
222 }
223 }
224
225 @Test public void testRecallAsInitiatorAfterSingleApproval() throws Exception {
226 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
227 document.route("");
228
229 document = WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId());
230 document.approve("");
231
232 document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
233 document.recall("recalling", true);
234
235 assertTrue("Document should be recalled", document.isRecalled());
236 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.RECALL);
237
238
239
240 List requests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
241 assertEquals("Should not have any active requests", 0, requests.size());
242
243 Collection<ActionItem> actionItems = KEWServiceLocator.getActionListService().findByDocumentId(document.getDocumentId());
244 assertEquals("Should not have any action items", 0, actionItems.size());
245
246
247 assertFalse(document.getValidActions().getValidActions().contains(ActionType.RECALL));
248 }
249
250 @Test
251 public void testRecallDoesNotRecallDocumentWhenProcessed() throws Exception {
252 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
253 document.route("");
254
255 for (String user: new String[] { JHOPF, EWESTFAL, RKIRKEND, NATJOHNS, BMCGOUGH }) {
256 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
257 document.approve("");
258 }
259
260 document.refresh();
261 assertTrue("Document should be processed", document.isProcessed());
262 assertTrue("Document should be approved", document.isApproved());
263 assertFalse("Document should not be final", document.isFinal());
264
265 document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
266 document.recall("recalling when processed should not recall the document", true);
267
268 Map<String, List<ErrorMessage>> errorMessages = GlobalVariables.getMessageMap().getErrorMessages();
269 assertTrue(errorMessages.size() == 1);
270 for (Map.Entry<String, List<ErrorMessage>> errorMessage : errorMessages.entrySet()) {
271 assertTrue(errorMessage.getValue().get(0).getErrorKey().equals(RiceKeyConstants.MESSAGE_RECALL_NOT_SUPPORTED));
272 }
273
274
275 assertTrue("Document should be processed", document.isProcessed());
276 assertTrue("Document should be approved", document.isApproved());
277 assertFalse("Document should not be final", document.isFinal());
278
279 GlobalVariables.getMessageMap().clearErrorMessages();
280 }
281
282 @Test
283 public void testRecallDoesNotRecallDocumentWhenFinal() throws Exception {
284 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
285 document.route("");
286
287 for (String user: new String[] { JHOPF, EWESTFAL, RKIRKEND, NATJOHNS, BMCGOUGH }) {
288 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
289 document.approve("");
290 }
291 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
292 document.acknowledge("");
293
294 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jthomas"), document.getDocumentId());
295 document.fyi();
296
297 for (ActionRequest a: document.getRootActionRequests()) {
298 System.err.println(a);
299 if (a.isAcknowledgeRequest() || a.isFyiRequest()) {
300 System.err.println(a.getPrincipalId());
301 System.err.println(KimApiServiceLocator.getIdentityService().getPrincipal(a.getPrincipalId()).getPrincipalName());
302 }
303 }
304
305 assertFalse("Document should not be processed", document.isProcessed());
306 assertTrue("Document should be approved", document.isApproved());
307 assertTrue("Document should be final", document.isFinal());
308
309 document = WorkflowDocumentFactory.loadDocument(EWESTFAL, document.getDocumentId());
310 document.recall("recalling when final should not recall the document", true);
311
312 Map<String, List<ErrorMessage>> errorMessages = GlobalVariables.getMessageMap().getErrorMessages();
313 assertTrue(errorMessages.size() == 1);
314 for (Map.Entry<String, List<ErrorMessage>> errorMessage : errorMessages.entrySet()) {
315 assertTrue(errorMessage.getValue().get(0).getErrorKey().equals(RiceKeyConstants.MESSAGE_RECALL_NOT_SUPPORTED));
316 }
317
318
319 assertFalse("Document should not be processed", document.isProcessed());
320 assertTrue("Document should be approved", document.isApproved());
321 assertTrue("Document should be final", document.isFinal());
322
323 GlobalVariables.getMessageMap().clearErrorMessages();
324 }
325
326 @Test public void testRecallToActionListAsInitiatorBeforeAnyApprovals() throws Exception {
327 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
328 document.route("");
329
330 document.recall("recalling", false);
331
332 assertTrue("Document should be saved", document.isSaved());
333 assertEquals(1, document.getCurrentNodeNames().size());
334 assertTrue(document.getCurrentNodeNames().contains("AdHoc"));
335 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.COMPLETE);
336
337
338 assertTrue(document.isCompletionRequested());
339
340 assertFalse(document.getValidActions().getValidActions().contains(ActionType.RECALL));
341
342
343 assertTrue(WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isFYIRequested());
344
345 document.complete("completing");
346
347 assertTrue("Document should be enroute", document.isEnroute());
348
349 assertTrue(WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isApprovalRequested());
350 }
351
352 private static final String PERM_APP_DOC_STATUS = "recallable by admins";
353 private static final String ROUTE_NODE = "NotifyFirst";
354 private static final String ROUTE_STATUS = "R";
355
356 protected Permission createRecallPermission(String docType, String appDocStatus, String routeNode, String routeStatus) {
357 return createPermissionForTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION + " for test case", docType, appDocStatus, routeNode, routeStatus);
358 }
359
360 protected Permission createRouteDocumentPermission(String docType, String appDocStatus, String routeNode, String routeStatus) {
361 return createPermissionForTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.ROUTE_PERMISSION, KewApiConstants.KEW_NAMESPACE, KewApiConstants.ROUTE_PERMISSION + " for test case", docType, appDocStatus, routeNode, routeStatus);
362 }
363
364 protected Permission createPermissionForTemplate(String template_ns, String template_name, String permission_ns, String permission_name, String docType, String appDocStatus, String routeNode, String routeStatus) {
365 Template permTmpl = KimApiServiceLocator.getPermissionService().findPermTemplateByNamespaceCodeAndName(template_ns, template_name);
366 assertNotNull(permTmpl);
367 Permission.Builder permission = Permission.Builder.create(permission_ns, permission_name);
368 permission.setDescription(permission_name);
369 permission.setTemplate(Template.Builder.create(permTmpl));
370 Map<String, String> attrs = new HashMap<String, String>();
371 attrs.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, docType);
372 attrs.put(KimConstants.AttributeConstants.APP_DOC_STATUS, appDocStatus);
373 attrs.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, routeNode);
374 attrs.put(KimConstants.AttributeConstants.ROUTE_STATUS_CODE, routeStatus);
375 permission.setActive(true);
376 permission.setAttributes(attrs);
377
378
379 Permission perm = KimApiServiceLocator.getPermissionService().createPermission(permission.build());
380 assertEquals(perm.getTemplate().getId(), permTmpl.getId());
381 int num = 1;
382 if (appDocStatus != null) {
383 num++;
384 }
385 if (routeNode != null) {
386 num++;
387 }
388 if (routeStatus != null) {
389 num++;
390 }
391 assertEquals(num, perm.getAttributes().size());
392 assertEquals(docType, perm.getAttributes().get(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME));
393 assertEquals(appDocStatus, perm.getAttributes().get(KimConstants.AttributeConstants.APP_DOC_STATUS));
394 assertEquals(routeNode, perm.getAttributes().get(KimConstants.AttributeConstants.ROUTE_NODE_NAME));
395 assertEquals(routeStatus, perm.getAttributes().get(KimConstants.AttributeConstants.ROUTE_STATUS_CODE));
396
397 return perm;
398 }
399
400
401 protected void disableInitiatorRecallPermission() {
402 Permission p = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName("KR-WKFLW", "Recall Document");
403 Permission.Builder pb = Permission.Builder.create(p);
404 pb.setActive(false);
405 KimApiServiceLocator.getPermissionService().updatePermission(pb.build());
406 }
407
408
409 protected void setKimPriorityOnDocumentTypeParameterValue(String parameterValue) {
410 if(CoreFrameworkServiceLocator.getParameterService().parameterExists(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.KIM_PRIORITY_ON_DOC_TYP_PERMS_IND)) {
411 Parameter kimPriorityOverDocTypePolicyParameter = CoreFrameworkServiceLocator.getParameterService().getParameter(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.KIM_PRIORITY_ON_DOC_TYP_PERMS_IND);
412 Parameter.Builder b = Parameter.Builder.create(kimPriorityOverDocTypePolicyParameter);
413 b.setValue(parameterValue);
414 CoreFrameworkServiceLocator.getParameterService().updateParameter(b.build());
415 }
416 }
417
418 protected String getKimPriorityOnDocumentTypeParameterValue() {
419 if(CoreFrameworkServiceLocator.getParameterService().parameterExists(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.KIM_PRIORITY_ON_DOC_TYP_PERMS_IND)) {
420 return CoreFrameworkServiceLocator.getParameterService().getParameter(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.KIM_PRIORITY_ON_DOC_TYP_PERMS_IND).getValue();
421 }
422 return null;
423 }
424
425
426
427
428
429
430 @Test public void testRecallPermissionMatching() {
431 disableInitiatorRecallPermission();
432 createRecallPermission(RECALL_TEST_DOC, PERM_APP_DOC_STATUS, ROUTE_NODE, ROUTE_STATUS);
433
434 Map<String, String> details = new HashMap<String, String>();
435 details.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, RECALL_TEST_DOC);
436 details.put(KimConstants.AttributeConstants.APP_DOC_STATUS, PERM_APP_DOC_STATUS);
437 details.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, ROUTE_NODE);
438 details.put(KimConstants.AttributeConstants.ROUTE_STATUS_CODE, ROUTE_STATUS);
439
440
441 for (Map.Entry<String, String> entry: details.entrySet()) {
442 Map<String, String> testDetails = new HashMap<String, String>(details);
443
444 testDetails.put(entry.getKey(), entry.getValue() + " BOGUS ");
445 assertFalse("non-matching " + entry.getKey() + " detail should cause template to not match", KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, testDetails));
446 }
447
448 assertTrue("template should match details", KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, details));
449 }
450
451 @Test public void testRecallPermissionTemplate() throws Exception {
452 WorkflowDocument document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
453 document.route("");
454
455
456 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
457 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
458
459
460 Permission perm = createRecallPermission(RECALL_TEST_DOC, PERM_APP_DOC_STATUS, ROUTE_NODE, ROUTE_STATUS);
461
462
463 Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
464 KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), techadmin.getId());
465
466
467
468
469 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_NOTIFY_TEST_DOC);
470 document.route(PERM_APP_DOC_STATUS);
471 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
472 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
473
474
475 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
476 document.route("");
477
478 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
479 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
480
481
482 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
483 document.route("");
484 WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).approve("");
485 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
486 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
487
488
489 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
490 document.route("");
491 document.cancel("cancelled");
492 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
493 assertFalse(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
494
495
496 document = WorkflowDocumentFactory.createDocument(EWESTFAL, RECALL_TEST_DOC);
497 document.setApplicationDocumentStatus(PERM_APP_DOC_STATUS);
498 document.route("");
499
500 assertTrue(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("admin"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
501 assertTrue(WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("quickstart"), document.getDocumentId()).getValidActions().getValidActions().contains(ActionType.RECALL));
502 }
503
504 @Test public void testRecallToActionListAsInitiatorAfterApprovals() throws Exception {
505 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_TEST_DOC);
506 }
507
508 @Test public void testRecallToActionListAsInitiatorWithNotificationAfterApprovals() throws Exception {
509 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NOTIFY_TEST_DOC);
510 }
511
512 @Test public void testRecallToActionListAsInitiatorWithoutPendingNotificationAfterApprovals() throws Exception {
513 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NO_PENDING_NOTIFY_TEST_DOC);
514 }
515
516 @Test public void testRecallToActionListAsInitiatorWithThirdPartyNotificationAfterApprovals() throws Exception {
517 this.testRecallToActionListAsInitiatorAfterApprovals(RECALL_NOTIFY_THIRDPARTY_TEST_DOC);
518 }
519
520
521
522
523
524 @Test public void testRecallToActionListAsThirdParty() throws Exception {
525 Permission perm = createRecallPermission(RECALL_TEST_DOC, null, null, null);
526
527 Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
528 KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), techadmin.getId());
529
530 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC);
531 }
532
533
534 protected void assignRoutePermissionToTechAdmin() {
535
536 Permission routePerm = createRouteDocumentPermission(RECALL_TEST_DOC, null, null, null);
537 Role techadmin = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-SYS", "Technical Administrator");
538 KimApiServiceLocator.getRoleService().assignPermissionToRole(routePerm.getId(), techadmin.getId());
539 }
540 protected void assignRecallPermissionToDocumentRouters() {
541
542 Permission recallPerm = createRecallPermission(RECALL_TEST_DOC, null, null, null);
543 Role documentRouterDerivedRole = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName("KR-WKFLW", "Document Router");
544 KimApiServiceLocator.getRoleService().assignPermissionToRole(recallPerm.getId(), documentRouterDerivedRole.getId());
545 }
546
547
548
549
550 @Test public void testRoutePermissionAssignmentInsufficientForRouterToRecallDoc() throws Exception {
551 assignRoutePermissionToTechAdmin();
552
553 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC, false);
554 }
555
556
557
558
559 @Test public void testRecallPermissionAssignmentInsufficientForRouterToRecallDoc() throws Exception {
560 assignRecallPermissionToDocumentRouters();
561
562 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC, false);
563 }
564
565
566
567 @Test public void testRecallToActionListAsRouterDerivedRole() throws Exception {
568
569 assignRoutePermissionToTechAdmin();
570 assignRecallPermissionToDocumentRouters();
571
572 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("admin"), RECALL_TEST_DOC);
573 }
574
575
576
577
578
579
580
581
582 protected Role createRoleWithRecallPermission(String ns, String name, Permission recallPerm, String kimTypeName, String roleQualifierName) {
583
584 KimAttribute.Builder attribute = KimAttribute.Builder.create("org.kuali.rice.kim.bo.impl.KimAttributes", roleQualifierName, "KR-SYS");
585 attribute.setAttributeLabel(roleQualifierName);
586 attribute.setActive(true);
587 KimAttributeBo customAttribute = KRADServiceLocator.getDataObjectService().save(KimAttributeBo.from(attribute.build()));
588
589
590 KimType.Builder kimType = KimType.Builder.create();
591 kimType.setNamespaceCode("KR-SYS");
592 kimType.setName(kimTypeName);
593 kimType.setActive(true);
594 KimTypeBo customKimType = KRADServiceLocator.getDataObjectService().save(KimTypeBo.from(kimType.build()));
595
596 KimTypeAttribute.Builder kimTypeAttribute = KimTypeAttribute.Builder.create();
597 kimTypeAttribute.setKimTypeId(customKimType.getId());
598 kimTypeAttribute.setKimAttribute(KimAttribute.Builder.create(customAttribute));
599 kimTypeAttribute.setActive(true);
600 KimTypeAttributeBo customKimTypeAttribute = KRADServiceLocator.getDataObjectService().save(KimTypeAttributeBo.from(kimTypeAttribute.build()));
601
602 kimType = KimType.Builder.create(customKimType);
603 kimTypeAttribute = KimTypeAttribute.Builder.create(customKimTypeAttribute);
604 kimType.setAttributeDefinitions(Collections.singletonList(kimTypeAttribute));
605 customKimType = KRADServiceLocator.getDataObjectService().save(KimTypeBo.from(kimType.build()));
606
607 KRADServiceLocator.getDataObjectService().flush(KimTypeBo.class);
608
609
610 Role.Builder role = Role.Builder.create();
611 role.setActive(true);
612 role.setDescription("RecallTest custom recall role");
613 role.setName(ns);
614 role.setNamespaceCode(name);
615 role.setKimTypeId(customKimType.getId());
616 Role customRole = KimApiServiceLocator.getRoleService().createRole(role.build());
617
618
619
620 Long chartAttributeId = getNextSequenceLongValue("KRIM_ATTR_DEFN_ID_S");
621 KimAttributeBo chartAttribute = new KimAttributeBo();
622 chartAttribute.setId("" + chartAttributeId);
623 chartAttribute.setAttributeName(roleQualifierName);
624 chartAttribute.setComponentName("org.kuali.rice.kim.bo.impl.KimAttributes");
625 chartAttribute.setNamespaceCode("KR-SYS");
626 chartAttribute.setAttributeLabel(roleQualifierName);
627 chartAttribute.setActive(true);
628 chartAttribute = KradDataServiceLocator.getDataObjectService().save(chartAttribute);
629
630 KimApiServiceLocator.getRoleService().assignPermissionToRole(recallPerm.getId(), customRole.getId());
631
632 List<String> recallCapableRoleIds = KimApiServiceLocator.getPermissionService().getRoleIdsForPermission(recallPerm.getNamespaceCode(), recallPerm.getName());
633 Assert.assertFalse("No recall-capable roles found", recallCapableRoleIds.isEmpty());
634 Assert.assertTrue("New role is not associated with Recall permission", recallCapableRoleIds.contains(customRole.getId()));
635
636 return customRole;
637 }
638
639
640
641
642
643
644
645
646 protected void assignUserQualifiedRole(String principalId, Role role, String roleQualifierName, String roleQualifierValue) {
647
648 Map<String, String> qualifications = new HashMap<String, String>();
649 qualifications.put(roleQualifierName, roleQualifierValue);
650 KimApiServiceLocator.getRoleService().assignPrincipalToRole(getPrincipalIdForName("arh14"), role.getNamespaceCode(), role.getName(), qualifications);
651
652 Collection<String> ids = KimApiServiceLocator.getRoleService().getRoleMemberPrincipalIds(role.getNamespaceCode(), role.getName(), qualifications);
653 Assert.assertTrue("Qualified role assignment failed", ids.contains(principalId));
654 }
655
656
657
658
659 @Test public void testRecallWithCustomDocumentTypeAuthorizer() throws Exception {
660
661 final String ARH14 = getPrincipalIdForName("arh14");
662
663
664 disableInitiatorRecallPermission();
665
666 RecallTestDocumentTypeAuthorizer.buildPermissionDetailsInvoked = false;
667 RecallTestDocumentTypeAuthorizer.buildRoleQualifiersInvoked = false;
668
669
670 testRecallToActionListAfterApprovals(EWESTFAL, getPrincipalIdForName("arh14"), RECALL_TEST_DOC, false);
671
672 final String RECALL_ROLE_NM = "CustomRecall";
673 final String RECALL_ROLE_NS = "KR-SYS";
674
675
676 Permission recallPerm = createRecallPermission(RECALL_TEST_DOC, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_DETAIL_VALUE, null, null);
677 Role recallRole = createRoleWithRecallPermission(RECALL_ROLE_NM, RECALL_ROLE_NS, recallPerm, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_KIM_TYPE_NAME, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_NAME);
678 assignUserQualifiedRole(ARH14, recallRole, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_NAME, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_VALUE);
679
680 Map<String, String> d = new HashMap<String, String>();
681 d.put(RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_DETAIL_NAME, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_DETAIL_VALUE);
682 d.put(KewApiConstants.DOCUMENT_TYPE_NAME_DETAIL, RECALL_TEST_DOC);
683 d.put(KewApiConstants.ROUTE_NODE_NAME_DETAIL, ROUTE_NODE);
684 d.put(KewApiConstants.DOCUMENT_STATUS_DETAIL, ROUTE_STATUS);
685 Map<String, String> q = new HashMap<String, String>();
686 q.put(RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_NAME, RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_VALUE);
687
688 List<Permission> permissions = KimApiServiceLocator.getPermissionService().getAuthorizedPermissionsByTemplate(ARH14, KewApiConstants.KEW_NAMESPACE, KewApiConstants.RECALL_PERMISSION, d, q);
689 Assert.assertEquals(1, permissions.size());
690 Assert.assertEquals(recallPerm.getId(), permissions.get(0).getId());
691
692
693 testRecallToActionListAfterApprovals(EWESTFAL, ARH14, RECALL_TEST_DOC, false);
694
695
696 org.kuali.rice.kew.api.doctype.DocumentType dt = KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(RECALL_TEST_DOC);
697 org.kuali.rice.kew.api.doctype.DocumentType.Builder b = org.kuali.rice.kew.api.doctype.DocumentType.Builder.create(dt);
698 b.setAuthorizer(RecallTestDocumentTypeAuthorizer.class.getName());
699
700 KEWServiceLocator.getDocumentTypeService().save(DocumentType.from(b));
701
702 Assert.assertEquals(RecallTestDocumentTypeAuthorizer.class.getName(), KewApiServiceLocator.getDocumentTypeService().getDocumentTypeByName(RECALL_TEST_DOC).getAuthorizer());
703
704
705 Assert.assertFalse(RecallTestDocumentTypeAuthorizer.buildPermissionDetailsInvoked);
706 Assert.assertFalse(RecallTestDocumentTypeAuthorizer.buildRoleQualifiersInvoked);
707
708
709 testRecallToActionListAfterApprovals(EWESTFAL, ARH14, RECALL_TEST_DOC);
710
711 Assert.assertTrue(RecallTestDocumentTypeAuthorizer.buildPermissionDetailsInvoked);
712 Assert.assertTrue(RecallTestDocumentTypeAuthorizer.buildRoleQualifiersInvoked);
713
714
715 String orig = RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_VALUE;
716 try {
717 RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_VALUE = "I will not match";
718 testRecallToActionListAfterApprovals(EWESTFAL, ARH14, RECALL_TEST_DOC, false);
719 } finally {
720 RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_QUALIFIER_VALUE = orig;
721 }
722
723 orig = RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_DETAIL_VALUE;
724 try {
725 RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_DETAIL_VALUE = "I won't match either";
726 testRecallToActionListAfterApprovals(EWESTFAL, ARH14, RECALL_TEST_DOC, false);
727 } finally {
728 RecallTestDocumentTypeAuthorizer.CUSTOM_RECALL_DETAIL_VALUE = orig;
729 }
730 }
731
732 protected void testRecallToActionListAsInitiatorAfterApprovals(String doctype) {
733 testRecallToActionListAfterApprovals(EWESTFAL, EWESTFAL, doctype);
734 }
735
736
737
738 protected void testRecallToActionListAfterApprovals(String initiator, String recaller, String doctype) {
739 testRecallToActionListAfterApprovals(initiator, recaller, doctype, true);
740 }
741 protected void testRecallToActionListAfterApprovals(String initiator, String recaller, String doctype, boolean expect_recall_success) {
742 boolean notifyPreviousRecipients = !RECALL_TEST_DOC.equals(doctype);
743 boolean notifyPendingRecipients = !RECALL_NO_PENDING_NOTIFY_TEST_DOC.equals(doctype);
744 String[] thirdPartiesNotified = RECALL_NOTIFY_THIRDPARTY_TEST_DOC.equals(doctype) ? new String[] { "quickstart", "admin" } : new String[] {};
745
746 WorkflowDocument document = WorkflowDocumentFactory.createDocument(initiator, doctype);
747 document.route("");
748
749 WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).approve("");
750 WorkflowDocumentFactory.loadDocument(initiator, document.getDocumentId()).approve("");
751 WorkflowDocumentFactory.loadDocument(RKIRKEND, document.getDocumentId()).approve("");
752
753 document = WorkflowDocumentFactory.loadDocument(recaller, document.getDocumentId());
754 System.err.println(document.getValidActions().getValidActions());
755 if (expect_recall_success) {
756 assertTrue("recaller '" + recaller + "' should be able to RECALL", document.getValidActions().getValidActions().contains(ActionType.RECALL));
757 } else {
758 assertFalse("recaller '" + recaller + "' should NOT be able to RECALL", document.getValidActions().getValidActions().contains(ActionType.RECALL));
759 return;
760 }
761 document.recall("recalling", false);
762
763 assertTrue("Document should be saved", document.isSaved());
764 assertAfterActionTakenCalled(ActionType.RECALL, ActionType.COMPLETE);
765
766
767 assertTrue(document.isCompletionRequested());
768
769
770 assertEquals(notifyPendingRecipients, WorkflowDocumentFactory.loadDocument(NATJOHNS, document.getDocumentId()).isFYIRequested());
771
772 assertEquals(notifyPreviousRecipients, WorkflowDocumentFactory.loadDocument(RKIRKEND, document.getDocumentId()).isFYIRequested());
773
774 assertFalse(WorkflowDocumentFactory.loadDocument(initiator, document.getDocumentId()).isFYIRequested());
775
776 assertEquals(notifyPreviousRecipients, WorkflowDocumentFactory.loadDocument(JHOPF, document.getDocumentId()).isFYIRequested());
777
778 if (!ArrayUtils.isEmpty(thirdPartiesNotified)) {
779 for (String recipient: thirdPartiesNotified) {
780 assertTrue("Expected FYI to be sent to: " + recipient, WorkflowDocumentFactory.loadDocument(getPrincipalIdForName(recipient), document.getDocumentId()).isFYIRequested());
781 }
782 }
783
784
785 for (String user: new String[] { RKIRKEND, NATJOHNS }) {
786 WorkflowDocumentFactory.loadDocument(user, document.getDocumentId()).fyi();
787 }
788
789 document.complete("completing");
790
791 assertTrue("Document should be enroute", document.isEnroute());
792
793
794
795 for (String user: new String[] { JHOPF, RKIRKEND, NATJOHNS }) {
796 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
797 assertFalse(getPrincipalNameForId(user) + " should not have an FYI", document.isFYIRequested());
798 }
799
800
801 for (String user: new String[] { JHOPF, initiator, RKIRKEND, NATJOHNS, BMCGOUGH }) {
802 document = WorkflowDocumentFactory.loadDocument(user, document.getDocumentId());
803 assertTrue(getPrincipalNameForId(user) + " should have approval request", document.isApprovalRequested());
804 document.approve("approving");
805 }
806
807
808 assertTrue("Document should be processed", document.isProcessed());
809 assertTrue("Document should be approved", document.isApproved());
810
811 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
812 document.acknowledge("");
813
814 document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jthomas"), document.getDocumentId());
815 document.fyi();
816
817 assertTrue("Document should be approved", document.isApproved());
818 assertTrue("Document should be final", document.isFinal());
819 }
820 }