001 /* 002 * Copyright 2007 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 java.lang.annotation.Annotation; 019 import java.lang.reflect.Method; 020 import java.util.ArrayList; 021 import java.util.List; 022 import java.util.Map; 023 import java.util.Set; 024 025 import junit.framework.Assert; 026 027 import org.kuali.rice.core.api.config.property.Config; 028 import org.kuali.rice.core.api.config.property.ConfigContext; 029 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 030 import org.kuali.rice.core.api.resourceloader.ResourceLoader; 031 import org.mortbay.jetty.webapp.WebAppClassLoader; 032 033 public class TestUtilities { 034 035 private static Thread exceptionThreader; 036 037 /** 038 * Waits "indefinately" for the exception routing thread to terminate. 039 * 040 * This actually doesn't wait forever but puts an upper bound of 5 minutes 041 * on the time to wait for the exception routing thread to complete. If a 042 * document cannot go into exception routing within 5 minutes then we got 043 * problems. 044 */ 045 public static void waitForExceptionRouting() { 046 waitForExceptionRouting(5*60*1000); 047 } 048 049 public static void waitForExceptionRouting(long milliseconds) { 050 try { 051 Thread thread = getExceptionThreader(); 052 if (thread == null) { 053 throw new IllegalStateException("No exception thread was established, likely message is not being processed for exception routing."); 054 } 055 thread.join(milliseconds); 056 } catch (InterruptedException e) { 057 Assert.fail("This thread was interuppted while waiting for exception routing."); 058 } 059 if (getExceptionThreader().isAlive()) { 060 Assert.fail("Document was not put into exception routing within the specified amount of time " + milliseconds); 061 } 062 } 063 064 public static Thread getExceptionThreader() { 065 return exceptionThreader; 066 } 067 068 public static void setExceptionThreader(Thread exceptionThreader) { 069 TestUtilities.exceptionThreader = exceptionThreader; 070 } 071 072 protected List<Class> annotationsPresent(Class clazz, Class[] annotationClasses) { 073 List<Class> annotationsPresent = new ArrayList<Class>(); 074 for (Class c: annotationClasses) { 075 if (clazz.isAnnotationPresent(c)) { 076 annotationsPresent.add(c); 077 } 078 } 079 return annotationsPresent; 080 } 081 082 protected static boolean contains(Class[] list, Class target) { 083 for (Class c: list) { 084 if (c.getName().equals(target.getName())) { 085 return true; 086 } 087 } 088 return false; 089 } 090 091 /** 092 * This method facilitates using annotations in a unit test class hierarchy. We walk up the class hierarchy 093 * and on each class, looking for the presence of any of the specified annotation types. If the particular class 094 * defines one of the annotation types, it is marked for handling. Once any single target annotation is found 095 * on the class, it is marked and no further annotations are inspected. 096 * 097 * If the annotation defines an 'overrideSuperClasses' method, and this method returns false, then processing 098 * continues up the class hierarchy. Otherwise processing stops when the first annotation is found. Note that 099 * 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 }