Skip to content

Commit

Permalink
Support not (!) operator for profile selection
Browse files Browse the repository at this point in the history
The following syntax is now supported

  <beans profile="p1,!p2">

  @Profile("p1", "!p2")

indicating that the <beans> element or annotated component should
be processed only if profile 'p1' is active or profile 'p2' is not
active.

Issue: SPR-8728
  • Loading branch information
cbeams committed May 27, 2012
1 parent e72c49f commit bcd44f3
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,28 @@
<xsd:attribute name="profile" use="optional" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The set of profiles for which this <beans> 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 <beans> element
will be parsed, and all of its <bean> elements registered, &lt;import&gt; 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 <beans> 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 <beans>
element will be parsed, and all of its <bean> elements registered, &lt;import&gt;
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.
<beans profile="p1,!p2">
indicates that the <beans> 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
]]></xsd:documentation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,28 @@
<xsd:attribute name="profile" use="optional" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The set of profiles for which this <beans> 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 <beans> element
will be parsed, and all of its <bean> elements registered, &lt;import&gt; 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 <beans> 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 <beans>
element will be parsed, and all of its <bean> elements registered, &lt;import&gt;
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.
<beans profile="p1,!p2">
indicates that the <beans> 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
]]></xsd:documentation>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -25,36 +25,44 @@
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.
*
* <p>A <em>profile</em> 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}.
* <p>A <em>profile</em> 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}.
*
* <p>The {@code @Profile} annotation may be used in any of the following ways:
* <ul>
* <li>as a type-level annotation on any class directly or indirectly annotated with
* {@code @Component}, including {@link Configuration @Configuration} classes
* <li>as a meta-annotation, for the purpose of composing custom stereotype annotations
* <li>as a type-level annotation on any class directly or indirectly annotated with
* {@code @Component}, including {@link Configuration @Configuration} classes
* <li>as a meta-annotation, for the purpose of composing custom stereotype annotations
* </ul>
*
* <p>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 <beans profile="p1,p2">}, 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"})</code>, 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 <beans profile="p1,p2">}, 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.
*
* <p>If the {@code @Profile} annotation is omitted, registration will occur, regardless of which,
* if any, profiles are active.
* <p>If a given profile is prefixed with the NOT operator ({@code !}), the annotated
* will be registered if the profile is <em>not</em> active. e.g., for
* {@code @Profile({"p1", "!p2"})}, registration will occur if profile 'p1' is active or
* if profile 'p2' is not active.
*
* <p>When defining Spring beans via XML, the {@code "profile"} attribute of the {@code <beans>}
* element may be used. See the documentation in {@code spring-beans-3.1.xsd} for details.
* <p>If the {@code @Profile} annotation is omitted, registration will occur, regardless
* of which (if any) profiles are active.
*
* <p>When defining Spring beans via XML, the {@code "profile"} attribute of the
* {@code <beans>} 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.util.StringUtils;

import static java.lang.String.*;

import static org.springframework.util.StringUtils.*;

/**
Expand Down Expand Up @@ -300,31 +301,43 @@ public void setDefaultProfiles(String... profiles) {

public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
boolean activeProfileFound = false;
Set<String> activeProfiles = this.doGetActiveProfiles();
Set<String> 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.
* <p>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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <em>not</em> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -147,6 +149,11 @@ public void setActiveProfiles_withEmptyProfile() {
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);
Expand All @@ -162,6 +169,11 @@ public void setDefaultProfiles_withEmptyProfile() {
environment.setDefaultProfiles("");
}

@Test(expected=IllegalArgumentException.class)
public void setDefaultProfiles_withNotOperator() {
environment.setDefaultProfiles("d1", "!d2");
}

@Test
public void addActiveProfile() {
assertThat(environment.getActiveProfiles().length, is(0));
Expand Down Expand Up @@ -284,6 +296,20 @@ public void acceptsProfiles_defaultProfile() {
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() {
Expand Down

0 comments on commit bcd44f3

Please sign in to comment.