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.test;
17  
18  import org.junit.Assert;
19  
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Set;
25  
26  public final class TestUtilities {
27  	
28  	private static Thread exceptionThreader;
29  	
30  	private TestUtilities() {
31  		throw new UnsupportedOperationException("do not call");
32  	}
33  	
34  	/**
35       * Waits "indefinately" for the exception routing thread to terminate.
36       *
37       * This actually doesn't wait forever but puts an upper bound of 5 minutes
38       * on the time to wait for the exception routing thread to complete.  If a
39       * document cannot go into exception routing within 5 minutes  then we got
40       * problems.
41       */
42      public static void waitForExceptionRouting() {
43      	waitForExceptionRouting(5*60*1000);
44      }
45  
46      public static void waitForExceptionRouting(long milliseconds) {
47      	try {
48      		Thread thread = getExceptionThreader();
49      		if (thread == null) {
50      			throw new IllegalStateException("No exception thread was established, likely message is not being processed for exception routing.");
51      		}
52      		thread.join(milliseconds);
53      	} catch (InterruptedException e) {
54      		Assert.fail("This thread was interuppted while waiting for exception routing.");
55      	}
56      	if (getExceptionThreader().isAlive()) {
57      		Assert.fail("Document was not put into exception routing within the specified amount of time " + milliseconds);
58      	}
59      }
60  
61      public static Thread getExceptionThreader() {
62          return exceptionThreader;
63      }
64  
65      public static void setExceptionThreader(Thread exceptionThreader) {
66          TestUtilities.exceptionThreader = exceptionThreader;
67      }
68  
69      protected static boolean contains(Class[] list, Class target) {
70          for (Class c: list) {
71              if (c.getName().equals(target.getName())) {
72                  return true;
73              }
74          }
75          return false;
76      }
77  
78      /**
79       * This method facilitates using annotations in a unit test class hierarchy.  We walk up the class hierarchy
80       * and on each class, looking for the presence of any of the specified annotation types.  If the particular class
81       * defines one of the annotation types, it is marked for handling.  Once any single target annotation is found
82       * on the class, it is marked and no further annotations are inspected.
83       * 
84       * If the annotation defines an 'overrideSuperClasses' method, and this method returns false, then processing
85       * continues up the class hierarchy.  Otherwise processing stops when the first annotation is found.  Note that
86       * this feature only makes sense when specifying a single annotation type.
87       * 
88       * After a list of classes in descending hierarchy order is compiled, the list is iterated over (again, in
89       * descending hierarchy order) and if the class is not already present in the caller-supplied list of classes
90       * already handled by the caller, the class is added to a list of classes that need to be handled by the caller,
91       * which is then returned to the caller.
92       * 
93       * It is the caller's responsibility to handle the returned classes, and store them in some internal list which it may
94       * give back to this method in the future.
95       * 
96       * @throws Exception if there is a problem in reflection on an Annotation object
97       */
98      public static List<Class> getHierarchyClassesToHandle(Class testClass, Class[] annotationClasses, Set<String> classesHandled) throws Exception {
99          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 }