Package org.springframework.test.context.support

Source Code of org.springframework.test.context.support.AbstractTestContextBootstrapper

/*
* Copyright 2002-2014 the original author or authors.
*
* 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.springframework.test.context.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestExecutionListeners.MergeMode;
import org.springframework.test.util.MetaAnnotationUtils;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
* Abstract implementation of the {@link TestContextBootstrapper} interface which
* provides most of the behavior required by a bootstrapper.
*
* <p>Concrete subclasses typically will only need to provide implementations for
* the following methods:
* <ul>
* <li>{@link #getDefaultContextLoaderClass}
* <li>{@link #processMergedContextConfiguration}
* </ul>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 4.1
*/
public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper {

  private final Log logger = LogFactory.getLog(getClass());

  private BootstrapContext bootstrapContext;


  /**
   * {@inheritDoc}
   */
  @Override
  public void setBootstrapContext(BootstrapContext bootstrapContext) {
    this.bootstrapContext = bootstrapContext;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public BootstrapContext getBootstrapContext() {
    return this.bootstrapContext;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  @Override
  public final List<TestExecutionListener> getTestExecutionListeners() {
    Class<?> clazz = getBootstrapContext().getTestClass();
    Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
    List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
    boolean usingDefaults = false;

    AnnotationDescriptor<TestExecutionListeners> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz,
      annotationType);

    // Use defaults?
    if (descriptor == null) {
      if (logger.isDebugEnabled()) {
        logger.debug(String.format("@TestExecutionListeners is not present for class [%s]: using defaults.",
          clazz.getName()));
      }
      usingDefaults = true;
      classesList.addAll(getDefaultTestExecutionListenerClasses());
    }
    else {
      // Traverse the class hierarchy...
      while (descriptor != null) {
        Class<?> declaringClass = descriptor.getDeclaringClass();
        AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
        if (logger.isTraceEnabled()) {
          logger.trace(String.format(
            "Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs,
            declaringClass.getName()));
        }

        Class<? extends TestExecutionListener>[] valueListenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("value");
        Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
        if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
          throw new IllegalStateException(String.format(
            "Class [%s] configured with @TestExecutionListeners' "
                + "'value' [%s] and 'listeners' [%s] attributes. Use one or the other, but not both.",
            declaringClass.getName(), ObjectUtils.nullSafeToString(valueListenerClasses),
            ObjectUtils.nullSafeToString(listenerClasses)));
        }
        else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
          listenerClasses = valueListenerClasses;
        }

        boolean inheritListeners = annAttrs.getBoolean("inheritListeners");
        AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor(
          descriptor.getRootDeclaringClass().getSuperclass(), annotationType);

        // If there are no listeners to inherit, we might need to merge the
        // locally declared listeners with the defaults.
        if ((!inheritListeners || superDescriptor == null)
            && (annAttrs.getEnum("mergeMode") == MergeMode.MERGE_WITH_DEFAULTS)) {
          if (logger.isDebugEnabled()) {
            logger.debug(String.format(
              "Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].",
              descriptor.getRootDeclaringClass().getName()));
          }
          usingDefaults = true;
          classesList.addAll(getDefaultTestExecutionListenerClasses());
        }

        classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));

        descriptor = (inheritListeners ? superDescriptor : null);
      }
    }

    // Remove possible duplicates if we loaded default listeners.
    if (usingDefaults) {
      Set<Class<? extends TestExecutionListener>> classesSet = new HashSet<Class<? extends TestExecutionListener>>();
      classesSet.addAll(classesList);
      classesList.clear();
      classesList.addAll(classesSet);
    }

    List<TestExecutionListener> listeners = instantiateListeners(classesList);

    // Sort by Ordered/@Order if we loaded default listeners.
    if (usingDefaults) {
      AnnotationAwareOrderComparator.sort(listeners);
    }

    if (logger.isInfoEnabled()) {
      logger.info(String.format("Using TestExecutionListeners: %s", listeners));
    }
    return listeners;
  }

  private List<TestExecutionListener> instantiateListeners(List<Class<? extends TestExecutionListener>> classesList) {
    List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
    for (Class<? extends TestExecutionListener> listenerClass : classesList) {
      NoClassDefFoundError ncdfe = null;
      try {
        listeners.add(BeanUtils.instantiateClass(listenerClass));
      }
      catch (NoClassDefFoundError err) {
        ncdfe = err;
      }
      catch (BeanInstantiationException ex) {
        if (ex.getCause() instanceof NoClassDefFoundError) {
          ncdfe = (NoClassDefFoundError) ex.getCause();
        }
      }
      if (ncdfe != null) {
        if (logger.isInfoEnabled()) {
          logger.info(String.format("Could not instantiate TestExecutionListener [%s]. "
              + "Specify custom listener classes or make the default listener classes "
              + "(and their required dependencies) available. Offending class: [%s]",
            listenerClass.getName(), ncdfe.getMessage()));
        }
      }
    }
    return listeners;
  }

  /**
   * Get the default {@link TestExecutionListener} classes for this bootstrapper.
   * <p>This method is invoked by {@link #getTestExecutionListeners()} and
   * delegates to {@link #getDefaultTestExecutionListenerClassNames()} to
   * retrieve the class names.
   * <p>If a particular class cannot be loaded, a {@code DEBUG} message will
   * be logged, but the associated exception will not be rethrown.
   */
  @SuppressWarnings("unchecked")
  protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
    Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>();
    ClassLoader cl = getClass().getClassLoader();
    for (String className : getDefaultTestExecutionListenerClassNames()) {
      try {
        defaultListenerClasses.add((Class<? extends TestExecutionListener>) ClassUtils.forName(className, cl));
      }
      catch (Throwable ex) {
        if (logger.isDebugEnabled()) {
          logger.debug("Could not load default TestExecutionListener class [" + className
              + "]. Specify custom listener classes or make the default listener classes available.", ex);
        }
      }
    }
    return defaultListenerClasses;
  }

  /**
   * Get the names of the default {@link TestExecutionListener} classes for
   * this bootstrapper.
   * <p>The default implementation looks up all
   * {@code org.springframework.test.context.TestExecutionListener} entries
   * configured in all {@code META-INF/spring.factories} files on the classpath.
   * <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
   * @return an <em>unmodifiable</em> list of names of default {@code TestExecutionListener}
   * classes
   * @see SpringFactoriesLoader#loadFactoryNames
   */
  protected List<String> getDefaultTestExecutionListenerClassNames() {
    final List<String> classNames = SpringFactoriesLoader.loadFactoryNames(TestExecutionListener.class,
      getClass().getClassLoader());

    if (logger.isInfoEnabled()) {
      logger.info(String.format("Loaded default TestExecutionListener class names from location [%s]: %s",
        SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
    }
    return Collections.unmodifiableList(classNames);
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  @Override
  public final MergedContextConfiguration buildMergedContextConfiguration() {
    Class<?> testClass = getBootstrapContext().getTestClass();
    CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate();

    if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class,
      ContextHierarchy.class) == null) {
      if (logger.isInfoEnabled()) {
        logger.info(String.format(
          "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
          testClass.getName()));
      }
      return new MergedContextConfiguration(testClass, null, null, null, null);
    }

    if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) {
      Map<String, List<ContextConfigurationAttributes>> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass);
      MergedContextConfiguration parentConfig = null;
      MergedContextConfiguration mergedConfig = null;

      for (List<ContextConfigurationAttributes> list : hierarchyMap.values()) {
        List<ContextConfigurationAttributes> reversedList = new ArrayList<ContextConfigurationAttributes>(list);
        Collections.reverse(reversedList);

        // Don't use the supplied testClass; instead ensure that we are
        // building the MCC for the actual test class that declared the
        // configuration for the current level in the context hierarchy.
        Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
        Class<?> declaringClass = reversedList.get(0).getDeclaringClass();

        mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList, parentConfig,
          cacheAwareContextLoaderDelegate);
        parentConfig = mergedConfig;
      }

      // Return the last level in the context hierarchy
      return mergedConfig;
    }
    else {
      return buildMergedContextConfiguration(testClass,
        ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null,
        cacheAwareContextLoaderDelegate);
    }
  }

  /**
   * Build the {@link MergedContextConfiguration merged context configuration}
   * for the supplied {@link Class testClass}, context configuration attributes,
   * and parent context configuration.
   * @param testClass the test class for which the {@code MergedContextConfiguration}
   * should be built (must not be {@code null})
   * @param configAttributesList the list of context configuration attributes for the
   * specified test class, ordered <em>bottom-up</em> (i.e., as if we were
   * traversing up the class hierarchy); never {@code null} or empty
   * @param parentConfig the merged context configuration for the parent application
   * context in a context hierarchy, or {@code null} if there is no parent
   * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
   * be passed to the {@code MergedContextConfiguration} constructor
   * @return the merged context configuration
   * @see #resolveContextLoader
   * @see ContextLoaderUtils#resolveContextConfigurationAttributes
   * @see SmartContextLoader#processContextConfiguration
   * @see ContextLoader#processLocations
   * @see ActiveProfilesUtils#resolveActiveProfiles
   * @see ApplicationContextInitializerUtils#resolveInitializerClasses
   * @see MergedContextConfiguration
   */
  private MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
      List<ContextConfigurationAttributes> configAttributesList, MergedContextConfiguration parentConfig,
      CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {

    ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList);
    List<String> locationsList = new ArrayList<String>();
    List<Class<?>> classesList = new ArrayList<Class<?>>();

    for (ContextConfigurationAttributes configAttributes : configAttributesList) {
      if (logger.isTraceEnabled()) {
        logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
          configAttributes));
      }
      if (contextLoader instanceof SmartContextLoader) {
        SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
        smartContextLoader.processContextConfiguration(configAttributes);
        locationsList.addAll(0, Arrays.asList(configAttributes.getLocations()));
        classesList.addAll(0, Arrays.asList(configAttributes.getClasses()));
      }
      else {
        String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
          configAttributes.getLocations());
        locationsList.addAll(0, Arrays.asList(processedLocations));
        // Legacy ContextLoaders don't know how to process classes
      }
      if (!configAttributes.isInheritLocations()) {
        break;
      }
    }

    String[] locations = StringUtils.toStringArray(locationsList);
    Class<?>[] classes = ClassUtils.toClassArray(classesList);
    Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
    ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList);
    String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass);
    MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass);

    MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, locations, classes,
      initializerClasses, activeProfiles, mergedTestPropertySources.getLocations(),
      mergedTestPropertySources.getProperties(), contextLoader, cacheAwareContextLoaderDelegate, parentConfig);

    return processMergedContextConfiguration(mergedConfig);
  }

  /**
   * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
   * supplied list of {@link ContextConfigurationAttributes} and then instantiate
   * and return that {@code ContextLoader}.
   * <p>If the user has not explicitly declared which loader to use, the value
   * returned from {@link #getDefaultContextLoaderClass} will be used as the
   * default context loader class. For details on the class resolution process,
   * see {@link #resolveExplicitContextLoaderClass} and
   * {@link #getDefaultContextLoaderClass}.
   * @param testClass the test class for which the {@code ContextLoader} should be
   * resolved; must not be {@code null}
   * @param configAttributesList the list of configuration attributes to process; must
   * not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
   * (i.e., as if we were traversing up the class hierarchy)
   * @return the resolved {@code ContextLoader} for the supplied {@code testClass}
   * (never {@code null})
   */
  private ContextLoader resolveContextLoader(Class<?> testClass,
      List<ContextConfigurationAttributes> configAttributesList) {

    Assert.notNull(testClass, "Class must not be null");
    Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");

    Class<? extends ContextLoader> contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList);
    if (contextLoaderClass == null) {
      contextLoaderClass = getDefaultContextLoaderClass(testClass);
    }
    if (logger.isTraceEnabled()) {
      logger.trace(String.format("Using ContextLoader class [%s] for test class [%s]",
        contextLoaderClass.getName(), testClass.getName()));
    }
    return BeanUtils.instantiateClass(contextLoaderClass, ContextLoader.class);
  }

  /**
   * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
   * list of {@link ContextConfigurationAttributes}.
   * <p>Beginning with the first level in the context configuration attributes hierarchy:
   * <ol>
   * <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
   * contextLoaderClass} property of {@link ContextConfigurationAttributes} is
   * configured with an explicit class, that class will be returned.</li>
   * <li>If an explicit {@code ContextLoader} class is not specified at the current
   * level in the hierarchy, traverse to the next level in the hierarchy and return to
   * step #1.</li>
   * </ol>
   * @param configAttributesList the list of configuration attributes to process;
   * must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
   * (i.e., as if we were traversing up the class hierarchy)
   * @return the {@code ContextLoader} class to use for the supplied configuration
   * attributes, or {@code null} if no explicit loader is found
   * @throws IllegalArgumentException if supplied configuration attributes are
   * {@code null} or <em>empty</em>
   */
  private Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
      List<ContextConfigurationAttributes> configAttributesList) {

    Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
    for (ContextConfigurationAttributes configAttributes : configAttributesList) {
      if (logger.isTraceEnabled()) {
        logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s",
          configAttributes));
      }
      Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
      if (!ContextLoader.class.equals(contextLoaderClass)) {
        if (logger.isDebugEnabled()) {
          logger.debug(String.format(
            "Found explicit ContextLoader class [%s] for context configuration attributes %s",
            contextLoaderClass.getName(), configAttributes));
        }
        return contextLoaderClass;
      }
    }
    return null;
  }

  /**
   * Determine the default {@link ContextLoader} class to use for the supplied
   * test class.
   * <p>The class returned by this method will only be used if a {@code ContextLoader}
   * class has not been explicitly declared via {@link ContextConfiguration#loader}.
   * @param testClass the test class for which to retrieve the default
   * {@code ContextLoader} class
   */
  protected abstract Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass);

  /**
   * Process the supplied, newly instantiated {@link MergedContextConfiguration} instance.
   * <p>The returned {@link MergedContextConfiguration} instance may be a wrapper
   * around or a replacement for the original.
   * <p>The default implementation simply returns the supplied instance unmodified.
   * <p>Concrete subclasses may choose to return a specialized subclass of
   * {@link MergedContextConfiguration} based on properties in the supplied instance.
   * @param mergedConfig the {@code MergedContextConfiguration} to process;
   * never {@code null}
   * @return a fully initialized {@code MergedContextConfiguration}; never
   * {@code null}
   */
  protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
    return mergedConfig;
  }

}
TOP

Related Classes of org.springframework.test.context.support.AbstractTestContextBootstrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.