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    }