From ebea9ec66c30c47045bf2068f8233a7a0df0bf0c Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Fri, 20 May 2011 03:49:15 +0000 Subject: [PATCH] Introduce reserved default profile support AbstractEnvironment and subclasses now register a reserved default profile named literally 'default' such that with no action on the part of the user, beans defined against the 'default' profile will be registered - if no other profiles are explicitly activated. For example, given the following three files a.xml, b.xml and c.xml: a.xml ----- b.xml ----- c.xml ----- bootstrapping all of the files in a Spring ApplicationContext as follows will result in beans 'a' and 'b', but not 'c' being registered: ApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("a.xml"); ctx.load("b.xml"); ctx.load("c.xml"); ctx.refresh(); ctx.containsBean("a"); // true ctx.containsBean("b"); // true ctx.containsBean("c"); // false whereas activating the 'custom' profile will result in beans 'a' and 'c', but not 'b' being registered: ApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("a.xml"); ctx.load("b.xml"); ctx.load("c.xml"); ctx.getEnvironment().setActiveProfiles("custom"); ctx.refresh(); ctx.containsBean("a"); // true ctx.containsBean("b"); // false ctx.containsBean("c"); // true that is, once the 'custom' profile is activated, beans defined against the the reserved default profile are no longer registered. Beans not defined against any profile ('a') are always registered regardless of which profiles are active, and of course beans registered against specific active profiles ('c') are registered. The reserved default profile is, in practice, just another 'default profile', as might be added through calling env.setDefaultProfiles() or via the 'spring.profiles.default' property. The only difference is that the reserved default is added automatically by AbstractEnvironment implementations. As such, any call to setDefaultProfiles() or value set for the 'spring.profiles.default' will override the reserved default profile. If a user wishes to add their own default profile while keeping the reserved default profile as well, it will need to be explicitly redeclared, e.g.: env.addDefaultProfiles("my-default-profile", "default") The reserved default profile(s) are determined by the value returned from AbstractEnvironment#getReservedDefaultProfiles(). This protected method may be overridden by subclasses in order to customize the set of reserved default profiles. Issue: SPR-8203 git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4334 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../xml/ProfileXmlBeanDefinitionTests.java | 4 +- .../core/env/AbstractEnvironment.java | 62 +++++++-- .../springframework/core/env/Environment.java | 27 ++-- .../core/env/CustomEnvironmentTests.java | 131 +++++++++++------- .../core/env/EnvironmentTests.java | 13 +- 5 files changed, 165 insertions(+), 72 deletions(-) diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java index eab10be5d4b..e750be96dd8 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/xml/ProfileXmlBeanDefinitionTests.java @@ -120,7 +120,7 @@ public class ProfileXmlBeanDefinitionTests { @Test public void testDefaultAndNonDefaultProfile() { - assertThat(beanFactoryFor(DEFAULT_ELIGIBLE_XML, NONE_ACTIVE), not(containsTargetBean())); + assertThat(beanFactoryFor(DEFAULT_ELIGIBLE_XML, NONE_ACTIVE), containsTargetBean()); assertThat(beanFactoryFor(DEFAULT_ELIGIBLE_XML, "other"), not(containsTargetBean())); { @@ -151,7 +151,7 @@ public class ProfileXmlBeanDefinitionTests { //env.setDefaultProfiles("default"); reader.setEnvironment(env); reader.loadBeanDefinitions(new ClassPathResource(DEFAULT_AND_DEV_ELIGIBLE_XML, getClass())); - assertThat(beanFactory, not(containsTargetBean())); + assertThat(beanFactory, containsTargetBean()); } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index 26042815020..7d7ae1b3bb2 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -16,23 +16,28 @@ package org.springframework.core.env; -import static java.lang.String.*; +import static java.lang.String.format; +import static org.springframework.util.StringUtils.commaDelimitedListToSet; +import static org.springframework.util.StringUtils.trimAllWhitespace; + import java.security.AccessControlException; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.core.convert.ConversionService; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import static org.springframework.util.StringUtils.*; /** - * Abstract base class for {@link Environment} implementations. + * Abstract base class for {@link Environment} implementations. Supports the notion of + * reserved default profile names and enables specifying active and default profiles + * through the {@link #ACTIVE_PROFILES_PROPERTY_NAME} and + * {@link #DEFAULT_PROFILES_PROPERTY_NAME} properties. * * @author Chris Beams * @since 3.1 @@ -52,10 +57,22 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { */ public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default"; + /** + * Name of reserved default profile name: {@value}. If no default profile names are + * explicitly and no active profile names are explictly set, this profile will + * automatically be activated by default. + * @see #getReservedDefaultProfiles + * @see ConfigurableEnvironment#setDefaultProfiles + * @see ConfigurableEnvironment#setActiveProfiles + * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME + * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME + */ + protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default"; + protected final Log logger = LogFactory.getLog(getClass()); private Set activeProfiles = new LinkedHashSet(); - private Set defaultProfiles = new LinkedHashSet(); + private Set defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles()); private MutablePropertySources propertySources = new MutablePropertySources(); private ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(propertySources); @@ -69,6 +86,14 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return StringUtils.toStringArray(doGetActiveProfiles()); } + /** + * Return the set of active profiles as explicitly set through + * {@link #setActiveProfiles} or if the current set of active profiles + * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME} + * property and assign its value to the set of active profiles. + * @see #getActiveProfiles() + * @see #ACTIVE_PROFILES_PROPERTY_NAME + */ protected Set doGetActiveProfiles() { if (this.activeProfiles.isEmpty()) { String profiles = this.propertyResolver.getProperty(ACTIVE_PROFILES_PROPERTY_NAME); @@ -88,16 +113,35 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { return StringUtils.toStringArray(doGetDefaultProfiles()); } + /** + * Return the set of default profiles explicitly set via + * {@link #setDefaultProfiles(String...)} or if the current set of default profiles + * consists only of {@linkplain #getReservedDefaultProfiles() reserved default + * profiles}, then check for the presence of the + * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any) + * to the set of default profiles. + * @see #AbstractEnvironment() + * @see #getDefaultProfiles() + * @see #DEFAULT_PROFILES_PROPERTY_NAME + * @see #getReservedDefaultProfiles() + */ protected Set doGetDefaultProfiles() { - if (this.defaultProfiles.isEmpty()) { - String profiles = this.propertyResolver.getProperty(DEFAULT_PROFILES_PROPERTY_NAME); - if (StringUtils.hasText(profiles)) { - this.defaultProfiles = commaDelimitedListToSet(trimAllWhitespace(profiles)); + if (this.defaultProfiles.equals(this.getReservedDefaultProfiles())) { + String defaultProfiles = this.propertyResolver.getProperty(DEFAULT_PROFILES_PROPERTY_NAME); + if (defaultProfiles != null) { + this.defaultProfiles = commaDelimitedListToSet(trimAllWhitespace(defaultProfiles)); } } return this.defaultProfiles; } + /** + * {@inheritDoc} + *

