/* * Copyright 2002-2019 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 * * https://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.HashSet; import java.util.LinkedHashMap; 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.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.SmartContextLoader; import org.springframework.test.context.TestConfiguration; import org.springframework.test.context.TestConfiguration.EnclosingConfiguration; import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally; import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes; /** * Utility methods for resolving {@link ContextConfigurationAttributes} from the * {@link ContextConfiguration @ContextConfiguration} and * {@link ContextHierarchy @ContextHierarchy} annotations for use with * {@link SmartContextLoader SmartContextLoaders}. * * @author Sam Brannen * @since 3.1 * @see SmartContextLoader * @see ContextConfigurationAttributes * @see ContextConfiguration * @see ContextHierarchy */ abstract class ContextLoaderUtils { static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#"; private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class); /** * Resolve the list of lists of {@linkplain ContextConfigurationAttributes context * configuration attributes} for the supplied {@linkplain Class test class} and its * superclasses, taking into account context hierarchies declared via * {@link ContextHierarchy @ContextHierarchy} and * {@link ContextConfiguration @ContextConfiguration}. *
The outer list represents a top-down ordering of context configuration * attributes, where each element in the list represents the context configuration * declared on a given test class in the class hierarchy. Each nested list * contains the context configuration attributes declared either via a single * instance of {@code @ContextConfiguration} on the particular class or via * multiple instances of {@code @ContextConfiguration} declared within a * single {@code @ContextHierarchy} instance on the particular class. * Furthermore, each nested list maintains the order in which * {@code @ContextConfiguration} instances are declared. *
Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will not
* be taken into consideration. If these flags need to be honored, that must be
* handled manually when traversing the nested lists returned by this method.
* @param testClass the class for which to resolve the context hierarchy attributes
* (must not be {@code null})
* @return the list of lists of configuration attributes for the specified class;
* never {@code null}
* @throws IllegalArgumentException if the supplied class is {@code null}; or if
* neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is
* present on the supplied class
* @throws IllegalStateException if a test class or composed annotation
* in the class hierarchy declares both {@code @ContextConfiguration} and
* {@code @ContextHierarchy} as top-level annotations.
* @since 3.2.2
* @see #buildContextHierarchyMap(Class)
* @see #resolveContextConfigurationAttributes(Class)
*/
@SuppressWarnings("unchecked")
static List Each value in the map represents the consolidated list of {@linkplain
* ContextConfigurationAttributes context configuration attributes} for a
* given level in the context hierarchy (potentially across the test class
* hierarchy), keyed by the {@link ContextConfiguration#name() name} of the
* context hierarchy level.
* If a given level in the context hierarchy does not have an explicit
* name (i.e., configured via {@link ContextConfiguration#name}), a name will
* be generated for that hierarchy level by appending the numerical level to
* the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}.
* @param testClass the class for which to resolve the context hierarchy map
* (must not be {@code null})
* @return a map of context configuration attributes for the context hierarchy,
* keyed by context hierarchy level name; never {@code null}
* @throws IllegalArgumentException if the lists of context configuration
* attributes for each level in the {@code @ContextHierarchy} do not define
* unique context configuration within the overall hierarchy.
* @since 3.2.2
* @see #resolveContextHierarchyAttributes(Class)
*/
static Map Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will not
* be taken into consideration. If these flags need to be honored, that must be
* handled manually when traversing the list returned by this method.
* @param testClass the class for which to resolve the configuration attributes
* (must not be {@code null})
* @return the list of configuration attributes for the specified class, ordered
* bottom-up (i.e., as if we were traversing up the class hierarchy);
* never {@code null}
* @throws IllegalArgumentException if the supplied class is {@code null} or if
* {@code @ContextConfiguration} is not present on the supplied class
*/
static List> resolveContextHierarchyAttributes(Class> testClass) {
Assert.notNull(testClass, "Class must not be null");
Class
> hierarchyAttributes = new ArrayList<>();
UntypedAnnotationDescriptor desc =
findAnnotationDescriptorForTypes(testClass, contextConfigType, contextHierarchyType);
Assert.notNull(desc, () -> String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]",
contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));
while (desc != null) {
Class> rootDeclaringClass = desc.getRootDeclaringClass();
Class> declaringClass = desc.getDeclaringClass();
boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
String msg = String.format("Class [%s] has been configured with both @ContextConfiguration " +
"and @ContextHierarchy. Only one of these annotations may be declared on a test class " +
"or composed annotation.", declaringClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}
List
> set = new HashSet<>(map.values());
if (set.size() != map.size()) {
String msg = String.format("The @ContextConfiguration elements configured via @ContextHierarchy in " +
"test class [%s] and its superclasses must define unique contexts per hierarchy level.",
testClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}
return map;
}
/**
* Resolve the list of {@linkplain ContextConfigurationAttributes context
* configuration attributes} for the supplied {@linkplain Class test class} and its
* superclasses.
*