org.ops4j.pax.exam.junit.extender.impl.internal.CallableTestMethodImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.exam.junit.extender.impl.internal.CallableTestMethodImpl.java

Source

/*
 * Copyright 2008,2009 Toni Menzel
 * Copyright 2008 Alin Dreghiciu
 *
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ops4j.pax.exam.junit.extender.impl.internal;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.osgi.framework.BundleContext;

import static org.ops4j.lang.NullArgumentException.*;

import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.junit.extender.CallableTestMethod;

/**
 * {@link Callable} implementation.
 *
 * @author Toni Menzel (tonit)
 * @author Alin Dreghiciu (adreghiciu@gmail.com)
 * @since May 29, 2008
 */
class CallableTestMethodImpl implements CallableTestMethod {

    /**
     * Logger.
     */
    private static final Log LOG = LogFactory.getLog(CallableTestMethodImpl.class);

    /**
     * Bundle context of the bundle containing the test class (cannot be null).
     */
    private BundleContext m_bundleContext;
    /**
     * Test class name (cannot be null or empty).
     */
    private final String m_testClassName;
    /**
     * Test method name (cannot be null or empty).
     */
    private final String m_testMethodName;

    /**
     * Constructor.
     *
     * @param bundleContext  bundle context of the bundle containing the test class (cannot be null)
     * @param testClassName  test class name (cannot be null  or empty)
     * @param testMethodName test method name (cannot be null or empty)
     *
     * @throws IllegalArgumentException - If bundle context is null
     *                                  - If test class name is null or empty
     *                                  - If test method name is null or empty
     */
    CallableTestMethodImpl(final BundleContext bundleContext, final String testClassName,
            final String testMethodName) {
        validateNotNull(bundleContext, "Bundle context");
        validateNotEmpty(testClassName, true, "Test class name");
        validateNotEmpty(testMethodName, true, "Test method name");

        m_bundleContext = bundleContext;
        m_testClassName = testClassName;
        m_testMethodName = testMethodName;
    }

    /**
     * {@inheritDoc}
     */
    public void call() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException {
        final Class testClass = m_bundleContext.getBundle().loadClass(m_testClassName);
        int encountered = 0;
        for (final Method testMethod : testClass.getMethods()) {
            if (testMethod.getName().equals(m_testMethodName)) {
                injectContextAndInvoke(testClass.newInstance(), testMethod);
                encountered++;
            }
        }
        if (encountered == 0) {
            throw new RuntimeException(
                    " test " + m_testMethodName + " not found in test class " + testClass.getName());
        }
    }

    /**
     * Invokes the bundle context (if possible and required) and executes the test method.
     *
     * @param testInstance an instance of the test class
     * @param testMethod   test method
     *
     * @throws IllegalAccessException    - Re-thrown from reflection invokation
     * @throws InvocationTargetException - Re-thrown from reflection invokation
     */
    private void injectContextAndInvoke(final Object testInstance, final Method testMethod)
            throws IllegalAccessException, InvocationTargetException {
        final Class<?>[] paramTypes = testMethod.getParameterTypes();
        injectFieldInstances(testInstance.getClass(), testInstance);
        boolean cleanup = false;
        try {
            runBefores(testInstance);
            // if there is only one param and is of type BundleContext we inject it, otherwise just call
            // this means that if there are actual params the call will fail, but that is okay as it will be reported back
            if (paramTypes.length == 1 && paramTypes[0].isAssignableFrom(BundleContext.class)) {
                testMethod.invoke(testInstance, m_bundleContext);
            } else {
                testMethod.invoke(testInstance);
            }
            cleanup = true;
            runAfters(testInstance);
        } finally {
            if (!cleanup) {
                try {
                    runAfters(testInstance);
                } catch (Throwable throwable) {
                    LOG.warn("Got the exception when calling the runAfters. [Exception]: " + throwable);
                }
            }
        }
    }