Calling this method removes overrides any reserved default profiles + * that may have been added during construction of the environment. + * @see #AbstractEnvironment() + * @see #getReservedDefaultProfiles() + */ public void setDefaultProfiles(String... profiles) { this.defaultProfiles.clear(); this.defaultProfiles.addAll(Arrays.asList(profiles)); diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java b/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java index 32cacfb2ed3..947268fa709 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java @@ -66,14 +66,15 @@ package org.springframework.core.env; public interface Environment extends PropertyResolver { /** - * Return the set of profiles explicitly made active for this environment. Profiles are used for - * creating logical groupings of bean definitions to be registered conditionally, often based on - * deployment environment. Profiles can be activated by setting {@linkplain - * AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME "spring.profiles.active"} as a system property - * or by calling {@link ConfigurableEnvironment#setActiveProfiles(String...)}. + * Return the set of profiles explicitly made active for this environment. Profiles + * are used for creating logical groupings of bean definitions to be registered + * conditionally, for example based on deployment environment. Profiles can be + * activated by setting {@linkplain AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME + * "spring.profiles.active"} as a system property or by calling + * {@link ConfigurableEnvironment#setActiveProfiles(String...)}. * - *

If no profiles have explicitly been specified as active, then any 'default' profiles will implicitly - * be considered active. + *

