View Javadoc
1   /**
2    * Copyright 2005-2014 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.routemanager;
17  
18  import org.junit.Test;
19  import org.kuali.rice.kew.api.KewApiServiceLocator;
20  import org.kuali.rice.kew.api.WorkflowDocument;
21  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
22  import org.kuali.rice.kew.api.action.ActionRequest;
23  import org.kuali.rice.kew.api.action.InvalidActionTakenException;
24  import org.kuali.rice.kew.api.document.DocumentProcessingQueue;
25  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
26  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
27  import org.kuali.rice.kew.service.KEWServiceLocator;
28  import org.kuali.rice.kew.test.KEWTestCase;
29  import org.kuali.rice.kew.test.TestUtilities;
30  import org.kuali.rice.kim.api.group.Group;
31  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
32  import org.kuali.rice.ksb.messaging.service.KSBXMLService;
33  import org.kuali.rice.test.BaselineTestCase;
34  
35  import javax.xml.namespace.QName;
36  import java.util.Collection;
37  import java.util.List;
38  
39  import static org.junit.Assert.*;
40  
41  @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.CLEAR_DB)
42  public class ExceptionRoutingTest extends KEWTestCase {
43  
44      protected void loadTestData() throws Exception {
45          loadXmlFile("RouteManagerConfig.xml");
46      }
47  
48      protected void setUpAfterDataLoad() throws Exception {
49  		super.setUpAfterDataLoad();
50  		// reset these static constants, otherwise they will cause problems between test runs
51  		ExceptionRoutingTestPostProcessor.THROW_DO_ACTION_TAKEN_EXCEPTION = false;
52  		ExceptionRoutingTestPostProcessor.THROW_ROUTE_DELETE_ROUTE_HEADER_EXCEPTION = false;
53  		ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = false;
54  		ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_LEVEL_EXCEPTION = false;
55  		ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING = false;
56  		ExceptionRoutingTestPostProcessor.BLOW_UP_ON_TRANSITION_INTO_EXCEPTION = false;
57  	}
58  
59      @Test public void testSequentialExceptionRouting() throws Exception {
60          WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "ExceptionRoutingSequentialDoc");
61          try {
62              doc.route("");
63              fail("should have thrown routing exception");
64          } catch (Exception e) {
65          }
66  
67          TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
68  
69          doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), doc.getDocumentId());
70          assertTrue("Document should be in exception status", doc.isException());
71  
72          List<ActionRequest> actionRequests = KewApiServiceLocator.getWorkflowDocumentService().getRootActionRequests(doc.getDocumentId());
73  
74          assertEquals("Should be a single exception request", 1, actionRequests.size());
75          for (ActionRequest actionRequest : actionRequests) {
76              Group group = KimApiServiceLocator.getGroupService().getGroup(actionRequest.getGroupId());
77              assertTrue("Request should be an exception request.", actionRequest.isExceptionRequest());
78              assertTrue("Complete should be requested", actionRequest.isCompleteRequest());
79              assertTrue("Request should be a workgroup request", actionRequest.isGroupRequest());
80              assertEquals("Request should be to 'ExceptionRoutingGroup'", "ExceptionRoutingGroup", group.getName());
81              assertNotNull("annotation cannot be null", actionRequest.getAnnotation());
82              assertFalse("annotation cannot be empty", "".equals(actionRequest.getAnnotation()));
83          }
84  
85      }
86      
87      /**
88       * This tests the solution for KULRICE-4493.  Essentially, the problem was that when the workflow engine
89       * would transition the document to exception status it would invoke the post processor.  If invoking
90       * the post processor raised an exception, that would cause the transaction to get rolled back and the
91       * document would get "stuck" in the ENROUTE state with no pending requests.
92       */
93  //    @Test public void testExceptionRouting_BlowUpOnStatusChangeToException() throws Exception {
94  //    	
95  //    	// first, configure the post processor so that it throws an exception when we call doRouteStatusChange on transition into exception status
96  //    	ExceptionRoutingTestPostProcessor.BLOW_UP_ON_TRANSITION_INTO_EXCEPTION = true;
97  //    	    	
98  //    	WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "AlwaysExplodeTestDocument");
99  //    	try {
100 //    		doc.route("");
101 //    		fail("We should be in exception routing");
102 //    	} catch (Exception e) {
103 //    	}
104 //
105 //    	TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
106 //    	
107 //    	doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), doc.getDocumentId());
108 //    	assertTrue("document should be in exception routing", doc.isException());
109 //
110 //    }
111 
112 	@Test public void testInvalidActionsInExceptionRouting() throws Exception {
113         WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "ExceptionRoutingSequentialDoc");
114         try {
115             doc.route("");
116             fail("should have thrown routing exception");
117         } catch (Exception e) {
118             log.info("Expected exception occurred: " + e);
119         }
120 
121         TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
122 
123         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), doc.getDocumentId());
124         assertTrue("Document should be in exception status", doc.isException());
125 
126         try {
127             doc.route("routing a document that is in exception routing");
128             fail("Succeeded in routing document that is in exception routing");
129         } catch (InvalidActionTakenException iate) {
130             log.info("Expected exception occurred: " + iate);
131         }
132     }
133 
134 	@Test public void testParallelExceptionRouting() throws Exception {
135         WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("user1"), "ExceptionRoutingParallelDoc");
136         doc.route("");
137         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("ewestfal"), doc.getDocumentId());
138         assertTrue("User should have an approve request", doc.isApprovalRequested());
139         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), doc.getDocumentId());
140         assertTrue("User should have an approve request", doc.isApprovalRequested());
141         List<RouteNodeInstance> nodes = KewApiServiceLocator.getWorkflowDocumentService().getActiveRouteNodeInstances(
142                 doc.getDocumentId());
143 
144         // at this point we should be at RouteNode1 and RouteNode3
145         assertEquals("There should be two active nodes", 2, nodes.size());
146         TestUtilities.assertAtNode(doc, "RouteNode1");
147         TestUtilities.assertAtNode(doc, "RouteNode3");
148 
149         try {
150             doc.approve("");
151             fail("should have generated routing exception");
152         } catch (Exception e) {
153         }
154 
155         TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
156         List<ActionRequest> actionRequests = KewApiServiceLocator.getWorkflowDocumentService().getRootActionRequests(doc.getDocumentId());
157         RouteNodeInstance routeNode1 = null;
158         for (RouteNodeInstance nodeInstanceVO : nodes) {
159         	if (nodeInstanceVO.getName().equals("RouteNode1")) {
160         		routeNode1 = nodeInstanceVO;
161         	}
162         }
163         assertNotNull("Could not locate the routeNode1 node instance.", routeNode1);
164 
165         boolean hasCompleteRequest = false;
166         for (ActionRequest actionRequest : actionRequests) {
167             if (actionRequest.isCompleteRequest()) {
168             	Group group = KimApiServiceLocator.getGroupService().getGroup(actionRequest.getGroupId());
169                 assertTrue("Complete should be requested", actionRequest.isCompleteRequest());
170                 assertTrue("Request should be a workgroup request", actionRequest.isGroupRequest());
171                 assertNull("For exception routing, node instance should have a null id.", actionRequest.getRouteNodeInstanceId());
172                 //assertEquals("Node instance id should be id of routeNode1", routeNode1.getRouteNodeInstanceId(), actionRequest.getNodeInstanceId());
173                 assertEquals("Request should be to 'ExceptionRoutingGroup'", "ExceptionRoutingGroup", group.getName());
174                 hasCompleteRequest = true;
175             }
176         }
177         assertTrue("Document should have had a complete request", hasCompleteRequest);
178         ExplodingRuleAttribute.dontExplode=true;
179 
180         //there should be a single action item to our member of the exception workgroup
181         Collection actionItems = KEWServiceLocator.getActionListService().findByDocumentId(doc.getDocumentId());
182         assertEquals("There should only be action items for the member of our exception workgroup", 1, actionItems.size());
183 
184         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("user3"), doc.getDocumentId());
185         assertTrue("Document should be routing for completion to member of exception workgroup", doc.isCompletionRequested());
186         assertTrue("Document should be in exception status", doc.isException());
187         doc.complete("");
188 
189         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), doc.getDocumentId());
190         doc.approve("");
191 
192         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("ewestfal"), doc.getDocumentId());
193         doc.approve("");
194 
195         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), doc.getDocumentId());
196         doc.approve("");
197 
198         doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jhopf"), doc.getDocumentId());
199         doc.approve("");
200 
201         assertTrue("Document should be final", doc.isFinal());
202     }
203 
204     /**
205      * this tests that the document appropriately gets to exception routing if there is a
206      * problem when transitioning out of first node
207      *
208      * @throws Exception
209      */
210     @Test public void testExceptionInTransitionFromStart() throws Exception {
211 
212     	WorkflowDocument doc = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "AdhocTransitionTestDocument");
213     	//blow chunks transitioning out of adhoc to the first route node
214     	ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_LEVEL_EXCEPTION = true;
215 
216     	try {
217     		doc.route("");
218     		fail("We should be in exception routing");
219     	} catch (Exception e) {
220     	}
221 
222     	TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
223     	doc = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), doc.getDocumentId());
224     	assertTrue("document should be in exception routing", doc.isException());
225     }
226 
227     /**
228      * Test to verify the fix for KULWF-669.
229      *
230      * This tests that if we requeue an exception document (through the RouteQueueService) that it doesn't transition
231      * out of exception routing.  Then check that, if we complete it, it properly transitions out of exception routing.
232      */
233     @Test public void testRequeueOfExceptionDocument() throws Exception {
234     	WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "AdhocTransitionTestDocument");
235     	document.route("");
236         assertFalse("Document should not be in exception routing.", document.isException());
237 
238         // in fact, at this point it should be routed to jhopf
239         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jhopf"), document.getDocumentId());
240         assertTrue("Jhopf should have an approve.", document.isApprovalRequested());
241 
242         // let's tell it to blow up on level change
243         ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = true;
244         try {
245         	document.approve("");
246         	fail("We should be in exception routing");
247     	} catch (Exception e) {
248     	}
249 
250     	TestUtilities.waitForExceptionRouting();
251     	document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), document.getDocumentId());
252     	assertTrue("document should be in exception routing", document.isException());
253 
254     	// now requeue the document it should stay at exception routing and the status change callback should not
255     	// indicate a transition out of exception routing (this is to make sure it's not going out of exception
256     	// routing and then right back in)
257     	ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = false;
258     	assertFalse("Should not have transitioned out of exception routing yet.", ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING);
259     	// the requeue here should happen synchronously because we are using the SynchronousRouteQueue
260     	DocumentRouteHeaderValue routeHeaderValue = KEWServiceLocator.getRouteHeaderService().getRouteHeader(document.getDocumentId());
261         String applicationId = routeHeaderValue.getDocumentType().getApplicationId();
262         DocumentProcessingQueue documentProcessingQueue = KewApiServiceLocator.getDocumentProcessingQueue(document.getDocumentId(), applicationId);
263     	documentProcessingQueue.process(String.valueOf(document.getDocumentId()));
264 
265     	// the document should still be in exception routing
266     	document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), document.getDocumentId());
267     	assertTrue("document should be in exception routing", document.isException());
268         assertFalse("document shouldn't have transitioned out of exception routing.", ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING);
269 
270         // now turn status change exceptions off and complete the exception request
271         ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = false;
272         assertTrue("rkirkend should be in the exception workgroup.", document.isCompletionRequested());
273         document.complete("Completing out of exception routing.");
274 
275         // Note: The behavior here will be a bit different then in a real setting because in these tests the route queue is synchronous so jhopf's original
276         // Approve never actually took place because the transaction was rolled back (because of the exception in the post process).  Therefore, we still
277         // need to take action as him again to push the document to FINAL
278         document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("jhopf"), document.getDocumentId());
279         assertTrue(document.isApprovalRequested());
280         document.approve("");
281 
282         // document should now be FINAL
283         assertTrue("Document should be FINAL.", document.isFinal());
284 
285         // the status change out of exception routing should have happened
286         assertTrue("Document should have transitioned out of exception routing.", ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING);
287     }
288 
289 }