Skip to content

Commit

Permalink
Polish "Add profile expression support"
Browse files Browse the repository at this point in the history
Issue: SPR-12458
  • Loading branch information
snicoll committed Jun 15, 2018
1 parent e2623b7 commit 1f3b4f1
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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 @@ -24,6 +24,7 @@

import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Profiles;

/**
* Indicates that a component is eligible for registration when one or more
Expand All @@ -47,9 +48,14 @@
*
* <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 of the specified profiles are active. This is
* analogous 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
* will be bypassed unless one or more of the specified profiles are active. A profile
* string may contains a simple profile name (for example {@code "p1"}) or a profile
* expression. A profile expression allows for more complicated profile logic to be
* expressed, for example {@code "p1 & p2"}. See {@link Profiles#of(String...)} for more
* details about supported formats.
*
* <p>This is analogous 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 at least profile 'p1' or 'p2' has been
* activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked
* with {@code @Profile({"p1", "p2"})}, that class will not be registered or processed unless
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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 @@ -16,6 +16,7 @@

package org.springframework.context.annotation;

import org.springframework.core.env.Profiles;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;

Expand All @@ -35,7 +36,7 @@ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles((String[]) value)) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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 Down Expand Up @@ -54,6 +54,7 @@
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
Expand Down Expand Up @@ -193,7 +194,7 @@ public void withCustomTypeFilter() {
@Test
public void withAwareTypeFilter() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithAwareTypeFilter.class);
assertTrue(ctx.getEnvironment().acceptsProfiles("the-filter-ran"));
assertTrue(ctx.getEnvironment().acceptsProfiles(Profiles.of("the-filter-ran")));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,23 @@ public void setDefaultProfiles(String... profiles) {
}
}

@Override
@Deprecated
public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
for (String profile : profiles) {
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
if (!isProfileActive(profile.substring(1))) {
return true;
}
}
else if (isProfileActive(profile)) {
return true;
}
}
return false;
}

@Override
public boolean acceptsProfiles(Profiles profiles) {
Assert.notNull(profiles, "Profiles must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,22 @@ 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. Profiles can simple indicators ('{@code p1}',
* {@code !p1}) or more complex boolean expressions. See {@link Profiles#of(String...)}
* for syntax details.
* 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, <pre class="code">env.acceptsProfiles("p1", "!p2")</pre> will
* return {@code true} if profile 'p1' is active or 'p2' is not active.
* @throws IllegalArgumentException if called with zero arguments
* or if any profile is {@code null}, empty or whitespace-only
* @see #getActiveProfiles
* @see #getDefaultProfiles
* @see #acceptsProfiles(Profiles)
* @deprecated as of 5.1 in favor of {@link #acceptsProfiles(Profiles)}
*/
default boolean acceptsProfiles(String... profiles) {
return acceptsProfiles(Profiles.of(profiles));
}
@Deprecated
boolean acceptsProfiles(String... profiles);

/**
* Returns whether the active profiles match the given {@link Profiles} set.
* Return whether the active profiles match the given {@link Profiles} predicate.
*/
boolean acceptsProfiles(Profiles profiles);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,32 @@

package org.springframework.core.env;

import java.util.function.Predicate;

