View Javadoc

1   /*
2    * Copyright 2007 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.test;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Method;
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import junit.framework.Assert;
26  
27  import org.kuali.rice.core.api.config.property.Config;
28  import org.kuali.rice.core.api.config.property.ConfigContext;
29  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
30  import org.kuali.rice.core.api.resourceloader.ResourceLoader;
31  import org.mortbay.jetty.webapp.WebAppClassLoader;
32  
33  public class TestUtilities {
34  	
35  	private static Thread exceptionThreader;
36  	
37  	/**
38       * Waits "indefinately" for the exception routing thread to terminate.
39       *
40       * This actually doesn't wait forever but puts an upper bound of 5 minutes
41       * on the time to wait for the exception routing thread to complete.  If a
42       * document cannot go into exception routing within 5 minutes  then we got
43       * problems.
44       */
45      public static void waitForExceptionRouting() {
46      	waitForExceptionRouting(5*60*1000);
47      }
48  
49      public static void waitForExceptionRouting(long milliseconds) {
50      	try {
51      		Thread thread = getExceptionThreader();
52      		if (thread == null) {
53      			throw new IllegalStateException("No exception thread was established, likely message is not being processed for exception routing.");
54      		}
55      		thread.join(milliseconds);
56      	} catch (InterruptedException e) {
57      		Assert.fail("This thread was interuppted while waiting for exception routing.");
58      	}
59      	if (getExceptionThreader().isAlive()) {
60      		Assert.fail("Document was not put into exception routing within the specified amount of time " + milliseconds);
61      	}
62      }
63  
64      public static Thread getExceptionThreader() {
65          return exceptionThreader;
66      }
67  
68      public static void setExceptionThreader(Thread exceptionThreader) {
69          TestUtilities.exceptionThreader = exceptionThreader;
70      }	
71  
72      protected List<Class> annotationsPresent(Class clazz, Class[] annotationClasses) {
73          List<Class> annotationsPresent = new ArrayList<Class>();
74          for (Class c: annotationClasses) {
75              if (clazz.isAnnotationPresent(c)) {
76                  annotationsPresent.add(c);
77              }
78          }
79          return annotationsPresent;
80      }
81  
82      protected static boolean contains(Class[] list, Class target) {
83          for (Class c: list) {
84              if (c.getName().equals(target.getName())) {
85                  return true;
86              }
87          }
88          return false;
89      }
90  
91      /**
92       * This method facilitates using annotations in a unit test class hierarchy.  We walk up the class hierarchy
93       * and on each class, looking for the presence of any of the specified annotation types.  If the particular class
94       * defines one of the annotation types, it is marked for handling.  Once any single target annotation is found
95       * on the class, it is marked and no further annotations are inspected.
96       * 
97       * If the annotation defines an 'overrideSuperClasses' method, and this method returns false, then processing
98       * continues up the class hierarchy.  Otherwise processing stops when the first annotation is found.  Note that
99       * this feature only makes sense when specifying a single annotation type.
100      * 
101      * After a list of classes in descending hierarchy order is compiled, the list is iterated over (again, in
102      * descending hierarchy order) and if the class is not already present in the caller-supplied list of classes
103      * already handled by the caller, the class is added to a list of classes that need to be handled by the caller,
104      * which is then returned to the caller.
105      * 
106      * It is the caller's responsibility to handle the returned classes, and store them in some internal list which it may
107      * give back to this method in the future.
108      * 
109      * @throws Exception if there is a problem in reflection on an Annotation object
110      */
111     public static List<Class> getHierarchyClassesToHandle(Class testClass, Class[] annotationClasses, Set<String> classesHandled) throws Exception {
112         List<Class> classesThatNeedHandling = new ArrayList<Class>();
113         // get a list of all classes the current class extends from that use the PerSuiteUnitTestData annotation
114         List<Class> classesToCheck = new ArrayList<Class>();
115         // here we get the list apart checking the annotations to support the perSuiteDataLoaderLifecycleNamesRun variable better
116         {
117             Class clazz = testClass;
118             superClassLoop: while (!clazz.getName().equals(Object.class.getName())) {
119                 for (Annotation annotation : clazz.getDeclaredAnnotations()) {
120                     // if this isn't one of the annotations we're interested in, move on
121                     if (!contains(annotationClasses, annotation.annotationType())) {
122                         continue;
123                     }
124 
125                     // this class should be processed because it contains an annotation we are interested in
126                     classesToCheck.add(0, clazz);
127                     
128                     // now check to see if annotation overrides super class implementations
129                     if (annotationOverridesSuperClass(annotation)) {
130                         // we're done here
131                         break superClassLoop;
132                     }
133                     // we just added the class to classes to check, we don't need to add it again
134                     // so just stop looking at annotations in this particular class
135                     break;
136                 }
137                 clazz = clazz.getSuperclass();
138             }
139         }
140         
141         for (Class clazz: classesToCheck) {
142             if (!classesHandled.contains(clazz.getName())) {
143                 classesThatNeedHandling.add(clazz);
144             }
145         }
146         return classesThatNeedHandling;
147     }
148     
149     /**
150      * Determines whether an annotation should override the same type of annotation on a superclass,
151      * by using reflection to invoke the 'overrideSuperClasses' method on the annotation if it exists.
152      * If the annotation does not supply this method, the default is true.
153      * @param annotation the annotation to inspect
154      * @return whether this annotation overrides any annotations of similar type in super classes
155      * @throws Exception if an error occurs during reflection
156      */
157     protected static boolean annotationOverridesSuperClass(Annotation annotation) throws Exception {
158         boolean overrides = true; // default is to just override
159         Method m = null;;
160         try {
161             m = annotation.getClass().getMethod("overrideSuperClasses", null);
162         } catch (NoSuchMethodException nsme) {
163             // do nothing
164         }
165         if (m != null) {
166             Object result = m.invoke(annotation, (Object[]) null);
167             if (result instanceof Boolean) {
168                 overrides = (Boolean) result;
169             } else {
170                 throw new RuntimeException("Annotation 'overrideSuperClasses' did not return Boolean value");
171             }
172         }
173         return overrides;
174     }
175     
176     /**
177      * Adds all ResourceLoaders registered to WebAppClassLoaders to the GlobalResourceLoader.
178      * Overrides the current context config with the Config registered to the (last) WebAppClassLoader
179      */
180     public static void addWebappsToContext() {
181         for (Map.Entry<ClassLoader, Config> configEntry : ConfigContext.getConfigs()) {
182             if (configEntry.getKey() instanceof WebAppClassLoader) {
183                 ResourceLoader rl = GlobalResourceLoader.getResourceLoader(configEntry.getKey());
184                 if (rl == null) {
185                     Assert.fail("didn't find resource loader for workflow test harness web app");
186                 }
187                 GlobalResourceLoader.addResourceLoader(rl);
188                 ConfigContext.overrideConfig(Thread.currentThread().getContextClassLoader(), configEntry.getValue());
189             }
190         }
191     }
192 }