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    }