    /**
     * Run all methods annotated with {@link Before}.
     *
     * @param testInstance an instance of the test class (cannot be null)
     *
     * @throws IllegalAccessException    - Re-thrown from reflection invokation
     * @throws InvocationTargetException - Re-thrown from reflection invokation
     */
    private void runBefores(final Object testInstance) throws IllegalAccessException, InvocationTargetException {
        for (final Method beforeMethod : getAnnotatedMethods(testInstance.getClass(), Before.class)) {
            final Class<?>[] paramTypes = beforeMethod.getParameterTypes();
            if (paramTypes.length == 1 && paramTypes[0].isAssignableFrom(BundleContext.class)) {
                beforeMethod.invoke(testInstance, m_bundleContext);
            } else {
                beforeMethod.invoke(testInstance);
            }
        }
    }

    /**
     * Run all methods annotated with {@link After}.
     *
     * @param testInstance an instance of the test class (cannot be null)
     *
     * @throws IllegalAccessException    - Re-thrown from reflection invokation
     * @throws InvocationTargetException - Re-thrown from reflection invokation
     */
    private void runAfters(final Object testInstance) throws IllegalAccessException, InvocationTargetException {
        for (final Method afterMethod : getAnnotatedMethods(testInstance.getClass(), After.class)) {
            final Class<?>[] paramTypes = afterMethod.getParameterTypes();
            if (paramTypes.length == 1 && paramTypes[0].isAssignableFrom(BundleContext.class)) {
                afterMethod.invoke(testInstance, m_bundleContext);
            } else {
                afterMethod.invoke(testInstance);
            }
        }
    }

    /**
     * Injects instances into fields found in testInstance and its superclases.
     *
     * @param clazz
     * @param inst
     */
    private void injectFieldInstances(Class clazz, Object inst) throws IllegalAccessException {
        if (clazz.getSuperclass() != null) {
            injectFieldInstances(clazz.getSuperclass(), inst);
        }
        for (Field field : clazz.getDeclaredFields()) {
            setIfMatching(inst, field, m_bundleContext);
        }
    }

    /**
     * @param testInstance object instance where you found field
     * @param field        field that is going to be set
     * @param o            target value of field
     */
    private void setIfMatching(Object testInstance, Field field, Object o) throws IllegalAccessException {
        if (isInjectionField(field) && isMatchingType(field, o.getClass())) {
            field.setAccessible(true);
            field.set(testInstance, o);
        }
    }

    /**
     * Just checks if type of field is a assignable from clazz.
     *
     * @param field
     * @param clazz
     */
    private boolean isMatchingType(Field field, Class clazz) {
        boolean result = field.getType().isAssignableFrom(clazz);
        LOG.debug("Trying to match " + field.getType() + " with injection " + clazz.getName() + ": " + result);
        return result;
    }

    /**
     * Tests if the given field has the {@link @Inject} annotation.
     * Due to some osgi quirks, currently direct getAnnotation( Inject.class ) does not work..:(
     *
     * @param field field to be tested
     *
     * @return trze if it has the Inject annotation. Otherwise false.
     */
    public boolean isInjectionField(Field field) {
        // Usually, this should be enough.
        if (field.getAnnotation(Inject.class) != null) {
            return true;
        } else {
            // the above one fails in some cases currently (returns null) while annotation is there.
            // So this is a fallback:
            for (Annotation annot : field.getAnnotations()) {
                if (annot.annotationType().getName().equals(Inject.class.getName())) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Find all methods marked with a specific annotation.
     *
     * @param testClass       class to be inspected
     * @param annotationClass annotation class to be found
     *
     * @return list of annotated methods (cannot be null)
     */
    public List<Method> getAnnotatedMethods(final Class testClass,
            final Class<? extends Annotation> annotationClass) {
        final List<Method> results = new ArrayList<Method>();
        for (final Class<?> clazz : getSuperClasses(testClass)) {
            final Method[] methods = clazz.getDeclaredMethods();
            for (final Method method : methods) {
                final Annotation annotation = method.getAnnotation(annotationClass);
                if (annotation != null) {
                    results.add(method);
                }
            }
        }
        return results;
    }

    /**
     * Finds all superclasses of a certain class including itself.
     *
     * @param testClass class whom superclasses should be found
     *
     * @return list of superclasses (cannot be null)
     */
    private List<Class<?>> getSuperClasses(final Class<?> testClass) {
        final ArrayList<Class<?>> results = new ArrayList<Class<?>>();
        Class<?> current = testClass;
        while (current != null) {
            results.add(current);
            current = current.getSuperclass();
        }
        return results;
    }

}