View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.routemanager;
18  
19  import java.util.Collection;
20  
21  import javax.xml.namespace.QName;
22  
23  import org.junit.Test;
24  import org.kuali.rice.kew.dto.ActionRequestDTO;
25  import org.kuali.rice.kew.dto.NetworkIdDTO;
26  import org.kuali.rice.kew.dto.RouteNodeInstanceDTO;
27  import org.kuali.rice.kew.exception.InvalidActionTakenException;
28  import org.kuali.rice.kew.exception.WorkflowException;
29  import org.kuali.rice.kew.messaging.MessageServiceNames;
30  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
31  import org.kuali.rice.kew.service.KEWServiceLocator;
32  import org.kuali.rice.kew.service.WorkflowDocument;
33  import org.kuali.rice.kew.service.WorkflowInfo;
34  import org.kuali.rice.kew.test.KEWTestCase;
35  import org.kuali.rice.kew.test.TestUtilities;
36  import org.kuali.rice.kim.bo.Group;
37  import org.kuali.rice.kim.service.KIMServiceLocator;
38  import org.kuali.rice.ksb.messaging.service.KSBXMLService;
39  
40  
41  public class ExceptionRoutingTest extends KEWTestCase {
42  
43      protected void loadTestData() throws Exception {
44          loadXmlFile("RouteManagerConfig.xml");
45      }
46  
47      protected void setUpAfterDataLoad() throws Exception {
48  		super.setUpAfterDataLoad();
49  		// reset these static constants, otherwise they will cause problems between test runs
50  		ExceptionRoutingTestPostProcessor.THROW_DO_ACTION_TAKEN_EXCEPTION = false;
51  		ExceptionRoutingTestPostProcessor.THROW_ROUTE_DELETE_ROUTE_HEADER_EXCEPTION = false;
52  		ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = false;
53  		ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_LEVEL_EXCEPTION = false;
54  		ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING = false;
55  		ExceptionRoutingTestPostProcessor.BLOW_UP_ON_TRANSITION_INTO_EXCEPTION = false;
56  	}
57  
58      @Test public void testSequentialExceptionRouting() throws Exception {
59          WorkflowDocument doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), "ExceptionRoutingSequentialDoc");
60          try {
61              doc.routeDocument("");
62              fail("should have thrown routing exception");
63          } catch (Exception e) {
64          }
65  
66          TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
67  
68          doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), doc.getRouteHeaderId());
69          assertTrue("Document should be in exception status", doc.stateIsException());
70          
71          WorkflowInfo info = new WorkflowInfo();
72          ActionRequestDTO[] actionRequests = info.getActionRequests(doc.getRouteHeaderId());
73  
74          assertEquals("Should be a single exception request", 1, actionRequests.length);
75          for (int i = 0; i < actionRequests.length; i++) {
76              ActionRequestDTO actionRequest = actionRequests[i];
77              Group group = KIMServiceLocator.getIdentityManagementService().getGroup(actionRequest.getGroupId());
78              assertTrue("Request should be an exception request.", actionRequest.isExceptionRequest());
79              assertTrue("Complete should be requested", actionRequest.isCompleteRequest());
80              assertTrue("Request should be a workgroup request", actionRequest.isGroupRequest());
81              assertEquals("Request should be to 'ExceptionRoutingGroup'", "ExceptionRoutingGroup", group.getGroupName());
82              assertNotNull("annotation cannot be null", actionRequest.getAnnotation());
83              assertFalse("annotation cannot be empty", "".equals(actionRequest.getAnnotation()));
84          }
85  
86      }
87      
88      /**
89       * This tests the solution for KULRICE-4493.  Essentially, the problem was that when the workflow engine
90       * would transition the document to exception status it would invoke the post processor.  If invoking
91       * the post processor raised an exception, that would cause the transaction to get rolled back and the
92       * document would get "stuck" in the ENROUTE state with no pending requests.
93       */
94  //    @Test public void testExceptionRouting_BlowUpOnStatusChangeToException() throws Exception {
95  //    	
96  //    	// first, configure the post processor so that it throws an exception when we call doRouteStatusChange on transition into exception status
97  //    	ExceptionRoutingTestPostProcessor.BLOW_UP_ON_TRANSITION_INTO_EXCEPTION = true;
98  //    	    	
99  //    	WorkflowDocument doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), "AlwaysExplodeTestDocument");
100 //    	try {
101 //    		doc.routeDocument("");
102 //    		fail("We should be in exception routing");
103 //    	} catch (Exception e) {
104 //    	}
105 //
106 //    	TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
107 //    	
108 //    	doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), doc.getRouteHeaderId());
109 //    	assertTrue("document should be in exception routing", doc.stateIsException());
110 //
111 //    }
112 
113 	@Test public void testInvalidActionsInExceptionRouting() throws Exception {
114         WorkflowDocument doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), "ExceptionRoutingSequentialDoc");
115         try {
116             doc.routeDocument("");
117             fail("should have thrown routing exception");
118         } catch (Exception e) {
119             log.info("Expected exception occurred: " + e);
120         }
121 
122         TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
123 
124         doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), doc.getRouteHeaderId());
125         assertTrue("Document should be in exception status", doc.stateIsException());
126 
127         try {
128             doc.routeDocument("routing a document that is in exception routing");
129             fail("Succeeded in routing document that is in exception routing");
130         } catch (InvalidActionTakenException iate) {
131             log.info("Expected exception occurred: " + iate);
132         } catch (WorkflowException we) {
133             fail("Attempt at routing document in exception routing succeeded, when it should have been an InvalidActionTakenException");
134         }
135     }
136 
137 	@Test public void testParallelExceptionRouting() throws Exception {
138         WorkflowDocument doc = new WorkflowDocument(new NetworkIdDTO("user1"), "ExceptionRoutingParallelDoc");
139         doc.routeDocument("");
140         doc = new WorkflowDocument(new NetworkIdDTO("ewestfal"), doc.getRouteHeaderId());
141         assertTrue("User should have an approve request", doc.isApprovalRequested());
142         doc = new WorkflowDocument(new NetworkIdDTO("bmcgough"), doc.getRouteHeaderId());
143         assertTrue("User should have an approve request", doc.isApprovalRequested());
144         RouteNodeInstanceDTO[] nodes = new WorkflowInfo().getActiveNodeInstances(doc.getRouteHeaderId());
145 
146         // at this point we should be at RouteNode1 and RouteNode3
147         assertEquals("There should be two active nodes", 2, nodes.length);
148         TestUtilities.assertAtNode(doc, "RouteNode1");
149         TestUtilities.assertAtNode(doc, "RouteNode3");
150 
151         try {
152             doc.approve("");
153             fail("should have generated routing exception");
154         } catch (Exception e) {
155         }
156 
157         TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
158         WorkflowInfo info = new WorkflowInfo();
159         ActionRequestDTO[] actionRequests = info.getActionRequests(doc.getRouteHeaderId());
160         RouteNodeInstanceDTO routeNode1 = null;
161         for (RouteNodeInstanceDTO nodeInstanceVO : nodes) {
162         	if (nodeInstanceVO.getName().equals("RouteNode1")) {
163         		routeNode1 = nodeInstanceVO;
164         	}
165         }
166         assertNotNull("Could not locate the routeNode1 node instance.", routeNode1);
167 
168         boolean hasCompleteRequest = false;
169         for (int i = 0; i < actionRequests.length; i++) {
170             ActionRequestDTO actionRequest = actionRequests[i];
171             if (actionRequest.isCompleteRequest()) {
172             	Group group = KIMServiceLocator.getIdentityManagementService().getGroup(actionRequest.getGroupId());
173                 assertTrue("Complete should be requested", actionRequest.isCompleteRequest());
174                 assertTrue("Request should be a workgroup request", actionRequest.isGroupRequest());
175                 assertNull("For exception routing, node instance should have a null id.", actionRequest.getNodeInstanceId());
176                 //assertEquals("Node instance id should be id of routeNode1", routeNode1.getRouteNodeInstanceId(), actionRequest.getNodeInstanceId());
177                 // routeMethod name should be null as well
178                 assertNull("Exception request routeMethodName wrong", actionRequest.getRouteMethodName());
179                 assertEquals("Request should be to 'ExceptionRoutingGroup'", "ExceptionRoutingGroup", group.getGroupName());
180                 hasCompleteRequest = true;
181             }
182         }
183         assertTrue("Document should have had a complete request", hasCompleteRequest);
184         ExplodingRuleAttribute.dontExplode=true;
185 
186         //there should be a single action item to our member of the exception workgroup
187         Collection actionItems = KEWServiceLocator.getActionListService().findByRouteHeaderId(doc.getRouteHeaderId());
188         assertEquals("There should only be action items for the member of our exception workgroup", 1, actionItems.size());
189 
190         doc = new WorkflowDocument(new NetworkIdDTO("user3"), doc.getRouteHeaderId());
191         assertTrue("Document should be routing for completion to member of exception workgroup", doc.isCompletionRequested());
192         assertTrue("Document should be in exception status", doc.stateIsException());
193         doc.complete("");
194 
195         doc = new WorkflowDocument(new NetworkIdDTO("bmcgough"), doc.getRouteHeaderId());
196         doc.approve("");
197 
198         doc = new WorkflowDocument(new NetworkIdDTO("ewestfal"), doc.getRouteHeaderId());
199         doc.approve("");
200 
201         doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), doc.getRouteHeaderId());
202         doc.approve("");
203 
204         doc = new WorkflowDocument(new NetworkIdDTO("jhopf"), doc.getRouteHeaderId());
205         doc.approve("");
206 
207         assertTrue("Document should be final", doc.stateIsFinal());
208     }
209 
210     /**
211      * this tests that the document appropriately gets to exception routing if there is a
212      * problem when transitioning out of first node
213      *
214      * @throws Exception
215      */
216     @Test public void testExceptionInTransitionFromStart() throws Exception {
217 
218     	WorkflowDocument doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), "AdhocTransitionTestDocument");
219     	//blow chunks transitioning out of adhoc to the first route node
220     	ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_LEVEL_EXCEPTION = true;
221 
222     	try {
223     		doc.routeDocument("");
224     		fail("We should be in exception routing");
225     	} catch (Exception e) {
226     	}
227 
228     	TestUtilities.getExceptionThreader().join();//this is necessary to ensure that the exception request will be generated.
229     	doc = new WorkflowDocument(new NetworkIdDTO("rkirkend"), doc.getRouteHeaderId());
230     	assertTrue("document should be in exception routing", doc.stateIsException());
231     }
232 
233     /**
234      * Test to verify the fix for KULWF-669.
235      *
236      * This tests that if we requeue an exception document (through the RouteQueueService) that it doesn't transition
237      * out of exception routing.  Then check that, if we complete it, it properly transitions out of exception routing.
238      */
239     @Test public void testRequeueOfExceptionDocument() throws Exception {
240     	WorkflowDocument document = new WorkflowDocument(new NetworkIdDTO("rkirkend"), "AdhocTransitionTestDocument");
241     	document.routeDocument("");
242         assertFalse("Document should not be in exception routing.", document.stateIsException());
243 
244         // in fact, at this point it should be routed to jhopf
245         document = new WorkflowDocument(new NetworkIdDTO("jhopf"), document.getRouteHeaderId());
246         assertTrue("Jhopf should have an approve.", document.isApprovalRequested());
247 
248         // let's tell it to blow up on level change
249         ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = true;
250         try {
251         	document.approve("");
252         	fail("We should be in exception routing");
253     	} catch (Exception e) {
254     	}
255 
256     	TestUtilities.waitForExceptionRouting();
257     	document = new WorkflowDocument(new NetworkIdDTO("rkirkend"), document.getRouteHeaderId());
258     	assertTrue("document should be in exception routing", document.stateIsException());
259 
260     	// now requeue the document it should stay at exception routing and the status change callback should not
261     	// indicate a transition out of exception routing (this is to make sure it's not going out of exception
262     	// routing and then right back in)
263     	ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = false;
264     	assertFalse("Should not have transitioned out of exception routing yet.", ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING);
265     	// the requeue here should happen synchronously because we are using the SynchronousRouteQueue
266     	DocumentRouteHeaderValue routeHeaderValue = KEWServiceLocator.getRouteHeaderService().getRouteHeader(document.getRouteHeaderId());
267     	QName documentServiceName = new QName(routeHeaderValue.getDocumentType().getServiceNamespace(), MessageServiceNames.DOCUMENT_ROUTING_SERVICE);
268     	KSBXMLService routeDocumentMessageService = (KSBXMLService)MessageServiceNames.getServiceAsynchronously(documentServiceName, routeHeaderValue);
269     	routeDocumentMessageService.invoke(String.valueOf(document.getRouteHeaderId()));
270 
271 //    	SpringServiceLocator.getMessageHelper().sendMessage(MessageServiceNames.DOCUMENT_ROUTING_SERVICE, String.valueOf(document.getRouteHeaderId()), routeHeaderValue);
272 
273     	// the document should still be in exception routing
274     	document = new WorkflowDocument(new NetworkIdDTO("rkirkend"), document.getRouteHeaderId());
275     	assertTrue("document should be in exception routing", document.stateIsException());
276         assertFalse("document shouldn't have transitioned out of exception routing.", ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING);
277 
278         // now turn status change exceptions off and complete the exception request
279         ExceptionRoutingTestPostProcessor.THROW_ROUTE_STATUS_CHANGE_EXCEPTION = false;
280         assertTrue("rkirkend should be in the exception workgroup.", document.isCompletionRequested());
281         document.complete("Completing out of exception routing.");
282 
283         // 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
284         // Approve never actually took place because the transaction was rolled back (because of the exception in the post process).  Therefore, we still
285         // need to take action as him again to push the document to FINAL
286         document = new WorkflowDocument(new NetworkIdDTO("jhopf"), document.getRouteHeaderId());
287         assertTrue(document.isApprovalRequested());
288         document.approve("");
289 
290         // document should now be FINAL
291         assertTrue("Document should be FINAL.", document.stateIsFinal());
292 
293         // the status change out of exception routing should have happened
294         assertTrue("Document should have transitioned out of exception routing.", ExceptionRoutingTestPostProcessor.TRANSITIONED_OUT_OF_EXCEPTION_ROUTING);
295     }
296 
297 }