001    /**
002     * Copyright 2005-2011 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.engine;
017    
018    
019    import static org.junit.Assert.assertEquals;
020    import static org.junit.Assert.assertNotNull;
021    import static org.junit.Assert.assertTrue;
022    
023    import java.util.Collection;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Set;
027    
028    import org.junit.Test;
029    import org.kuali.rice.kew.actionrequest.ActionRequestValue;
030    import org.kuali.rice.kew.api.WorkflowDocument;
031    import org.kuali.rice.kew.api.WorkflowDocumentFactory;
032    import org.kuali.rice.kew.api.action.ActionRequestStatus;
033    import org.kuali.rice.kew.engine.node.RouteNodeInstance;
034    import org.kuali.rice.kew.service.KEWServiceLocator;
035    import org.kuali.rice.kew.test.KEWTestCase;
036    import org.kuali.rice.kew.api.KewApiConstants;
037    
038    public class ParallelRoutingTest extends KEWTestCase {
039        
040        private static final String DOCUMENT_TYPE_NAME = "ParallelDocType";
041        private static final String PARALLEL_EMPTY_DOCUMENT_TYPE_NAME = "ParallelEmptyDocType";
042        private static final String PARALLEL_EMPTY_DOCUMENT_TYPE_2_NAME = "ParallelEmptyDocType2";
043        private static final String ACKNOWLEDGE_1_NODE = "Acknowledge1";
044        private static final String WORKFLOW_DOCUMENT_2_NODE = "WorkflowDocument2";
045        private static final String WORKFLOW_DOCUMENT_3_NODE = "WorkflowDocument3";
046        private static final String JOIN_NODE = "Join";
047        private static final String WORKFLOW_DOCUMENT_FINAL_NODE = "WorkflowDocumentFinal";
048            
049        protected void loadTestData() throws Exception {
050            loadXmlFile("EngineConfig.xml");
051        }
052    
053        @Test public void testParallelRoute() throws Exception {
054            WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), DOCUMENT_TYPE_NAME);
055            document.saveDocumentData();
056            assertTrue("Document should be initiated", document.isInitiated());
057            assertEquals("Should be no action requests.", 0, document.getRootActionRequests().size());
058            Collection nodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
059            assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
060            document.route("Routing for parallel");
061            
062            // should have generated a request to "bmcgough"
063            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), document.getDocumentId());
064            assertTrue("Document should be enroute", document.isEnroute());
065            List actionRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
066            assertEquals("Incorrect pending action requests.", 1, actionRequests.size());
067            ActionRequestValue bRequest = (ActionRequestValue)actionRequests.get(0);
068            assertNotNull("Should have been routed through node instance.", bRequest.getNodeInstance());
069            assertTrue(document.isApprovalRequested());
070            
071            document.approve("Approving test");
072            
073            // document should split at this point and generate an ack to temay and approves to rkirkend and pmckown
074            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), document.getDocumentId());
075            assertTrue("Document should be enroute", document.isEnroute());
076            actionRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
077            assertEquals("Incorrect pending action requests.", 3, actionRequests.size());
078            boolean isToTemay = false;
079            boolean isToPmckown = false;
080            boolean isToRkirkend = false;
081            for (Iterator iterator = actionRequests.iterator(); iterator.hasNext();) {
082                ActionRequestValue actionRequest = (ActionRequestValue) iterator.next();
083                if (actionRequest.getPrincipalId().equals(getPrincipalIdForName("temay"))) {
084                    isToTemay = true;
085                    assertEquals("Request should be activated.", ActionRequestStatus.ACTIVATED.getCode(), actionRequest.getStatus());
086                    assertEquals("Wrong action requested.", KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, actionRequest.getActionRequested());
087                    assertNotNull("Should have been routed through node instance.", actionRequest.getNodeInstance());
088                    assertEquals("Invalid node.", ACKNOWLEDGE_1_NODE, actionRequest.getNodeInstance().getRouteNode().getRouteNodeName());
089                }
090                if (actionRequest.getPrincipalId().equals(getPrincipalIdForName("rkirkend"))) {
091                    isToRkirkend = true;
092                    assertEquals("Request should be activated.", ActionRequestStatus.ACTIVATED.getCode(), actionRequest.getStatus());
093                    assertEquals("Wrong action requested.", KewApiConstants.ACTION_REQUEST_APPROVE_REQ, actionRequest.getActionRequested());
094                    assertNotNull("Should have been routed through node instance.", actionRequest.getNodeInstance());
095                    assertEquals("Invalid node.", WORKFLOW_DOCUMENT_2_NODE, actionRequest.getNodeInstance().getRouteNode().getRouteNodeName());
096                }
097                if (actionRequest.getPrincipalId().equals(getPrincipalIdForName("pmckown"))) {
098                    isToPmckown = true;
099                    assertEquals("Request should be activated.", ActionRequestStatus.ACTIVATED.getCode(), actionRequest.getStatus());
100                    assertEquals("Wrong action requested.", KewApiConstants.ACTION_REQUEST_APPROVE_REQ, actionRequest.getActionRequested());
101                    assertNotNull("Should have been routed through node instance.", actionRequest.getNodeInstance());
102                    assertEquals("Invalid node.", WORKFLOW_DOCUMENT_3_NODE, actionRequest.getNodeInstance().getRouteNode().getRouteNodeName());
103                }
104            }
105            assertTrue("No request to temay.", isToTemay);
106            assertTrue("No request to pmckown.", isToPmckown);
107            assertTrue("No request to rkirkend.", isToRkirkend);
108            
109            // check that we are at both nodes, one in each branch
110            Set<String> nodeNames = document.getNodeNames();
111            assertEquals("Wrong number of node names.", 2, nodeNames.size() );
112            boolean isNode2 = false;
113            boolean isNode3 = false;
114            for (String name : nodeNames) {
115                if (name.equals(WORKFLOW_DOCUMENT_2_NODE)) {
116                    isNode2 = true;
117                }
118                if (name.equals(WORKFLOW_DOCUMENT_3_NODE)) {
119                    isNode3 = true;
120                }
121            }
122            assertTrue("Not at node2.", isNode2);
123            assertTrue("Not at node3.", isNode3);
124            nodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
125            assertEquals("Wrong number of active nodes.", 2, nodeInstances.size());
126            Iterator iterator = nodeInstances.iterator();
127            RouteNodeInstance instance1 = (RouteNodeInstance)iterator.next();
128            RouteNodeInstance instance2 = (RouteNodeInstance)iterator.next();
129            assertNotNull("Node should be in branch.", instance1.getBranch());
130            assertNotNull("Node should be in branch.", instance2.getBranch());
131            assertTrue("Branches should be different.", !instance1.getBranch().getBranchId().equals(instance2.getBranch().getBranchId()));
132            
133            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), document.getDocumentId());
134            assertTrue("Should have request.", document.isApprovalRequested());
135            document.approve("Git-r-dun");
136            
137            nodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
138            assertEquals("Wrong number of active nodes.", 2, nodeInstances.size());
139            boolean isAtJoin = false;
140            boolean isAtWD3 = false;
141            for (Iterator iter = nodeInstances.iterator(); iter.hasNext();) {
142                RouteNodeInstance nodeInstance = (RouteNodeInstance) iter.next();
143                if (nodeInstance.getRouteNode().getRouteNodeName().equals(JOIN_NODE)) {
144                    assertEquals("Join branch should be split branch.", instance1.getBranch().getParentBranch().getBranchId(), nodeInstance.getBranch().getBranchId());
145                    isAtJoin = true;
146                }
147                if (nodeInstance.getRouteNode().getRouteNodeName().equals(WORKFLOW_DOCUMENT_3_NODE)) {
148                    isAtWD3 = true;
149                }
150            }
151            assertTrue("Not at join", isAtJoin);
152            assertTrue("Not at WD3", isAtWD3);
153            
154            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("pmckown"), document.getDocumentId());
155            assertTrue("Should have request.", document.isApprovalRequested());
156            document.approve("Do it.");
157            
158            nodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
159            assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
160            boolean isAtWDF = false;
161            for (Iterator iter = nodeInstances.iterator(); iter.hasNext();) {
162                RouteNodeInstance nodeInstance = (RouteNodeInstance) iter.next();
163                if (nodeInstance.getRouteNode().getRouteNodeName().equals(WORKFLOW_DOCUMENT_FINAL_NODE)) {
164                    isAtWDF = true;
165                }
166            }
167            assertTrue("Not at WDF", isAtWDF);
168            
169            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
170            assertTrue("Should still be enroute.", document.isEnroute());
171            assertTrue("Should have request.", document.isApprovalRequested());
172            document.approve("I'm the last approver");
173            
174            assertTrue("Document should be processed.", document.isProcessed());
175            nodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
176            //commented out because the final RouteNodeInstance is now not active when the doc goes final
177    //        assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
178    //        isAtWDF = false;
179    //        for (Iterator iter = nodeInstances.iterator(); iter.hasNext();) {
180    //            RouteNodeInstance nodeInstance = (RouteNodeInstance) iter.next();
181    //            if (nodeInstance.getRouteNode().getRouteNodeName().equals(WORKFLOW_DOCUMENT_FINAL_NODE)) {
182    //                isAtWDF = true;
183    //            }
184    //        }
185    //        assertTrue("Not at WDF", isAtWDF);
186            
187            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("temay"), document.getDocumentId());
188            assertTrue("Should have request.", document.isAcknowledgeRequested());
189            document.acknowledge("");
190            assertTrue(document.isFinal());
191        }
192        
193        /**
194         * Tests that the document route past the join properly when there are parallel branches that don't generate requests.
195         * This was coded in response to a bug found while testing with ERA in order to track it down and fix it.
196         */
197        @Test public void testEmptyParallelBranches() throws Exception {
198            
199            WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), PARALLEL_EMPTY_DOCUMENT_TYPE_NAME);
200            document.saveDocumentData();
201            assertTrue("Document should be initiated", document.isInitiated());
202            assertEquals("Should be no action requests.", 0, document.getRootActionRequests().size());
203            Collection<? extends Object> nodeInstances = KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
204            assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
205            document.route("");
206            
207            // should have generated a request to "bmcgough"
208            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), document.getDocumentId());
209            assertTrue("Document should be enroute", document.isEnroute());
210            List actionRequests = KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
211            assertEquals("Incorrect pending action requests.", 1, actionRequests.size());
212            ActionRequestValue bRequest = (ActionRequestValue)actionRequests.get(0);
213            assertNotNull("Should have been routed through node instance.", bRequest.getNodeInstance());
214            assertTrue(document.isApprovalRequested());
215            
216            document.approve("");
217            
218            // now the document should have split, passed through nodes in each branch which didn't generate requests,
219            // and then passed the join node and generated requests at WorkflowDocumentFinal
220            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
221            assertTrue("Document should be enroute", document.isEnroute());
222            assertTrue(document.isApprovalRequested());
223            
224        }
225        
226        /**
227         * This runs the test with the adhoc approvers branch second instead of first
228         *//*
229        public void testEmptyParallelBranchesSwitched() throws Exception {
230            
231            WorkflowDocument document = WorkflowDocumentFactory.createDocument(new NetworkIdVO("ewestfal"), PARALLEL_EMPTY_DOCUMENT_TYPE_2_NAME);
232            document.saveDocumentData();
233            assertTrue("Document should be initiated", document.isInitiated());
234            assertEquals("Should be no action requests.", 0, document.getActionRequests().length);
235            assertEquals("Invalid route level.", new Integer(0), document.getRouteHeader().getDocRouteLevel());
236            Collection nodeInstances = SpringServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
237            assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
238            document.route("");
239            
240            // should have generated a request to "bmcgough"
241            document = WorkflowDocumentFactory.loadDocument(new NetworkIdVO("bmcgough"), document.getDocumentId());
242            assertTrue("Document should be enroute", document.isEnroute());
243            List actionRequests = TestUtilities.getActionRequestService().findPendingByDoc(document.getDocumentId());
244            assertEquals("Incorrect pending action requests.", 1, actionRequests.size());
245            ActionRequestValue bRequest = (ActionRequestValue)actionRequests.get(0);
246            assertNotNull("Should have been routed through node instance.", bRequest.getRouteNodeInstance());
247            assertTrue(document.isApprovalRequested());
248            
249            document.approve("");
250            
251            // now the document should have split, passed through nodes in each branch which didn't generate requests,
252            // and then passed the join node and generated requests at WorkflowDocumentFinal
253            document = WorkflowDocumentFactory.loadDocument(new NetworkIdVO("xqi"), document.getDocumentId());
254            assertTrue("Document should be enroute", document.isEnroute());
255            assertTrue(document.isApprovalRequested());
256            
257        }*/
258        
259        @Test public void testAdhocApproversJoinScenario() throws Exception {
260            WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "AdHocApproversDocType");
261            document.route("");
262            
263            // should send an approve to bmcgough
264            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("bmcgough"), document.getDocumentId());
265            assertTrue("Bmcgough should have approve request.", document.isApprovalRequested());
266            document.approve("");
267            
268            // at this point the document should pass the split, and end up at the WorkflowDocument2 node and the AdHocApproversJoin node
269            // after bypassing the AdHocJoinPoint
270            Set<String> nodeNames = document.getNodeNames();
271            assertEquals("There should be two node names.", 2, nodeNames.size());
272            assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("WorkflowDocument2"));
273            assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("AdHocApproversJoin"));
274            
275            // pmckown has the request at the adhoc approvers node, if we approve as him then the document should _not_ transition out
276            // of it's current nodes
277            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("pmckown"), document.getDocumentId());
278            assertTrue("Pmckown should have approve request.", document.isApprovalRequested());
279            document.approve("");
280            
281            // the document should still be at the same nodes
282            nodeNames = document.getNodeNames();
283            assertEquals("There should be two node names.", 2, nodeNames.size());
284            assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("WorkflowDocument2"));
285            assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("AdHocApproversJoin"));
286        
287            // at WorkflowDocument2, rkirkend is the approver, if we approve as him we should end up at the WorkflowDocumentFinal node
288            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("rkirkend"), document.getDocumentId());
289            assertTrue("Rkirkend should have approve request.", document.isApprovalRequested());
290            document.approve("");
291            
292            // the document should now be at WorkflowDocumentFinal with a request to xqi
293            nodeNames = document.getNodeNames();
294            assertEquals("There should be one node name.", 1, nodeNames.size());
295            assertTrue("Should be at WorkflowDocumentFinal node.", nodeNames.contains("WorkflowDocumentFinal"));
296            
297            document = WorkflowDocumentFactory.loadDocument(getPrincipalIdForName("xqi"), document.getDocumentId());
298            assertTrue("Document should still be enroute.", document.isEnroute());
299            document.approve("");
300            assertTrue("Document should now be final.", document.isFinal());
301            
302        }
303        
304    }