001 /**
002 * Copyright 2005-2013 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.test;
017
018 import org.junit.Assert;
019
020 import java.lang.annotation.Annotation;
021 import java.lang.reflect.Method;
022 import java.util.ArrayList;
023 import java.util.List;
024 import java.util.Set;
025
026 public final class TestUtilities {
027
028 private static Thread exceptionThreader;
029
030 private TestUtilities() {
031 throw new UnsupportedOperationException("do not call");
032 }
033
034 /**
035 * Waits "indefinately" for the exception routing thread to terminate.
036 *
037 * This actually doesn't wait forever but puts an upper bound of 5 minutes
038 * on the time to wait for the exception routing thread to complete. If a
039 * document cannot go into exception routing within 5 minutes then we got
040 * problems.
041 */
042 public static void waitForExceptionRouting() {
043 waitForExceptionRouting(5*60*1000);
044 }
045
046 public static void waitForExceptionRouting(long milliseconds) {
047 try {
048 Thread thread = getExceptionThreader();
049 if (thread == null) {
050 throw new IllegalStateException("No exception thread was established, likely message is not being processed for exception routing.");
051 }
052 thread.join(milliseconds);
053 } catch (InterruptedException e) {
054 Assert.fail("This thread was interuppted while waiting for exception routing.");
055 }
056 if (getExceptionThreader().isAlive()) {
057 Assert.fail("Document was not put into exception routing within the specified amount of time " + milliseconds);
058 }
059 }
060
061 public static Thread getExceptionThreader() {
062 return exceptionThreader;
063 }
064
065 public static void setExceptionThreader(Thread exceptionThreader) {
066 TestUtilities.exceptionThreader = exceptionThreader;
067 }
068
069 protected static boolean contains(Class[] list, Class target) {
070 for (Class c: list) {
071 if (c.getName().equals(target.getName())) {
072 return true;
073 }
074 }
075 return false;
076 }
077
078 /**
079 * This method facilitates using annotations in a unit test class hierarchy. We walk up the class hierarchy
080 * and on each class, looking for the presence of any of the specified annotation types. If the particular class
081 * defines one of the annotation types, it is marked for handling. Once any single target annotation is found
082 * on the class, it is marked and no further annotations are inspected.
083 *
084 * If the annotation defines an 'overrideSuperClasses' method, and this method returns false, then processing
085 * continues up the class hierarchy. Otherwise processing stops when the first annotation is found. Note that
086 * this feature only makes sense when specifying a single annotation type.
087 *
088 * After a list of classes in descending hierarchy order is compiled, the list is iterated over (again, in
089 * descending hierarchy order) and if the class is not already present in the caller-supplied list of classes
090 * already handled by the caller, the class is added to a list of classes that need to be handled by the caller,
091 * which is then returned to the caller.
092 *
093 * It is the caller's responsibility to handle the returned classes, and store them in some internal list which it may
094 * give back to this method in the future.
095 *
096 * @throws Exception if there is a problem in reflection on an Annotation object
097 */
098 public static List<Class> getHierarchyClassesToHandle(Class testClass, Class[] annotationClasses, Set<String> classesHandled) throws Exception {
099 List<Class> classesThatNeedHandling = new ArrayList<Class>();
100 // get a list of all classes the current class extends from that use the PerSuiteUnitTestData annotation
101 List<Class> classesToCheck = new ArrayList<Class>();
102 // here we get the list apart checking the annotations to support the perSuiteDataLoaderLifecycleNamesRun variable better
103 {
104 Class clazz = testClass;
105 superClassLoop: while (!clazz.getName().equals(Object.class.getName())) {
106 for (Annotation annotation : clazz.getDeclaredAnnotations()) {
107 // if this isn't one of the annotations we're interested in, move on
108 if (!contains(annotationClasses, annotation.annotationType())) {
109 continue;
110 }
111
112 // this class should be processed because it contains an annotation we are interested in
113 classesToCheck.add(0, clazz);
114
115 // now check to see if annotation overrides super class implementations
116 if (annotationOverridesSuperClass(annotation)) {
117 // we're done here
118 break superClassLoop;
119 }
120 // we just added the class to classes to check, we don't need to add it again
121 // so just stop looking at annotations in this particular class
122 break;
123 }
124 clazz = clazz.getSuperclass();
125 }
126 }
127
128 for (Class clazz: classesToCheck) {
129 if (!classesHandled.contains(clazz.getName())) {
130 classesThatNeedHandling.add(clazz);
131 }
132 }
133 return classesThatNeedHandling;
134 }
135
136 /**
137 * Determines whether an annotation should override the same type of annotation on a superclass,
138 * by using reflection to invoke the 'overrideSuperClasses' method on the annotation if it exists.
139 * If the annotation does not supply this method, the default is true.
140 * @param annotation the annotation to inspect
141 * @return whether this annotation overrides any annotations of similar type in super classes
142 * @throws Exception if an error occurs during reflection
143 */
144 protected static boolean annotationOverridesSuperClass(Annotation annotation) throws Exception {
145 boolean overrides = true; // default is to just override
146 Method m = null;;
147 try {
148 m = annotation.getClass().getMethod("overrideSuperClasses", null);
149 } catch (NoSuchMethodException nsme) {
150 // do nothing
151 }
152 if (m != null) {
153 Object result = m.invoke(annotation, (Object[]) null);
154 if (result instanceof Boolean) {
155 overrides = (Boolean) result;
156 } else {
157 throw new RuntimeException("Annotation 'overrideSuperClasses' did not return Boolean value");
158 }
159 }
160 return overrides;
161 }
162 }