diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd index e5400988e7c..d26bebf5323 100644 --- a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd +++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.1.xsd @@ -82,19 +82,28 @@ element may be parsed. Multiple profiles can be - separated by any number of spaces, commas, or semi-colons (or indeed any mixture of the three). - If one or more of the specified profiles are active at time of parsing, the element - will be parsed, and all of its elements registered, <import> elements followed, - etc. If none of the specified profiles are active at time of parsing, then the entire element - and its contents will be ignored. + The set of profiles for which this element should be parsed. Multiple profiles + can be separated by spaces, commas, or semi-colons. + + If one or more of the specified profiles are active at time of parsing, the + element will be parsed, and all of its elements registered, <import> + elements followed, etc. If none of the specified profiles are active at time of + parsing, then the entire element and its contents will be ignored. + + If a profile is prefixed with the NOT operator '!', e.g. + + + + indicates that the element should be parsed if profile "p1" is active or + if profile "p2" is not active. Profiles are activated in one of two ways: Programmatic: ConfigurableEnvironment#setActiveProfiles(String...) ConfigurableEnvironment#setDefaultProfiles(String...) - Properties (typically through -D system properties, environment variables, or servlet context init params): + Properties (typically through -D system properties, environment variables, or + servlet context init params): spring.profiles.active=p1,p2 spring.profiles.default=p1,p2 ]]> diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd index 48194aeaa68..ba053e86576 100644 --- a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd +++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-3.2.xsd @@ -82,19 +82,28 @@ element may be parsed. Multiple profiles can be - separated by any number of spaces, commas, or semi-colons (or indeed any mixture of the three). - If one or more of the specified profiles are active at time of parsing, the element - will be parsed, and all of its elements registered, <import> elements followed, - etc. If none of the specified profiles are active at time of parsing, then the entire element - and its contents will be ignored. + The set of profiles for which this element should be parsed. Multiple profiles + can be separated by spaces, commas, or semi-colons. + + If one or more of the specified profiles are active at time of parsing, the + element will be parsed, and all of its elements registered, <import> + elements followed, etc. If none of the specified profiles are active at time of + parsing, then the entire element and its contents will be ignored. + + If a profile is prefixed with the NOT operator '!', e.g. + + + + indicates that the element should be parsed if profile "p1" is active or + if profile "p2" is not active. Profiles are activated in one of two ways: Programmatic: ConfigurableEnvironment#setActiveProfiles(String...) ConfigurableEnvironment#setDefaultProfiles(String...) - Properties (typically through -D system properties, environment variables, or servlet context init params): + Properties (typically through -D system properties, environment variables, or + servlet context init params): spring.profiles.active=p1,p2 spring.profiles.default=p1,p2 ]]> diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java index d7670601323..ab793599c01 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -25,36 +25,44 @@ import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; /** - * Indicates that a component is eligible for registration when one or more {@linkplain #value - * specified profiles} are active. + * Indicates that a component is eligible for registration when one or more {@linkplain + * #value specified profiles} are active. * - *

