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.config.Config;
28 import org.kuali.rice.core.config.ConfigContext;
29 import org.kuali.rice.core.resourceloader.GlobalResourceLoader;
30 import org.kuali.rice.core.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 }