If no profiles have explicitly been specified as active, then any {@linkplain + * #getDefaultProfiles() default profiles} will automatically be activated. * * @see #getDefaultProfiles * @see ConfigurableEnvironment#setActiveProfiles @@ -82,18 +83,22 @@ public interface Environment extends PropertyResolver { String[] getActiveProfiles(); /** - * Return the set of profiles to be active by default when no active profiles have been set explicitly. + * Return the set of profiles to be active by default when no active profiles have + * been set explicitly. * * @see #getActiveProfiles * @see ConfigurableEnvironment#setDefaultProfiles + * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME */ String[] getDefaultProfiles(); /** - * @return whether one or more of the given profiles is active, or in the case of no explicit active - * profiles, whether one or more of the given profiles is included in the set of default profiles + * @return whether one or more of the given profiles is active, or in the case of no + * explicit active profiles, whether one or more of the given profiles is included in + * the set of default profiles * @throws IllegalArgumentException unless at least one profile has been specified - * @throws IllegalArgumentException if any profile is the empty string or consists only of whitespace + * @throws IllegalArgumentException if any profile is the empty string or consists + * only of whitespace * @see #getActiveProfiles * @see #getDefaultProfiles */ diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java index f8f8160e73f..0f8a6c36199 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java @@ -1,75 +1,108 @@ +/* + * 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.core.env; -import org.junit.Ignore; -import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.junit.Test; /** - * Unit tests covering the extensibility of AbstractEnvironment + * Unit tests covering the extensibility of {@link AbstractEnvironment}. * * @author Chris Beams * @since 3.1 */ public class CustomEnvironmentTests { - @Ignore + // -- tests relating to customizing reserved default profiles ---------------------- + @Test - public void noop() { + public void control() { + Environment env = new AbstractEnvironment() { }; + assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(true)); } - /* - @Retention(RetentionPolicy.RUNTIME) - public - static @interface MyEnvironment { } - - /** - * A custom {@link Environment} that evaluates class literals - * for the presence of a custom annotation. - * / - static class CustomEnvironment extends AbstractEnvironment { - @Override - public boolean accepts(Object object) { - if (object instanceof Class) { - return ((Class)object).isAnnotationPresent(MyEnvironment.class); + + @Test + public void withNoReservedDefaultProfile() { + class CustomEnvironment extends AbstractEnvironment { + @Override + protected Set getReservedDefaultProfiles() { + return Collections.emptySet(); } - return super.accepts(object); } - } - - @MyEnvironment - static class CandidateWithCustomAnnotation { } - - static class CandidateWithoutCustomAnnotation { } - @Test - public void subclassOfAbstractEnvironment() { - ConfigurableEnvironment env = new CustomEnvironment(); - env.setActiveProfiles("test"); - assertThat(env.accepts(CandidateWithCustomAnnotation.class), is(true)); - assertThat(env.accepts(CandidateWithoutCustomAnnotation.class), is(false)); - assertThat(env.accepts("test"), is(true)); // AbstractEnvironment always returns true - assertThat(env.accepts(new Object()), is(true)); // AbstractEnvironment always returns true + Environment env = new CustomEnvironment(); + assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); } - static class CustomDefaultEnvironment extends DefaultEnvironment { - @Override - public boolean accepts(Object object) { - if (object instanceof Class) { - return ((Class)object).isAnnotationPresent(MyEnvironment.class); + @Test + public void withSingleCustomReservedDefaultProfile() { + class CustomEnvironment extends AbstractEnvironment { + @Override + protected Set getReservedDefaultProfiles() { + return Collections.singleton("rd1"); } - return super.accepts(object); } + + Environment env = new CustomEnvironment(); + assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); + assertThat(env.acceptsProfiles("rd1"), is(true)); } @Test - public void subclassOfDefaultEnvironment() { - ConfigurableEnvironment env = new CustomDefaultEnvironment(); - env.setActiveProfiles("test"); - assertThat(env.accepts(CandidateWithCustomAnnotation.class), is(true)); - assertThat(env.accepts(CandidateWithoutCustomAnnotation.class), is(false)); - assertThat(env.accepts("test"), is(true)); // delegates to DefaultEnvironment - assertThat(env.accepts("bogus"), is(false)); // delegates to DefaultEnvironment - assertThat(env.accepts(new Object()), is(false)); // delegates to DefaultEnvironment + public void withMultiCustomReservedDefaultProfile() { + class CustomEnvironment extends AbstractEnvironment { + @Override + @SuppressWarnings("serial") + protected Set getReservedDefaultProfiles() { + return new HashSet() {{ add("rd1"); add("rd2"); }}; + } + } + + ConfigurableEnvironment env = new CustomEnvironment(); + assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); + assertThat(env.acceptsProfiles("rd1", "rd2"), is(true)); + + // finally, issue additional assertions to cover all combinations of calling these + // methods, however unlikely. + env.setDefaultProfiles("d1"); + assertThat(env.acceptsProfiles("rd1", "rd2"), is(false)); + assertThat(env.acceptsProfiles("d1"), is(true)); + + env.setActiveProfiles("a1", "a2"); + assertThat(env.acceptsProfiles("d1"), is(false)); + assertThat(env.acceptsProfiles("a1", "a2"), is(true)); + + env.setActiveProfiles(); + assertThat(env.acceptsProfiles("d1"), is(true)); + assertThat(env.acceptsProfiles("a1", "a2"), is(false)); + + env.setDefaultProfiles(); + assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); + assertThat(env.acceptsProfiles("rd1", "rd2"), is(false)); + assertThat(env.acceptsProfiles("d1"), is(false)); + assertThat(env.acceptsProfiles("a1", "a2"), is(false)); } - */ + + // -- tests relating to customizing property sources ------------------------------- } diff --git a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java b/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java index 1902fe858a7..34172606fac 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/env/EnvironmentTests.java @@ -30,6 +30,7 @@ import static org.junit.matchers.JUnitMatchers.hasItem; import static org.junit.matchers.JUnitMatchers.hasItems; import static org.springframework.core.env.AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME; import static org.springframework.core.env.AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME; +import static org.springframework.core.env.AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME; import java.lang.reflect.Field; import java.security.AccessControlException; @@ -79,6 +80,16 @@ public class EnvironmentTests { assertThat(activeProfiles.length, is(2)); } + @Test + public void reservedDefaultProfile() { + assertThat(environment.getDefaultProfiles(), equalTo(new String[]{RESERVED_DEFAULT_PROFILE_NAME})); + System.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "d0"); + assertThat(environment.getDefaultProfiles(), equalTo(new String[]{"d0"})); + environment.setDefaultProfiles("d1", "d2"); + assertThat(environment.getDefaultProfiles(), equalTo(new String[]{"d1","d2"})); + System.getProperties().remove(DEFAULT_PROFILES_PROPERTY_NAME); + } + @Test public void getActiveProfiles_systemPropertiesEmpty() { assertThat(environment.getActiveProfiles().length, is(0)); @@ -113,7 +124,7 @@ public class EnvironmentTests { @Test public void getDefaultProfiles() { - assertThat(environment.getDefaultProfiles().length, is(0)); + assertThat(environment.getDefaultProfiles(), equalTo(new String[] {RESERVED_DEFAULT_PROFILE_NAME})); environment.getPropertySources().addFirst(new MockPropertySource().withProperty(DEFAULT_PROFILES_PROPERTY_NAME, "pd1")); assertThat(environment.getDefaultProfiles().length, is(1)); assertThat(Arrays.asList(environment.getDefaultProfiles()), hasItem("pd1"));