A profile is a named logical grouping that may be activated programatically via - * {@link ConfigurableEnvironment#setActiveProfiles} or declaratively through setting the - * {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME spring.profiles.active} property, - * usually through JVM system properties, as an environment variable, or for web applications - * as a Servlet context parameter in {@code web.xml}. + *

A profile is a named logical grouping that may be activated + * programmatically via {@link ConfigurableEnvironment#setActiveProfiles} or declaratively + * through setting the {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME + * spring.profiles.active} property, usually through JVM system properties, as an + * environment variable, or for web applications as a Servlet context parameter in + * {@code web.xml}. * *

The {@code @Profile} annotation may be used in any of the following ways: *

    - *
  • as a type-level annotation on any class directly or indirectly annotated with - * {@code @Component}, including {@link Configuration @Configuration} classes - *
  • as a meta-annotation, for the purpose of composing custom stereotype annotations + *
  • as a type-level annotation on any class directly or indirectly annotated with + * {@code @Component}, including {@link Configuration @Configuration} classes + *
  • as a meta-annotation, for the purpose of composing custom stereotype annotations *
* *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the - * {@code @Bean} methods and {@link Import @Import} annotations associated with that class will - * be bypassed unless one or more the specified profiles are active. This is very similar to - * the behavior in Spring XML: if the {@code profile} attribute of the {@code beans} element is - * supplied e.g., {@code }, the {@code beans} element will not be parsed unless - * profiles 'p1' and/or 'p2' have been activated. Likewise, if a {@code @Component} or - * {@code @Configuration} class is marked with @Profile({"p1", "p2"}), that class will - * not be registered/processed unless profiles 'p1' and/or 'p2' have been activated. + * {@code @Bean} methods and {@link Import @Import} annotations associated with that class + * will be bypassed unless one or more the specified profiles are active. This is very + * similar to the behavior in Spring XML: if the {@code profile} attribute of the + * {@code beans} element is supplied e.g., {@code }, the + * {@code beans} element will not be parsed unless profiles 'p1' and/or 'p2' have been + * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked + * with {@code @Profile({"p1", "p2"})}, that class will not be registered/processed unless + * profiles 'p1' and/or 'p2' have been activated. * - *

If the {@code @Profile} annotation is omitted, registration will occur, regardless of which, - * if any, profiles are active. + *

If a given profile is prefixed with the NOT operator ({@code !}), the annotated + * will be registered if the profile is not active. e.g., for + * {@code @Profile({"p1", "!p2"})}, registration will occur if profile 'p1' is active or + * if profile 'p2' is not active. * - *

When defining Spring beans via XML, the {@code "profile"} attribute of the {@code } - * element may be used. See the documentation in {@code spring-beans-3.1.xsd} for details. + *

If the {@code @Profile} annotation is omitted, registration will occur, regardless + * of which (if any) profiles are active. + * + *

When defining Spring beans via XML, the {@code "profile"} attribute of the + * {@code } element may be used. See the documentation in + * {@code spring-beans} XSD (version 3.1 or greater) for details. * * @author Chris Beams * @since 3.1 diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index 65574834a9a..58f4d0bfe27 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -31,6 +31,7 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import static java.lang.String.*; + import static org.springframework.util.StringUtils.*; /** @@ -300,31 +301,43 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { public boolean acceptsProfiles(String... profiles) { Assert.notEmpty(profiles, "Must specify at least one profile"); - boolean activeProfileFound = false; - Set activeProfiles = this.doGetActiveProfiles(); - Set defaultProfiles = this.doGetDefaultProfiles(); for (String profile : profiles) { - this.validateProfile(profile); - if (activeProfiles.contains(profile) - || (activeProfiles.isEmpty() && defaultProfiles.contains(profile))) { - activeProfileFound = true; - break; + if (profile != null && profile.length() > 0 && profile.charAt(0) == '!') { + return !this.isProfileActive(profile.substring(1)); + } + if (this.isProfileActive(profile)) { + return true; } } - return activeProfileFound; + return false; + } + + /** + * Return whether the given profile is active, or if active profiles are empty + * whether the profile should be active by default. + * @throws IllegalArgumentException per {@link #validateProfile(String)} + * @since 3.2 + */ + protected boolean isProfileActive(String profile) { + this.validateProfile(profile); + return this.doGetActiveProfiles().contains(profile) + || (this.doGetActiveProfiles().isEmpty() && this.doGetDefaultProfiles().contains(profile)); } /** * Validate the given profile, called internally prior to adding to the set of * active or default profiles. *

Subclasses may override to impose further restrictions on profile syntax. - * @throws IllegalArgumentException if the profile is null, empty or whitespace-only + * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or + * begins with the profile NOT operator (!). * @see #acceptsProfiles * @see #addActiveProfile * @see #setDefaultProfiles */ protected void validateProfile(String profile) { Assert.hasText(profile, "Invalid profile [" + profile + "]: must contain text"); + Assert.isTrue(profile.charAt(0) != '!', + "Invalid profile [" + profile + "]: must not begin with the ! operator"); } public MutablePropertySources getPropertySources() { diff --git a/spring-core/src/main/java/org/springframework/core/env/Environment.java b/spring-core/src/main/java/org/springframework/core/env/Environment.java index 792c1034d53..a0347acd5a7 100644 --- a/spring-core/src/main/java/org/springframework/core/env/Environment.java +++ b/spring-core/src/main/java/org/springframework/core/env/Environment.java @@ -100,7 +100,10 @@ public interface Environment extends PropertyResolver { /** * 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 + * the set of default profiles. If a profile begins with '!' the logic is inverted, + * i.e. the method will return true if the given profile is not active. For + * example, {@code env.acceptsProfiles("p1", "!p2")} will return true if profile 'p1' + * is active or 'p2' is not active. * @throws IllegalArgumentException if called with zero arguments * @throws IllegalArgumentException if any profile is null, empty or whitespace-only * @see #getActiveProfiles diff --git a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index 8fca6ec6d83..d0515bcc32a 100644 --- a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -17,8 +17,10 @@ package org.springframework.core.env; import java.lang.reflect.Field; + import java.security.AccessControlException; import java.security.Permission; + import java.util.Arrays; import java.util.Collections; import java.util.Map; @@ -147,6 +149,11 @@ public class StandardEnvironmentTests { environment.setActiveProfiles(""); } + @Test(expected=IllegalArgumentException.class) + public void setActiveProfiles_withNotOperator() { + environment.setActiveProfiles("p1", "!p2"); + } + @Test(expected=IllegalArgumentException.class) public void setDefaultProfiles_withNullProfileArray() { environment.setDefaultProfiles((String[])null); @@ -162,6 +169,11 @@ public class StandardEnvironmentTests { environment.setDefaultProfiles(""); } + @Test(expected=IllegalArgumentException.class) + public void setDefaultProfiles_withNotOperator() { + environment.setDefaultProfiles("d1", "!d2"); + } + @Test public void addActiveProfile() { assertThat(environment.getActiveProfiles().length, is(0)); @@ -284,6 +296,20 @@ public class StandardEnvironmentTests { assertThat(environment.acceptsProfiles("p1"), is(true)); } + @Test + public void acceptsProfiles_withNotOperator() { + assertThat(environment.acceptsProfiles("p1"), is(false)); + assertThat(environment.acceptsProfiles("!p1"), is(true)); + environment.addActiveProfile("p1"); + assertThat(environment.acceptsProfiles("p1"), is(true)); + assertThat(environment.acceptsProfiles("!p1"), is(false)); + } + + @Test(expected=IllegalArgumentException.class) + public void acceptsProfiles_withInvalidNotOperator() { + environment.acceptsProfiles("p1", "!"); + } + @Test public void environmentSubclass_withCustomProfileValidation() { ConfigurableEnvironment env = new AbstractEnvironment() {