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.routeheader;
17  
18  import org.junit.Test;
19  import org.kuali.rice.kew.api.KewApiConstants;
20  import org.kuali.rice.kew.api.WorkflowDocument;
21  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
22  import org.kuali.rice.kew.api.document.DocumentStatus;
23  import org.kuali.rice.kew.doctype.bo.DocumentType;
24  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
25  import org.kuali.rice.kew.service.KEWServiceLocator;
26  import org.kuali.rice.kew.test.KEWTestCase;
27  import org.kuali.rice.test.BaselineTestCase;
28  import org.springframework.transaction.TransactionStatus;
29  import org.springframework.transaction.support.TransactionCallback;
30  
31  import java.sql.Timestamp;
32  import java.util.concurrent.CountDownLatch;
33  import java.util.concurrent.TimeUnit;
34  
35  import static org.junit.Assert.*;
36  
37  @BaselineTestCase.BaselineMode(BaselineTestCase.Mode.NONE)
38  public class RouteHeaderServiceTest extends KEWTestCase {
39  
40      private RouteHeaderService routeHeaderService;
41  
42      protected void setUpAfterDataLoad() throws Exception {
43          super.setUpAfterDataLoad();
44          routeHeaderService = KEWServiceLocator.getRouteHeaderService();
45      }
46  
47      /**
48       * Tests that getRoutedByDisplayName works in it's various cases
49       */
50      @Test
51      public void testGetRoutedByDisplayName() {
52          DocumentRouteHeaderValue document = new DocumentRouteHeaderValue();
53  
54          // when there is no routed by user and the doc is not enroute, it should return empty string
55          assertEquals("", document.getRoutedByDisplayName());
56  
57          // switch the document to enroute status, should still return empty string because we don't have an initiator id yet
58          document.setDocRouteStatus(DocumentStatus.ENROUTE.getCode());
59          assertEquals("", document.getRoutedByDisplayName());
60  
61          // now set an initiator, it should use that instead when displaying the routed by display name
62          // also, not sure why but for whatever reason KIM adds an extra space to the end of the display name (maybe because of missing middle name?)
63          document.setInitiatorWorkflowId(getPrincipalIdForName("ewestfal"));
64          assertEquals("Westfall, Eric", document.getRoutedByDisplayName());
65  
66          // yeah it's weird, but for some reason it only falls back to initiator id if the document is enroute, let's
67          // switch it back to initiated and make sure it returns empty string again
68          document.setDocRouteStatus(DocumentStatus.INITIATED.getCode());
69          assertEquals("", document.getRoutedByDisplayName());
70  
71          // now let's set that routed by id! also switch it back to ENROUTE status and make sure it still gives us the
72          // routed by user and not the initiator
73          document.setRoutedByUserWorkflowId(getPrincipalIdForName("administrator"));
74          assertEquals("administrator, administrator", document.getRoutedByDisplayName());
75          document.setDocRouteStatus(DocumentStatus.ENROUTE.getCode());
76          assertEquals("administrator, administrator", document.getRoutedByDisplayName());
77  
78      }
79  
80      /**
81       * Tests the saving of a document with large XML content.  This verifies that large CLOBs (> 4000 bytes)
82       * can be saved by OJB.  This can cause paticular issues with Oracle and OJB has to unwrap the native jdbc
83       * Connections and Statements from the pooled connection.  We need to make sure this is working for our
84       * pooling software of choice.
85       */
86      @Test
87      public void testLargeDocumentContent() throws Exception {
88          StringBuffer buffer = new StringBuffer();
89          buffer.append("<content>");
90          for (int index = 0; index < 10000; index++) {
91              buffer.append("abcdefghijklmnopqrstuvwxyz");
92          }
93          buffer.append("</content>");
94          DocumentRouteHeaderValue document = new DocumentRouteHeaderValue();
95          document.setDocContent(buffer.toString());
96          document.setDocRouteStatus(KewApiConstants.ROUTE_HEADER_INITIATED_CD);
97          document.setDocRouteLevel(0);
98          document.setDateModified(new Timestamp(System.currentTimeMillis()));
99          document.setCreateDate(new Timestamp(System.currentTimeMillis()));
100         document.setInitiatorWorkflowId("1");
101         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName("TestDocumentType");
102         assertNotNull(documentType);
103         document.setDocumentTypeId(documentType.getDocumentTypeId());
104         document = routeHeaderService.saveRouteHeader(document);
105         assertNotNull("Document was saved, it should have an ID now.", document.getDocumentId());
106         
107         // now reload from database and verify it's the right size
108         document = routeHeaderService.getRouteHeader(document.getDocumentId());
109         String docContent = document.getDocContent();
110         assertEquals("Doc content should be the same size as original string buffer.", buffer.length(), docContent.length());
111         assertTrue("Should be greater than about 5000 bytes.", docContent.getBytes().length > 5000);
112     }
113 
114     @Test public void testGetApplicationIdByDocumentId() throws Exception {
115     	WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestDocumentType2");
116     	String documentId = document.getDocumentId();
117     	String applicationId = routeHeaderService.getApplicationIdByDocumentId(documentId);
118     	assertEquals("applicationId should be KEWNEW", "KEWNEW", applicationId);
119 
120     	// now check TestDocumentType
121     	document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("ewestfal"), "TestDocumentType");
122     	documentId = document.getDocumentId();
123     	applicationId = routeHeaderService.getApplicationIdByDocumentId(documentId);
124     	assertEquals("applicationId should be KUALI", "KUALI", applicationId);
125     }
126 
127     @Test public void testLockRouteHeader() throws Exception {
128 
129         long timeout = 60 * 1000;
130 
131     	WorkflowDocument document = WorkflowDocumentFactory.createDocument(getPrincipalIdForName("rkirkend"), "TestDocumentType");
132     	document.saveDocumentData();
133     	String documentId = document.getDocumentId();
134 
135         final Locker locker1 = new Locker(documentId);
136         locker1.start();
137         locker1.latch1.await(timeout, TimeUnit.MILLISECONDS);
138 
139         // the locker show now be waiting on the second latch
140         assertTrue(locker1.waiting);
141         assertFalse(locker1.completed);
142 
143         // now start a second locker thread to attempt to lock the document as well, it should end up getting blocked
144         // from locking
145         final Locker locker2 = new Locker(documentId);
146         locker2.start();
147 
148         // the thread has been started, let's give it a little bit of time to attempt to lock the doc, it should get
149         // blocked in the select ... for update
150         Thread.sleep(2000);
151 
152         // this locker should essentially be blocked on the call to lock the route header
153         assertTrue(locker2.prelock);
154         assertFalse(locker2.waiting);
155 
156         // now, release the first lock
157         locker1.latch2.countDown();
158         locker1.join(timeout);
159 
160         // at this point locker1 should have completed
161         assertTrue(locker1.completed);
162 
163         // give locker2 a little bit of time to finish it's lock on the route header and proceed to it's wait
164         Thread.sleep(2000);
165         locker2.latch2.countDown();
166         locker2.join(timeout);
167 
168         // locker 2 should be completed as well
169         assertTrue(locker2.completed);
170 
171     }
172 
173     private class Locker extends Thread {
174 
175         private static final long TIMEOUT = 60 * 1000;
176 
177         String documentId;
178         CountDownLatch latch1;
179         CountDownLatch latch2;
180 
181         volatile boolean prelock;
182         volatile boolean waiting;
183         volatile boolean completed;
184 
185         Locker(String documentId) {
186             this.documentId = documentId;
187             this.latch1 = new CountDownLatch(1);
188             this.latch2 = new CountDownLatch(1);
189         }
190 
191         public void run() {
192             getTransactionTemplate().execute(new TransactionCallback() {
193                 public Object doInTransaction(TransactionStatus status) {
194                     prelock = true;
195                     routeHeaderService.lockRouteHeader(documentId);
196                     try {
197                         waiting = true;
198                         latch1.countDown();
199                         latch2.await(TIMEOUT, TimeUnit.MILLISECONDS);
200                     } catch (InterruptedException e) {
201                          throw new RuntimeException("Shouldn't have been interrupted but was.", e);
202                     }
203                     return null;
204                 }
205             });
206             completed = true;
207         }
208     }
209 
210 }