/**
* A set of profiles that may be {@link Environment#acceptsProfiles(Profiles) accepted} by
* Profile predicate that may be {@link Environment#acceptsProfiles(Profiles) accepted} by
* an {@link Environment}.
* <p>
* May be implemented directly or, more usually, created using the {@link #of(String...)
* of(...)} factory method.
*
* @author Phillip Webb
* @since 5.0
* @see #of(String...)
* @since 5.1
*/
@FunctionalInterface
public interface Profiles {

/**
* Test if this profile set matches against given active profiles.
* Test if this profile predicate matches against given active profiles.
* @param activeProfiles test whether a given profile is currently active
*/
boolean matches(ActiveProfiles activeProfiles);
boolean matches(Predicate<String> activeProfiles);

/**
* Return a new {@link Profiles} instance that checks for matches against the given
* profile strings. The returned instance will
* {@link Profiles#matches(ActiveProfiles) matches} if any one of the given profile
* strings match.
* {@link Profiles#matches(Predicate)} match} if any one of the given profile strings
* match.
* <p>
* A profile string may contains a simple profile name (for example
* {@code "production"}) or a profile expression. A profile expression allows for more
Expand All @@ -59,18 +61,8 @@ public interface Profiles {
* @param profiles the profiles to include
* @return a new {@link Profiles} instance
*/
public static Profiles of(String... profiles) {
static Profiles of(String... profiles) {
return ProfilesParser.parse(profiles);
}

/**
* The current set of active profiles.
*/
interface ActiveProfiles {

/**
* Tests if given profile is currently active.
*/
boolean contains(String profile);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,17 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Predicate;

import org.springframework.core.env.Profiles.ActiveProfiles;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
* Internal parser used by {@link Profiles#of}.
*
* @author Phillip Webb
* @since 5.0
*/
class ProfilesParser {

Expand All @@ -55,7 +54,7 @@ private static Profiles parseTokens(String expression, StringTokenizer tokens) {
Operator operator = null;
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken().trim();
if(token.isEmpty()) {
if (token.isEmpty()) {
continue;
}
switch (token) {
Expand Down Expand Up @@ -86,7 +85,8 @@ private static Profiles parseTokens(String expression, StringTokenizer tokens) {
return merge(expression, elements, operator);
}

private static Profiles merge(String expression, List<Profiles> elements, Operator operator) {
private static Profiles merge(String expression, List<Profiles> elements,
Operator operator) {
assertWellFormed(expression, !elements.isEmpty());
if (elements.size() == 1) {
return elements.get(0);
Expand Down Expand Up @@ -115,16 +115,17 @@ private static Profiles not(Profiles profiles) {
}

private static Profiles equals(String profile) {
return (activeProfile) -> activeProfile.contains(profile);
return (activeProfile) -> activeProfile.test(profile);
}

private static Predicate<Profiles> isMatch(ActiveProfiles activeProfile) {
private static Predicate<Profiles> isMatch(Predicate<String> activeProfile) {
return (profiles) -> profiles.matches(activeProfile);
}

enum Operator {
AND, OR
};
AND,
OR
}

private static class ParsedProfiles implements Profiles {

Expand All @@ -138,7 +139,7 @@ public ParsedProfiles(String[] expressions, Profiles[] parsed) {
}

@Override
public boolean matches(ActiveProfiles activeProfiles) {
public boolean matches(Predicate<String> activeProfiles) {
for (Profiles candidate : this.parsed) {
if (candidate.matches(activeProfiles)) {
return true;
Expand All @@ -149,7 +150,9 @@ public boolean matches(ActiveProfiles activeProfiles) {

@Override
public String toString() {
return StringUtils.arrayToCommaDelimitedString(this.expressions);
return StringUtils.arrayToDelimitedString(this.expressions, " or ");
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2018 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 Down Expand Up @@ -38,7 +38,7 @@ public class CustomEnvironmentTests {
@Test
public void control() {
Environment env = new AbstractEnvironment() { };
assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(true));
assertThat(env.acceptsProfiles(defaultProfile()), is(true));
}

@Test
Expand All @@ -51,7 +51,7 @@ protected Set<String> getReservedDefaultProfiles() {
}

Environment env = new CustomEnvironment();
assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false));
assertThat(env.acceptsProfiles(defaultProfile()), is(false));
}

@Test
Expand All @@ -64,8 +64,8 @@ protected Set<String> getReservedDefaultProfiles() {
}

Environment env = new CustomEnvironment();
assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false));
assertThat(env.acceptsProfiles("rd1"), is(true));
assertThat(env.acceptsProfiles(defaultProfile()), is(false));
assertThat(env.acceptsProfiles(Profiles.of("rd1")), is(true));
}

@Test
Expand All @@ -79,28 +79,32 @@ protected Set<String> getReservedDefaultProfiles() {
}

ConfigurableEnvironment env = new CustomEnvironment();
assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false));
assertThat(env.acceptsProfiles("rd1", "rd2"), is(true));
assertThat(env.acceptsProfiles(defaultProfile()), is(false));
assertThat(env.acceptsProfiles(Profiles.of("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));
assertThat(env.acceptsProfiles(Profiles.of("rd1 | rd2")), is(false));
assertThat(env.acceptsProfiles(Profiles.of("d1")), is(true));

env.setActiveProfiles("a1", "a2");
assertThat(env.acceptsProfiles("d1"), is(false));
assertThat(env.acceptsProfiles("a1", "a2"), is(true));
assertThat(env.acceptsProfiles(Profiles.of("d1")), is(false));
assertThat(env.acceptsProfiles(Profiles.of("a1 | a2")), is(true));

env.setActiveProfiles();
assertThat(env.acceptsProfiles("d1"), is(true));
assertThat(env.acceptsProfiles("a1", "a2"), is(false));
assertThat(env.acceptsProfiles(Profiles.of("d1")), is(true));
assertThat(env.acceptsProfiles(Profiles.of("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));
assertThat(env.acceptsProfiles(defaultProfile()), is(false));
assertThat(env.acceptsProfiles(Profiles.of("rd1 | rd2")), is(false));
assertThat(env.acceptsProfiles(Profiles.of("d1")), is(false));
assertThat(env.acceptsProfiles(Profiles.of("a1 | a2")), is(false));
}

private Profiles defaultProfile() {
return Profiles.of(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,14 @@ public String[] getDefaultProfiles() {
return null;
}

@Override
public boolean acceptsProfiles(String... profiles) {
return false;
}

@Override
public boolean acceptsProfiles(Profiles profiles) {
return false;
}

}
Loading

0 comments on commit 1f3b4f1

Please sign in to comment.