001: /*
002: * Copyright 2005-2007 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.test;
018:
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.net.InetAddress;
024: import java.sql.Connection;
025: import java.sql.ResultSet;
026: import java.sql.SQLException;
027: import java.sql.Statement;
028: import java.sql.Timestamp;
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.Date;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Properties;
036: import java.util.Set;
037:
038: import javax.sql.DataSource;
039: import javax.xml.namespace.QName;
040:
041: import junit.framework.Assert;
042: import junit.framework.AssertionFailedError;
043:
044: import org.apache.commons.io.FileUtils;
045: import org.apache.commons.lang.StringUtils;
046: import org.apache.commons.lang.SystemUtils;
047: import org.apache.ojb.broker.PBKey;
048: import org.kuali.bus.services.KSBServiceLocator;
049: import org.kuali.rice.core.Core;
050: import org.kuali.rice.resourceloader.GlobalResourceLoader;
051: import org.kuali.rice.resourceloader.ResourceLoader;
052: import org.springframework.jdbc.core.ConnectionCallback;
053: import org.springframework.jdbc.core.JdbcTemplate;
054: import org.springframework.jdbc.core.StatementCallback;
055: import org.springframework.transaction.PlatformTransactionManager;
056: import org.springframework.transaction.TransactionStatus;
057: import org.springframework.transaction.support.TransactionCallback;
058: import org.springframework.transaction.support.TransactionTemplate;
059: import org.springmodules.orm.ojb.PersistenceBrokerTemplate;
060:
061: import edu.iu.uis.eden.KEWServiceLocator;
062: import edu.iu.uis.eden.SpringLoader;
063: import edu.iu.uis.eden.actionitem.ActionItem;
064: import edu.iu.uis.eden.actionrequests.ActionRequestValue;
065: import edu.iu.uis.eden.clientapp.WorkflowDocument;
066: import edu.iu.uis.eden.clientapp.vo.NetworkIdVO;
067: import edu.iu.uis.eden.engine.node.RouteNodeInstance;
068: import edu.iu.uis.eden.exception.EdenUserNotFoundException;
069: import edu.iu.uis.eden.exception.WorkflowException;
070: import edu.iu.uis.eden.exception.WorkflowRuntimeException;
071: import edu.iu.uis.eden.messaging.JavaServiceDefinition;
072: import edu.iu.uis.eden.messaging.PersistedMessage;
073: import edu.iu.uis.eden.messaging.RemoteResourceServiceLocatorImpl;
074: import edu.iu.uis.eden.messaging.ServiceDefinition;
075: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
076: import edu.iu.uis.eden.user.AuthenticationUserId;
077: import edu.iu.uis.eden.user.WorkflowUser;
078: import edu.iu.uis.eden.util.Utilities;
079:
080: /**
081: * Defines utilities for unit testing
082: */
083: public class TestUtilities {
084:
085: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
086: .getLogger(TestUtilities.class);
087:
088: private static final String TEST_TABLE_NAME = "EN_UNITTEST_T";
089: private static Thread exceptionThreader;
090:
091: private static List<String> BUS_TABLES = new ArrayList<String>();
092: static {
093: BUS_TABLES.add("EN_SERVICE_DEF_T");
094: BUS_TABLES.add("EN_SERVICE_DEF_DUEX_T");
095: BUS_TABLES.add("EN_SERVICE_DEF_INTER_T");
096: BUS_TABLES.add("EN_MESSAGE_QUE_T");
097: BUS_TABLES.add("EN_BAM_T");
098: BUS_TABLES.add("EN_BAM_PARAM_T");
099: }
100:
101: private TestUtilities() {
102: // prevent construction
103: }
104:
105: public static InputStream loadResource(Class packageClass,
106: String resourceName) {
107: return packageClass.getResourceAsStream(resourceName);
108: }
109:
110: // public static PersistedMessage createRouteQueue(DocumentRouteHeaderValue routeHeader) throws Exception {
111: // Assert.assertNotNull(routeHeader);
112: // Assert.assertNotNull(routeHeader.getRouteHeaderId());
113: // return createRouteQueue(routeHeader.getRouteHeaderId().toString());
114: // }
115: //
116: // public static PersistedMessage createRouteQueue(String payload) throws Exception {
117: // PersistedMessage routeQueue = new PersistedMessage();
118: // routeQueue.setIpNumber(InetAddress.getLocalHost().getHostAddress());
119: // // set the time back 60 seconds so that we can pull it from the queue
120: // routeQueue.setQueueDate(new Timestamp(new Date().getTime()-60*1000));
121: // routeQueue.setQueuePriority(new Integer(0));
122: // routeQueue.setQueueStatus("Q");
123: // routeQueue.setRetryCount(new Integer(0));
124: // routeQueue.setPayload( KSBServiceLocator.getMessageHelper().serializeObject(payload));
125: // routeQueue.setMessageEntity(Core.getCurrentContextConfig().getMessageEntity());
126: // KEWServiceLocator.getRouteQueueService().save(routeQueue);
127: // return routeQueue;
128: // }
129:
130: // /**
131: // * There's no easy way to wire up additional topics from the test harness because it's the
132: // * workflow server's point of view. At the moment we'd have to override the ksbconfigurer in the
133: // * test spring files or give the ksbconfigurer an option to do nothing but register services...
134: // *
135: // * @throws Exception
136: // */
137: // public static void programmaticallyRegisterTestHarnessTopic(QName serviceName, Object service) throws Exception {
138: // ServiceDefinition serviceDef = new JavaServiceDefinition();
139: // serviceDef.setPriority(3);
140: // serviceDef.setRetryAttempts(3);
141: // serviceDef.setService(service);
142: // serviceDef.setServiceName(serviceName);
143: // serviceDef.setQueue(false);
144: // try {
145: // serviceDef.validate();
146: // } catch (Exception e) {
147: // throw new WorkflowRuntimeException(e);
148: // }
149: // KEWServiceLocator.getServiceDeployer().registerService(serviceDef);
150: // // force a refresh on our node
151: // RemoteResourceServiceLocatorImpl remoteResourceServiceLocator = (RemoteResourceServiceLocatorImpl) GlobalResourceLoader.getResourceLoader(new QName(Core.getCurrentContextConfig().getMessageEntity(),
152: // ResourceLoader.REMOTE_RESOURCE_LOADER_NAME));
153: // remoteResourceServiceLocator.run();
154: // }
155:
156: public static TransactionTemplate getTransactionTemplate() {
157: return (TransactionTemplate) SpringLoader.getInstance()
158: .getBean(KEWServiceLocator.TRANSACTION_TEMPLATE);
159: }
160:
161: public static void verifyTestEnvironment(DataSource dataSource) {
162: if (dataSource == null) {
163: Assert.fail("Could not locate the EDEN data source.");
164: }
165: JdbcTemplate template = new JdbcTemplate(dataSource);
166: template.execute(new ConnectionCallback() {
167: public Object doInConnection(Connection connection)
168: throws SQLException {
169: ResultSet resultSet = connection.getMetaData()
170: .getTables(null, null, TEST_TABLE_NAME, null);
171: if (!resultSet.next()) {
172: LOG
173: .error("No table named '"
174: + TEST_TABLE_NAME
175: + "' was found in the configured database. "
176: + "You are attempting to run tests against a non-test database!!!");
177: LOG
178: .error("The test environment will not start up properly!!!");
179: Assert
180: .fail("No table named '"
181: + TEST_TABLE_NAME
182: + "' was found in the configured database. "
183: + "You are attempting to run tests against a non-test database!!!");
184: }
185: return null;
186: }
187: });
188: }
189:
190: public static void clearTables(
191: final PlatformTransactionManager transactionManager,
192: final DataSource dataSource, final String edenSchemaName,
193: final List<String> dontClear) {
194: LOG.info("Clearing tables for schema " + edenSchemaName);
195: if (dataSource == null) {
196: Assert.fail("Null data source given");
197: }
198: if (edenSchemaName == null || edenSchemaName.equals("")) {
199: Assert.fail("Empty eden schema name given");
200: }
201: new TransactionTemplate(transactionManager)
202: .execute(new TransactionCallback() {
203: public Object doInTransaction(
204: TransactionStatus status) {
205: verifyTestEnvironment(dataSource);
206: JdbcTemplate template = new JdbcTemplate(
207: dataSource);
208: return template
209: .execute(new StatementCallback() {
210: public Object doInStatement(
211: Statement statement)
212: throws SQLException {
213: List<String> reEnableConstraints = new ArrayList<String>();
214: ResultSet resultSet = statement
215: .getConnection()
216: .getMetaData()
217: .getTables(
218: null,
219: edenSchemaName,
220: null,
221: new String[] { "TABLE" });
222: while (resultSet.next()) {
223: String tableName = resultSet
224: .getString("TABLE_NAME");
225: if (tableName
226: .startsWith("EN_")
227: && !dontClear
228: .contains(tableName)) {
229: ResultSet keyResultSet = statement
230: .getConnection()
231: .getMetaData()
232: .getExportedKeys(
233: null,
234: edenSchemaName,
235: tableName);
236: while (keyResultSet
237: .next()) {
238: String fkName = keyResultSet
239: .getString("FK_NAME");
240: String fkTableName = keyResultSet
241: .getString("FKTABLE_NAME");
242: statement
243: .addBatch("ALTER TABLE "
244: + fkTableName
245: + " DISABLE CONSTRAINT "
246: + fkName);
247: reEnableConstraints
248: .add("ALTER TABLE "
249: + fkTableName
250: + " ENABLE CONSTRAINT "
251: + fkName);
252: }
253: keyResultSet.close();
254: statement
255: .addBatch("DELETE FROM "
256: + tableName);
257: }
258: }
259: for (String constraint : reEnableConstraints) {
260: statement
261: .addBatch(constraint);
262: }
263: statement.executeBatch();
264: resultSet.close();
265: return null;
266: }
267: });
268: }
269: });
270: LOG.info("Tables successfully cleared for schema "
271: + edenSchemaName);
272: }
273:
274: public static Set<String> createNodeInstanceNameSet(
275: Collection nodeInstances) {
276: Set<String> nameSet = new HashSet<String>();
277: for (Iterator iterator = nodeInstances.iterator(); iterator
278: .hasNext();) {
279: RouteNodeInstance nodeInstance = (RouteNodeInstance) iterator
280: .next();
281: nameSet.add(nodeInstance.getName());
282: }
283: return nameSet;
284: }
285:
286: /**
287: * Checks that the document is at a node with the given name. This does not check that the document is at
288: * the given node and only the given node, the document can be at other nodes as well and this assertion
289: * will still pass.
290: */
291: public static void assertAtNode(String message,
292: WorkflowDocument document, String nodeName)
293: throws WorkflowException {
294: String[] nodeNames = document.getNodeNames();
295: for (int index = 0; index < nodeNames.length; index++) {
296: String docNodeName = nodeNames[index];
297: if (docNodeName.equals(nodeName)) {
298: return;
299: }
300: }
301: throw new AssertionFailedError((Utilities.isEmpty(message) ? ""
302: : message + ": ")
303: + "Was ["
304: + StringUtils.join(nodeNames, ", ")
305: + "], Expected " + nodeName);
306: }
307:
308: public static void assertAtNode(WorkflowDocument document,
309: String nodeName) throws WorkflowException {
310: assertAtNode("", document, nodeName);
311: }
312:
313: /**
314: * Asserts that the given document id is in the given user's action list.
315: */
316: public static void assertInActionList(NetworkIdVO networkId,
317: Long documentId) throws EdenUserNotFoundException {
318: WorkflowUser user = KEWServiceLocator.getUserService()
319: .getWorkflowUser(networkId);
320: Assert.assertNotNull("Given network id was invalid: "
321: + networkId, user);
322: Collection actionList = KEWServiceLocator
323: .getActionListService().findByWorkflowUser(user);
324: for (Iterator iterator = actionList.iterator(); iterator
325: .hasNext();) {
326: ActionItem actionItem = (ActionItem) iterator.next();
327: if (actionItem.getRouteHeaderId().equals(documentId)) {
328: return;
329: }
330: }
331: Assert
332: .fail("Could not locate an action item in the user's action list for the given document id.");
333: }
334:
335: /**
336: * Asserts that the given document id is NOT in the given user's action list.
337: */
338: public static void assertNotInActionList(NetworkIdVO networkId,
339: Long documentId) throws EdenUserNotFoundException {
340: WorkflowUser user = KEWServiceLocator.getUserService()
341: .getWorkflowUser(networkId);
342: Assert.assertNotNull("Given network id was invalid: "
343: + networkId, user);
344: Collection actionList = KEWServiceLocator
345: .getActionListService().findByWorkflowUser(user);
346: for (Iterator iterator = actionList.iterator(); iterator
347: .hasNext();) {
348: ActionItem actionItem = (ActionItem) iterator.next();
349: if (actionItem.getRouteHeaderId().equals(documentId)) {
350: Assert
351: .fail("Found an action item in the user's acton list for the given document id.");
352: }
353: }
354: }
355:
356: public static void assertNumberOfPendingRequests(Long documentId,
357: int numberOfPendingRequests) {
358: List actionRequests = KEWServiceLocator
359: .getActionRequestService().findPendingByDoc(documentId);
360: Assert.assertEquals(
361: "Wrong number of pending requests for document: "
362: + documentId, numberOfPendingRequests,
363: actionRequests.size());
364: }
365:
366: /**
367: * Asserts that the user with the given network id has a pending request on the given document
368: */
369: public static void assertUserHasPendingRequest(Long documentId,
370: String networkId) throws WorkflowException {
371: WorkflowUser user = KEWServiceLocator.getUserService()
372: .getWorkflowUser(new AuthenticationUserId(networkId));
373: List actionRequests = KEWServiceLocator
374: .getActionRequestService().findPendingByDoc(documentId);
375: boolean foundRequest = false;
376: for (Iterator iterator = actionRequests.iterator(); iterator
377: .hasNext();) {
378: ActionRequestValue actionRequest = (ActionRequestValue) iterator
379: .next();
380: if (actionRequest.isUserRequest()
381: && actionRequest.getWorkflowUser()
382: .getAuthenticationUserId()
383: .getAuthenticationId().equals(networkId)) {
384: foundRequest = true;
385: break;
386: } else if (actionRequest.isWorkgroupRequest()
387: && actionRequest.getWorkgroup().hasMember(user)) {
388: foundRequest = true;
389: break;
390: }
391: }
392: Assert.assertTrue(
393: "Could not locate pending request for the given user: "
394: + networkId, foundRequest);
395: }
396:
397: public static PersistenceBrokerTemplate getPersistenceBrokerTemplate() {
398: PersistenceBrokerTemplate pbt = new PersistenceBrokerTemplate();
399: pbt.setPbKey(new PBKey("enWorkflowDataSource"));
400: pbt.setDataSource(KEWServiceLocator.getEdenDataSource());
401: pbt.setJcdAlias("enWorkflowDataSource");
402: pbt.afterPropertiesSet();
403: return pbt;
404: }
405:
406: /**
407: * Waits "indefinately" for the exception routing thread to terminate.
408: *
409: * This actually doesn't wait forever but puts an upper bound of 5 minutes
410: * on the time to wait for the exception routing thread to complete. If a
411: * document cannot go into exception routing within 5 minutes then we got
412: * problems.
413: */
414: public static void waitForExceptionRouting() {
415: waitForExceptionRouting(5 * 60 * 1000);
416: }
417:
418: public static void waitForExceptionRouting(long milliseconds) {
419: try {
420: getExceptionThreader().join(milliseconds);
421: } catch (InterruptedException e) {
422: Assert
423: .fail("This thread was interuppted while waiting for exception routing.");
424: }
425: if (getExceptionThreader().isAlive()) {
426: Assert
427: .fail("Document was not put into exception routing within the specified amount of time "
428: + milliseconds);
429: }
430: }
431:
432: public static Thread getExceptionThreader() {
433: return exceptionThreader;
434: }
435:
436: public static void setExceptionThreader(Thread exceptionThreader) {
437: TestUtilities.exceptionThreader = exceptionThreader;
438: }
439:
440: private static final String DEFAULT_TEST_PLATFORM = "oracle";
441: private static final String BUILD_PROPERTIES = "build.properties";
442: private static final String TEST_PLATFORM = "test.platform";
443:
444: /**
445: * Attempts to derive the database "platform" to use for unit tests by
446: * inspected Ant build.properties files in typical locations.
447: * @return the test platform if so defined in Ant build.properties file(s), or the DEFAULT_TEST_PLATFORM otherwise
448: * @see #DEFAULT_TEST_PLATFORM
449: * @throws IOException if anything goes awry
450: */
451: public static String getTestPlatform() throws IOException {
452: // check the user's build.properties in user's home
453: File userBuildProperties = new File(SystemUtils.USER_HOME + "/"
454: + BUILD_PROPERTIES);
455: if (userBuildProperties.isFile()) {
456: Properties properties = loadProperties(userBuildProperties);
457: if (properties.containsKey(TEST_PLATFORM)) {
458: return properties.getProperty(TEST_PLATFORM)
459: .toLowerCase();
460: }
461: }
462: // check the "local" build.properties in the current directory
463: File localBuildProperties = new File(BUILD_PROPERTIES);
464: if (localBuildProperties.isFile()) {
465: Properties properties = loadProperties(localBuildProperties);
466: if (properties.containsKey(TEST_PLATFORM)) {
467: return properties.getProperty(TEST_PLATFORM)
468: .toLowerCase();
469: }
470: }
471: return DEFAULT_TEST_PLATFORM.toLowerCase();
472: }
473:
474: /**
475: * Loads a file into a Properties object
476: * @param file the file
477: * @return a Properties object
478: */
479: private static Properties loadProperties(File file)
480: throws IOException {
481: Properties properties = new Properties();
482: FileInputStream fis = new FileInputStream(file);
483: try {
484: properties.load(fis);
485: } finally {
486: fis.close();
487: }
488: return properties;
489: }
490:
491: // public static void clearDatabase() throws Exception {
492: // new ClearDatabaseLifecycle().start();
493: // }
494: //
495: // public static void clearNonBusDatabase() throws Exception {
496: // new ClearDatabaseLifecycle(BUS_TABLES).start();
497: // }
498:
499: public static File createTempDir() throws Exception {
500: File tmpFile = File.createTempFile("wfUnitTest", "");
501: Assert.assertTrue(tmpFile.delete());
502: File tmpDir = new File(new File(SystemUtils.JAVA_IO_TMPDIR),
503: tmpFile.getName());
504: Assert.assertTrue(tmpDir.mkdir());
505: tmpDir.deleteOnExit();
506: return tmpDir;
507: }
508:
509: public static File getEnPluginsDirectory() {
510: return new File("./work/unit-test/en-plugins");
511: }
512:
513: public static File getPluginsDirectory() {
514: return new File("./work/unit-test/plugins");
515: }
516:
517: public static void initializePluginDirectories() throws Exception {
518: File enPluginDir = getEnPluginsDirectory();
519: if (enPluginDir.exists()) {
520: FileUtils.forceDelete(enPluginDir);
521: }
522: File pluginDir = getPluginsDirectory();
523: if (pluginDir.exists()) {
524: FileUtils.forceDelete(pluginDir);
525: }
526: FileUtils.forceMkdir(enPluginDir);
527: FileUtils.forceMkdir(pluginDir);
528: FileUtils.forceDeleteOnExit(enPluginDir);
529: FileUtils.forceDeleteOnExit(pluginDir);
530: }
531:
532: public static void cleanupPluginDirectories() throws Exception {
533: FileUtils.deleteDirectory(getEnPluginsDirectory());
534: FileUtils.deleteDirectory(getPluginsDirectory());
535: }
536:
537: /**
538: * wait until the route queue is empty because we may have some cache items to be picked up still.
539: *
540: * @throws Exception
541: */
542: public static void waitForCacheNotificationsToClearFromQueue()
543: throws Exception {
544: // NOTE: when using McKoi for units tests, route queue service never seems to be clear of items, and correspondingly
545: // this method will throw an exception
546: int iterations = 0;
547: while (true) {
548: int itemCount = KEWServiceLocator.getRouteQueueService()
549: .findAll().size();
550: if (itemCount == 0) {
551: break;
552: }
553: if (iterations > 20) {
554: throw new WorkflowRuntimeException(
555: "Waited too long for route queue to clear out cache notifications");
556: }
557: iterations++;
558: System.out
559: .println("!!!Sleeping for 1 second to let cache notifications clear out");
560: Thread.sleep(1000);
561: }
562: }
563:
564: }
|