diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/ActivateProfiles.java b/org.springframework.test/src/main/java/org/springframework/test/context/ActivateProfiles.java
new file mode 100644
index 00000000000..63c839eed38
--- /dev/null
+++ b/org.springframework.test/src/main/java/org/springframework/test/context/ActivateProfiles.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2011 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * {@code ActivateProfiles} is a class-level annotation that is used to
+ * activate the bean definition profiles to use when loading an
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * for test classes.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see ContextConfiguration
+ * @see ContextLoader
+ * @see org.springframework.context.ApplicationContext
+ * @see org.springframework.context.annotation.Profile
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ActivateProfiles {
+
+ /**
+ * Alias for {@link #profiles() profiles}.
+ *
+ *
This attribute may not be used in conjunction
+ * with {@link #profiles}, but it may be used instead of
+ * {@link #profiles}.
+ */
+ String[] value() default {};
+
+ /**
+ * The list of bean definition profiles to activate.
+ *
+ *
This attribute may not be used in conjunction
+ * with {@link #value}, but it may be used instead of
+ * {@link #value}.
+ */
+ String[] profiles() default {};
+
+ /**
+ * TODO Document inheritLocations.
+ */
+ boolean inheritProfiles() default true;
+
+}
diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
index 0ad20cec081..2f414deff15 100644
--- a/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
+++ b/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
@@ -209,6 +209,58 @@ abstract class ContextLoaderUtils {
return locationsList.toArray(new String[locationsList.size()]);
}
+ /**
+ * TODO Document resolveActivatedProfiles().
+ *
+ * @param clazz
+ * @return
+ */
+ static String[] resolveActivatedProfiles(Class> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+
+ Class annotationType = ActivateProfiles.class;
+ Class> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
+
+ if (declaringClass == null && logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType, clazz));
+ }
+
+ List profilesList = new ArrayList();
+
+ while (declaringClass != null) {
+ ActivateProfiles activateProfiles = declaringClass.getAnnotation(annotationType);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ActivateProfiles [%s] for declaring class [%s].",
+ activateProfiles, declaringClass));
+ }
+
+ String[] profiles = activateProfiles.profiles();
+ String[] valueProfiles = activateProfiles.value();
+
+ if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) {
+ String msg = String.format("Test class [%s] has been configured with @ActivateProfiles' 'value' [%s] "
+ + "and 'profiles' [%s] attributes. Only one declaration of bean "
+ + "definition profiles is permitted per @ActivateProfiles annotation.", declaringClass,
+ ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ else if (!ObjectUtils.isEmpty(valueProfiles)) {
+ profiles = valueProfiles;
+ }
+
+ profilesList.addAll(0, Arrays. asList(profiles));
+
+ declaringClass = activateProfiles.inheritProfiles() ? AnnotationUtils.findAnnotationDeclaringClass(
+ annotationType, declaringClass.getSuperclass()) : null;
+ }
+
+ return profilesList.toArray(new String[profilesList.size()]);
+ }
+
/**
* Strategy interface for resolving application context resource locations.
diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java b/org.springframework.test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
new file mode 100644
index 00000000000..0ed8ccb4927
--- /dev/null
+++ b/org.springframework.test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2011 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link ContextLoaderUtils}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+public class ContextLoaderUtilsTests {
+
+ @Test
+ public void resolveActivatedProfilesWithoutAnnotation() {
+ String[] profiles = ContextLoaderUtils.resolveActivatedProfiles(Enigma.class);
+ assertNotNull(profiles);
+ assertEquals(0, profiles.length);
+ }
+
+ @Test
+ public void resolveActivatedProfilesWithLocalAnnotation() {
+ String[] profiles = ContextLoaderUtils.resolveActivatedProfiles(Foo.class);
+ assertNotNull(profiles);
+ assertEquals(1, profiles.length);
+ assertEquals("foo", profiles[0]);
+ }
+
+ @Test
+ public void resolveActivatedProfilesWithInheritedAnnotation() {
+ String[] profiles = ContextLoaderUtils.resolveActivatedProfiles(InheritedFoo.class);
+ assertNotNull(profiles);
+ assertEquals(1, profiles.length);
+ assertEquals("foo", profiles[0]);
+ }
+
+ @Test
+ public void resolveActivatedProfilesWithLocalAndInheritedAnnotations() {
+ String[] profiles = ContextLoaderUtils.resolveActivatedProfiles(Bar.class);
+ assertNotNull(profiles);
+ assertEquals(2, profiles.length);
+ assertEquals("foo", profiles[0]);
+ assertEquals("bar", profiles[1]);
+ }
+
+ @Test
+ public void resolveActivatedProfilesWithOverriddenAnnotation() {
+ String[] profiles = ContextLoaderUtils.resolveActivatedProfiles(Animals.class);
+ assertNotNull(profiles);
+ assertEquals(2, profiles.length);
+ assertEquals("dog", profiles[0]);
+ assertEquals("cat", profiles[1]);
+ }
+
+
+ private static class Enigma {
+ }
+
+ @ActivateProfiles(profiles = "foo")
+ private static class Foo {
+ }
+
+ private static class InheritedFoo extends Foo {
+ }
+
+ @ActivateProfiles("bar")
+ private static class Bar extends Foo {
+ }
+
+ @ActivateProfiles(profiles = { "dog", "cat" }, inheritProfiles = false)
+ private static class Animals extends Bar {
+ }
+
+}