diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java b/org.springframework.test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java new file mode 100644 index 00000000000..ba68d37a741 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java @@ -0,0 +1,151 @@ +/* + * 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.ObjectUtils; + +/** + * TODO [SPR-8386] Document ContextConfigurationAttributes. + * + * @author Sam Brannen + * @since 3.1 + * @see ContextConfiguration + */ +public class ContextConfigurationAttributes { + + private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class); + + private final Class declaringClass; + + private final String[] locations; + + private final Class[] classes; + + private final boolean inheritLocations; + + private final Class contextLoader; + + + /** + * Resolves resource locations from the {@link ContextConfiguration#locations() locations} + * and {@link ContextConfiguration#value() value} attributes of the supplied + * {@link ContextConfiguration} annotation. + * + * @throws IllegalStateException if both the locations and value attributes have been declared + */ + static String[] resolveLocations(Class declaringClass, ContextConfiguration contextConfiguration) { + + String[] locations = contextConfiguration.locations(); + String[] valueLocations = contextConfiguration.value(); + + if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) { + String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'value' [%s] " + + "and 'locations' [%s] attributes. Only one declaration of resource " + + "locations is permitted per @ContextConfiguration annotation.", declaringClass, + ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations)); + logger.error(msg); + throw new IllegalStateException(msg); + } + else if (!ObjectUtils.isEmpty(valueLocations)) { + locations = valueLocations; + } + + return locations; + } + + /** + * TODO Document ContextConfigurationAttributes constructor. + * + * @param declaringClass + * @param contextConfiguration + */ + public ContextConfigurationAttributes(Class declaringClass, ContextConfiguration contextConfiguration) { + this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(), + contextConfiguration.inheritLocations(), contextConfiguration.loader()); + } + + /** + * TODO Document ContextConfigurationAttributes constructor. + * + * @param declaringClass + * @param locations + * @param classes + * @param inheritLocations + * @param contextLoader + */ + public ContextConfigurationAttributes(Class declaringClass, String[] locations, Class[] classes, + boolean inheritLocations, Class contextLoader) { + this.declaringClass = declaringClass; + this.locations = locations; + this.classes = classes; + this.inheritLocations = inheritLocations; + this.contextLoader = contextLoader; + } + + /** + * TODO Document getDeclaringClass(). + */ + public Class getDeclaringClass() { + return this.declaringClass; + } + + /** + * TODO Document getLocations(). + */ + public String[] getLocations() { + return this.locations; + } + + /** + * TODO Document getClasses(). + */ + public Class[] getClasses() { + return this.classes; + } + + /** + * TODO Document isInheritLocations(). + */ + public boolean isInheritLocations() { + return this.inheritLocations; + } + + /** + * TODO Document getContextLoader(). + */ + public Class getContextLoader() { + return this.contextLoader; + } + + /** + * TODO Document overridden toString(). + */ + @Override + public String toString() { + return new ToStringCreator(this)// + .append("declaringClass", this.declaringClass)// + .append("locations", ObjectUtils.nullSafeToString(this.locations))// + .append("classes", ObjectUtils.nullSafeToString(this.classes))// + .append("inheritLocations", this.inheritLocations)// + .append("contextLoader", this.contextLoader)// + .toString(); + } + +} 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 1309d300125..574ff323b6b 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 @@ -281,6 +281,69 @@ abstract class ContextLoaderUtils { return StringUtils.toStringArray(activeProfiles); } + /** + * TODO Document resolveContextConfigurationAttributes(). + * + * @param clazz + * @return + */ + static List resolveContextConfigurationAttributes(Class clazz) { + Assert.notNull(clazz, "Class must not be null"); + + final List attributesList = new ArrayList(); + + Class annotationType = ContextConfiguration.class; + Class declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz); + Assert.notNull(declaringClass, String.format( + "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType, + clazz)); + + while (declaringClass != null) { + ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType); + + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", + contextConfiguration, declaringClass)); + } + + ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, + contextConfiguration); + if (logger.isTraceEnabled()) { + logger.trace("Resolved context configuration attributes: " + attributes); + } + + attributesList.add(0, attributes); + + declaringClass = contextConfiguration.inheritLocations() ? AnnotationUtils.findAnnotationDeclaringClass( + annotationType, declaringClass.getSuperclass()) : null; + } + + return attributesList; + } + + /** + * TODO Document buildMergedContextConfiguration(). + * + * @param testClass + * @param defaultContextLoaderClassName + * @return + */ + static MergedContextConfiguration buildMergedContextConfiguration(Class testClass, + String defaultContextLoaderClassName) { + + ContextLoader contextLoader = resolveContextLoader(testClass, defaultContextLoaderClassName); + + // TODO Merge locations from List + String[] locations = resolveContextLocations(contextLoader, testClass); + + // TODO Merge classes from List + Class[] classes = {}; + + String[] activeProfiles = resolveActiveProfiles(testClass); + + return new MergedContextConfiguration(testClass, locations, classes, activeProfiles, contextLoader); + } + /** * Strategy interface for resolving application context resource locations. @@ -317,24 +380,7 @@ abstract class ContextLoaderUtils { * attributes have been declared */ public String[] resolveLocations(ContextConfiguration contextConfiguration, Class declaringClass) { - - String[] locations = contextConfiguration.locations(); - String[] valueLocations = contextConfiguration.value(); - - if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) { - String msg = String.format( - "Test class [%s] has been configured with @ContextConfiguration's 'value' [%s] " - + "and 'locations' [%s] attributes. Only one declaration of resource " - + "locations is permitted per @ContextConfiguration annotation.", declaringClass, - ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations)); - ContextLoaderUtils.logger.error(msg); - throw new IllegalStateException(msg); - } - else if (!ObjectUtils.isEmpty(valueLocations)) { - locations = valueLocations; - } - - return locations; + return ContextConfigurationAttributes.resolveLocations(declaringClass, contextConfiguration); } } diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/org.springframework.test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java new file mode 100644 index 00000000000..87829aedcf0 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -0,0 +1,110 @@ +/* + * 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 org.springframework.core.style.ToStringCreator; +import org.springframework.util.ObjectUtils; + +/** + * TODO [SPR-8386] Document MergedContextConfiguration. + * + * @author Sam Brannen + * @since 3.1 + * @see ContextConfiguration + * @see ActiveProfiles + */ +public class MergedContextConfiguration { + + private final Class testClass; + + private final String[] locations; + + private final Class[] classes; + + private final String[] activeProfiles; + + private final ContextLoader contextLoader; + + + /** + * TODO Document MergedContextConfiguration constructor. + * + * @param testClass + * @param locations + * @param classes + * @param activeProfiles + * @param contextLoader + */ + public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, + String[] activeProfiles, ContextLoader contextLoader) { + this.testClass = testClass; + this.locations = locations; + this.classes = classes; + this.activeProfiles = activeProfiles; + this.contextLoader = contextLoader; + } + + /** + * TODO Document getTestClass(). + */ + public Class getTestClass() { + return this.testClass; + } + + /** + * TODO Document getLocations(). + */ + public String[] getLocations() { + return this.locations; + } + + /** + * TODO Document getClasses(). + */ + public Class[] getClasses() { + return this.classes; + } + + /** + * TODO Document getActiveProfiles(). + */ + public String[] getActiveProfiles() { + return this.activeProfiles; + } + + /** + * TODO Document getContextLoader(). + */ + public ContextLoader getContextLoader() { + return this.contextLoader; + } + + /** + * TODO Document overridden toString(). + */ + @Override + public String toString() { + return new ToStringCreator(this)// + .append("testClass", this.testClass)// + .append("locations", ObjectUtils.nullSafeToString(this.locations))// + .append("classes", ObjectUtils.nullSafeToString(this.classes))// + .append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles))// + .append("contextLoader", this.contextLoader)// + .toString(); + } + +} diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java new file mode 100644 index 00000000000..c2ba977130b --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java @@ -0,0 +1,38 @@ +/* + * 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 org.springframework.context.ApplicationContext; + +/** + * TODO [SPR-8386] Document SmartContextLoader. + * + * @author Sam Brannen + * @since 3.1 + */ +public interface SmartContextLoader extends ContextLoader { + + /** + * TODO Document loadContext(). + * + * @param mergedContextConfiguration + * @return + * @throws Exception + */ + ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) throws Exception; + +} 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 index 60012d3f9f4..7a32edb9ae1 100644 --- 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 @@ -16,6 +16,7 @@ package org.springframework.test.context; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -24,6 +25,10 @@ import java.util.Arrays; import java.util.List; import org.junit.Test; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.support.GenericPropertiesContextLoader; +import org.springframework.test.context.support.GenericXmlContextLoader; /** * Unit tests for {@link ContextLoaderUtils}. @@ -33,25 +38,138 @@ import org.junit.Test; */ public class ContextLoaderUtilsTests { + private static final String[] EMPTY_STRING_ARRAY = new String[] {}; + + + private void assertAttributes(ContextConfigurationAttributes attributes, Class expectedDeclaringClass, + String[] expectedLocations, Class[] expectedClasses, + Class expectedContextLoaderClass, boolean expectedInheritLocations) { + assertEquals(expectedDeclaringClass, attributes.getDeclaringClass()); + assertArrayEquals(expectedLocations, attributes.getLocations()); + assertArrayEquals(expectedClasses, attributes.getClasses()); + assertEquals(expectedInheritLocations, attributes.isInheritLocations()); + assertEquals(expectedContextLoaderClass, attributes.getContextLoader()); + } + + private void assertFooAttributes(ContextConfigurationAttributes attributes) { + assertAttributes(attributes, Foo.class, new String[] { "/foo.xml" }, new Class[] { FooConfig.class }, + ContextLoader.class, false); + } + + private void assertBarAttributes(ContextConfigurationAttributes attributes) { + assertAttributes(attributes, Bar.class, new String[] { "/bar.xml" }, new Class[] { BarConfig.class }, + AnnotationConfigContextLoader.class, true); + } + + private void assertMergedContextConfiguration(MergedContextConfiguration mergedConfig, Class expectedTestClass, + String[] expectedLocations, Class expectedContextLoaderClass) { + assertNotNull(mergedConfig); + assertEquals(expectedTestClass, mergedConfig.getTestClass()); + assertNotNull(mergedConfig.getLocations()); + assertArrayEquals(expectedLocations, mergedConfig.getLocations()); + assertNotNull(mergedConfig.getClasses()); + assertNotNull(mergedConfig.getActiveProfiles()); + assertEquals(expectedContextLoaderClass, mergedConfig.getContextLoader().getClass()); + } + + @Test(expected = IllegalStateException.class) + public void resolveContextConfigurationAttributesWithConflictingLocations() { + ContextLoaderUtils.resolveContextConfigurationAttributes(ConflictingLocations.class); + } + + @Test + public void resolveContextConfigurationAttributesWithBareAnnotations() { + List attributesList = ContextLoaderUtils.resolveContextConfigurationAttributes(BareAnnotations.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertAttributes(attributesList.get(0), BareAnnotations.class, EMPTY_STRING_ARRAY, new Class[] {}, + ContextLoader.class, true); + } + + @Test + public void resolveContextConfigurationAttributesWithLocalAnnotation() { + List attributesList = ContextLoaderUtils.resolveContextConfigurationAttributes(Foo.class); + assertNotNull(attributesList); + assertEquals(1, attributesList.size()); + assertFooAttributes(attributesList.get(0)); + } + + @Test + public void resolveContextConfigurationAttributesWithLocalAndInheritedAnnotations() { + List attributesList = ContextLoaderUtils.resolveContextConfigurationAttributes(Bar.class); + assertNotNull(attributesList); + assertEquals(2, attributesList.size()); + assertFooAttributes(attributesList.get(0)); + assertBarAttributes(attributesList.get(1)); + } + + @Test(expected = IllegalArgumentException.class) + public void buildMergedContextConfigurationWithoutAnnotation() { + ContextLoaderUtils.buildMergedContextConfiguration(Enigma.class, null); + } + + @Test + public void buildMergedContextConfigurationWithBareAnnotations() { + Class testClass = BareAnnotations.class; + MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass, null); + + assertMergedContextConfiguration( + mergedConfig, + testClass, + new String[] { "classpath:/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml" }, + GenericXmlContextLoader.class); + } + + @Test + public void buildMergedContextConfigurationWithLocalAnnotation() { + Class testClass = Foo.class; + MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass, null); + + assertMergedContextConfiguration(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, + GenericXmlContextLoader.class); + } + + @Test + public void buildMergedContextConfigurationWithLocalAnnotationAndOverriddenContexLoader() { + Class testClass = Foo.class; + Class expectedContextLoaderClass = GenericPropertiesContextLoader.class; + MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass, + expectedContextLoaderClass.getName()); + + assertMergedContextConfiguration(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, + expectedContextLoaderClass); + } + + @Test + public void buildMergedContextConfigurationWithLocalAndInheritedAnnotations() { + Class testClass = Bar.class; + MergedContextConfiguration mergedConfig = ContextLoaderUtils.buildMergedContextConfiguration(testClass, null); + + // TODO Assert @Configuration classes instead of locations + String[] expectedLocations = new String[] { + "org.springframework.test.context.ContextLoaderUtilsTests$FooConfig", + "org.springframework.test.context.ContextLoaderUtilsTests$BarConfig" }; + + assertMergedContextConfiguration(mergedConfig, testClass, expectedLocations, + AnnotationConfigContextLoader.class); + } + @Test public void resolveActiveProfilesWithoutAnnotation() { String[] profiles = ContextLoaderUtils.resolveActiveProfiles(Enigma.class); - assertNotNull(profiles); - assertEquals(0, profiles.length); + assertArrayEquals(EMPTY_STRING_ARRAY, profiles); } @Test public void resolveActiveProfilesWithNoProfilesDeclared() { - String[] profiles = ContextLoaderUtils.resolveActiveProfiles(NoProfilesDeclared.class); - assertNotNull(profiles); - assertEquals(0, profiles.length); + String[] profiles = ContextLoaderUtils.resolveActiveProfiles(BareAnnotations.class); + assertArrayEquals(EMPTY_STRING_ARRAY, profiles); } @Test public void resolveActiveProfilesWithEmptyProfiles() { String[] profiles = ContextLoaderUtils.resolveActiveProfiles(EmptyProfiles.class); - assertNotNull(profiles); - assertEquals(0, profiles.length); + assertArrayEquals(EMPTY_STRING_ARRAY, profiles); } @Test @@ -70,16 +188,14 @@ public class ContextLoaderUtilsTests { public void resolveActiveProfilesWithLocalAnnotation() { String[] profiles = ContextLoaderUtils.resolveActiveProfiles(Foo.class); assertNotNull(profiles); - assertEquals(1, profiles.length); - assertEquals("foo", profiles[0]); + assertArrayEquals(new String[] { "foo" }, profiles); } @Test public void resolveActiveProfilesWithInheritedAnnotation() { String[] profiles = ContextLoaderUtils.resolveActiveProfiles(InheritedFoo.class); assertNotNull(profiles); - assertEquals(1, profiles.length); - assertEquals("foo", profiles[0]); + assertArrayEquals(new String[] { "foo" }, profiles); } @Test @@ -108,8 +224,13 @@ public class ContextLoaderUtilsTests { private static class Enigma { } + @ContextConfiguration(value = "x", locations = "y") + private static class ConflictingLocations { + } + + @ContextConfiguration @ActiveProfiles - private static class NoProfilesDeclared { + private static class BareAnnotations { } @ActiveProfiles({ " ", "\t" }) @@ -120,6 +241,11 @@ public class ContextLoaderUtilsTests { private static class DuplicatedProfiles { } + @Configuration + private static class FooConfig { + } + + @ContextConfiguration(locations = "/foo.xml", classes = FooConfig.class, inheritLocations = false) @ActiveProfiles(profiles = "foo") private static class Foo { } @@ -127,6 +253,11 @@ public class ContextLoaderUtilsTests { private static class InheritedFoo extends Foo { } + @Configuration + private static class BarConfig { + } + + @ContextConfiguration(locations = "/bar.xml", classes = BarConfig.class, inheritLocations = true, loader = AnnotationConfigContextLoader.class) @ActiveProfiles("bar") private static class Bar extends Foo { }