Introduce ActiveProfilesResolver in the TCF
Prior to this commit, the active bean definition profiles to use when loading an ApplicationContext for tests could only be configured declaratively (i.e., via hard-coded values supplied to the 'value' or 'profiles' attribute of @ActiveProfiles). This commit makes it possible to programmatically configure active bean definition profiles in tests via a new ActiveProfileResolver interface. Custom resolvers can be registered via a new 'resolver' attribute introduced in @ActiveProfiles. Overview of changes: - Introduced a new ActiveProfilesResolver API. - Added a 'resolver' attribute to @ActiveProfiles. - Updated ContextLoaderUtils.resolveActiveProfiles() to support ActiveProfilesResolvers. - Documented these new features in the reference manual. - Added new content to the reference manual regarding the 'inheritProfiles' attribute of @ActiveProfiles - Removed the use of <lineannotation> Docbook markup in the testing chapter of the reference manual for Java code examples in order to allow comments to have proper syntax highlighting in the generated HTML and PDF. Issue: SPR-10338
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -34,6 +34,7 @@ import java.lang.annotation.Target;
|
||||
* @see SmartContextLoader
|
||||
* @see MergedContextConfiguration
|
||||
* @see ContextConfiguration
|
||||
* @see ActiveProfilesResolver
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.annotation.Profile
|
||||
*/
|
||||
@@ -47,8 +48,8 @@ public @interface ActiveProfiles {
|
||||
* Alias for {@link #profiles}.
|
||||
*
|
||||
* <p>This attribute may <strong>not</strong> be used in conjunction
|
||||
* with {@link #profiles}, but it may be used <em>instead</em> of
|
||||
* {@link #profiles}.
|
||||
* with {@link #profiles} or {@link #resolver}, but it may be used
|
||||
* <em>instead</em> of them.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
@@ -56,11 +57,24 @@ public @interface ActiveProfiles {
|
||||
* The bean definition profiles to activate.
|
||||
*
|
||||
* <p>This attribute may <strong>not</strong> be used in conjunction
|
||||
* with {@link #value}, but it may be used <em>instead</em> of
|
||||
* {@link #value}.
|
||||
* with {@link #value} or {@link #resolver}, but it may be used
|
||||
* <em>instead</em> of them.
|
||||
*/
|
||||
String[] profiles() default {};
|
||||
|
||||
/**
|
||||
* The type of {@link ActiveProfilesResolver} to use for resolving the active
|
||||
* bean definition profiles programmatically.
|
||||
*
|
||||
* <p>This attribute may <strong>not</strong> be used in conjunction
|
||||
* with {@link #profiles} or {@link #value}, but it may be used <em>instead</em>
|
||||
* of them in order to resolve the active profiles programmatically.
|
||||
*
|
||||
* @since 4.0
|
||||
* @see ActiveProfilesResolver
|
||||
*/
|
||||
Class<? extends ActiveProfilesResolver> resolver() default ActiveProfilesResolver.class;
|
||||
|
||||
/**
|
||||
* Whether or not bean definition profiles from superclasses should be
|
||||
* <em>inherited</em>.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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;
|
||||
|
||||
/**
|
||||
* Strategy interface for programmatically resolving which <em>active bean
|
||||
* definition profiles</em> should be used when loading an
|
||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
||||
* for a test class.
|
||||
*
|
||||
* <p>A custom {@code ActiveProfilesResolver} can be registered via the
|
||||
* {@link ActiveProfiles#resolver resolver} attribute of {@code @ActiveProfiles}.
|
||||
*
|
||||
* <p>Concrete implementations must provide a {@code public} no-args constructor.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Michail Nikolaev
|
||||
* @since 4.0
|
||||
* @see ActiveProfiles
|
||||
*/
|
||||
public interface ActiveProfilesResolver {
|
||||
|
||||
/**
|
||||
* Resolve the <em>bean definition profiles</em> to use when loading an
|
||||
* {@code ApplicationContext} for the given {@linkplain Class test class}.
|
||||
*
|
||||
* @param testClass the test class for which the profiles should be resolved;
|
||||
* never {@code null}
|
||||
* @return the list of bean definition profiles to use when loading the
|
||||
* {@code ApplicationContext}; never {@code null}
|
||||
* @see ActiveProfiles#resolver
|
||||
* @see ActiveProfiles#inheritProfiles
|
||||
*/
|
||||
String[] resolve(Class<?> testClass);
|
||||
|
||||
}
|
||||
@@ -16,11 +16,6 @@
|
||||
|
||||
package org.springframework.test.context;
|
||||
|
||||
import static org.springframework.beans.BeanUtils.instantiateClass;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClassForTypes;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
@@ -42,6 +37,9 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.beans.BeanUtils.*;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
|
||||
/**
|
||||
* Utility methods for working with {@link ContextLoader ContextLoaders} and
|
||||
* {@link SmartContextLoader SmartContextLoaders} and resolving resource locations,
|
||||
@@ -49,12 +47,14 @@ import org.springframework.util.StringUtils;
|
||||
* initializers.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Michail Nikolaev
|
||||
* @since 3.1
|
||||
* @see ContextLoader
|
||||
* @see SmartContextLoader
|
||||
* @see ContextConfiguration
|
||||
* @see ContextConfigurationAttributes
|
||||
* @see ActiveProfiles
|
||||
* @see ActiveProfilesResolver
|
||||
* @see ApplicationContextInitializer
|
||||
* @see ContextHierarchy
|
||||
* @see MergedContextConfiguration
|
||||
@@ -477,24 +477,43 @@ abstract class ContextLoaderUtils {
|
||||
|
||||
while (declaringClass != null) {
|
||||
ActiveProfiles annotation = declaringClass.getAnnotation(annotationType);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
|
||||
declaringClass.getName()));
|
||||
}
|
||||
validateActiveProfilesConfiguration(declaringClass, annotation);
|
||||
|
||||
String[] profiles = annotation.profiles();
|
||||
String[] valueProfiles = annotation.value();
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
|
||||
|
||||
if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) {
|
||||
String msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
|
||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean "
|
||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
|
||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
|
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
|
||||
|
||||
if (resolverDeclared) {
|
||||
ActiveProfilesResolver resolver = null;
|
||||
try {
|
||||
resolver = instantiateClass(resolverClass, ActiveProfilesResolver.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
String msg = String.format("Could not instantiate ActiveProfilesResolver of "
|
||||
+ "type [%s] for test class [%s].", resolverClass.getName(), declaringClass.getName());
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg, e);
|
||||
}
|
||||
|
||||
if (resolver != null) {
|
||||
profiles = resolver.resolve(declaringClass);
|
||||
if (profiles == null) {
|
||||
String msg = String.format(
|
||||
"ActiveProfilesResolver [%s] returned a null array of bean definition profiles.",
|
||||
resolverClass.getName());
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!ObjectUtils.isEmpty(valueProfiles)) {
|
||||
else if (valueDeclared) {
|
||||
profiles = valueProfiles;
|
||||
}
|
||||
|
||||
@@ -511,6 +530,43 @@ abstract class ContextLoaderUtils {
|
||||
return StringUtils.toStringArray(activeProfiles);
|
||||
}
|
||||
|
||||
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, ActiveProfiles annotation) {
|
||||
String[] valueProfiles = annotation.value();
|
||||
String[] profiles = annotation.profiles();
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annotation.resolver();
|
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
|
||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
|
||||
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
|
||||
|
||||
String msg = null;
|
||||
|
||||
if (valueDeclared && profilesDeclared) {
|
||||
msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
|
||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean "
|
||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
|
||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
|
||||
}
|
||||
else if (valueDeclared && resolverDeclared) {
|
||||
msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
|
||||
+ "and 'resolver' [%s] attributes. Only one source of active bean "
|
||||
+ "definition profiles is permitted per @ActiveProfiles annotation, "
|
||||
+ "either declaritively or programmatically.", declaringClass.getName(),
|
||||
ObjectUtils.nullSafeToString(valueProfiles), resolverClass.getName());
|
||||
}
|
||||
else if (profilesDeclared && resolverDeclared) {
|
||||
msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'profiles' [%s] "
|
||||
+ "and 'resolver' [%s] attributes. Only one source of active bean "
|
||||
+ "definition profiles is permitted per @ActiveProfiles annotation, "
|
||||
+ "either declaritively or programmatically.", declaringClass.getName(),
|
||||
ObjectUtils.nullSafeToString(profiles), resolverClass.getName());
|
||||
}
|
||||
|
||||
if (msg != null) {
|
||||
logger.error(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link MergedContextConfiguration merged context configuration} for
|
||||
* the supplied {@link Class testClass} and {@code defaultContextLoaderClassName},
|
||||
|
||||
Reference in New Issue
Block a user