/* * Copyright 2012-2017 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.boot.yaml; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher; import org.springframework.beans.factory.config.YamlProcessor.MatchStatus; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; import org.springframework.core.env.Environment; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** * {@link DocumentMatcher} backed by {@link Environment#getActiveProfiles()}. A YAML * document may define a "spring.profiles" element as a comma-separated list of Spring * profile names, optionally negated using the {@code !} character. If both negated and * non-negated profiles are specified for a single document, at least one non-negated * profile must match and no negated profiles may match. * * @author Dave Syer * @author Matt Benson * @author Phillip Webb * @author Andy Wilkinson * @author Madhura Bhave */ public class SpringProfileDocumentMatcher implements DocumentMatcher { private String[] activeProfiles = new String[0]; public SpringProfileDocumentMatcher(String... profiles) { addActiveProfiles(profiles); } public void addActiveProfiles(String... profiles) { LinkedHashSet<String> set = new LinkedHashSet<>( Arrays.asList(this.activeProfiles)); Collections.addAll(set, profiles); this.activeProfiles = set.toArray(new String[set.size()]); } @Override public MatchStatus matches(Properties properties) { return matches(extractSpringProfiles(properties)); } protected List<String> extractSpringProfiles(Properties properties) { Binder binder = new Binder(new MapConfigurationPropertySource(properties)); return binder.bind("spring.profiles", Bindable.of(String[].class)) .map(Arrays::asList).orElse(Collections.emptyList()); } private MatchStatus matches(List<String> profiles) { ProfilesMatcher profilesMatcher = getProfilesMatcher(); Set<String> negative = extractProfiles(profiles, ProfileType.NEGATIVE); Set<String> positive = extractProfiles(profiles, ProfileType.POSITIVE); if (!CollectionUtils.isEmpty(negative)) { if (profilesMatcher.matches(negative) == MatchStatus.FOUND) { return MatchStatus.NOT_FOUND; } if (CollectionUtils.isEmpty(positive)) { return MatchStatus.FOUND; } } return profilesMatcher.matches(positive); } private ProfilesMatcher getProfilesMatcher() { return this.activeProfiles.length == 0 ? new EmptyProfilesMatcher() : new ActiveProfilesMatcher( new HashSet<>(Arrays.asList(this.activeProfiles))); } private Set<String> extractProfiles(List<String> profiles, ProfileType type) { if (CollectionUtils.isEmpty(profiles)) { return null; } Set<String> extractedProfiles = new HashSet<>(); for (String candidate : profiles) { ProfileType candidateType = ProfileType.POSITIVE; if (candidate.startsWith("!")) { candidateType = ProfileType.NEGATIVE; } if (candidateType == type) { extractedProfiles.add(type == ProfileType.POSITIVE ? candidate : candidate.substring(1)); } } return extractedProfiles; } /** * Profile match types. */ enum ProfileType { POSITIVE, NEGATIVE } /** * Base class for profile matchers. */ private static abstract class ProfilesMatcher { public final MatchStatus matches(Set<String> profiles) { if (CollectionUtils.isEmpty(profiles)) { return MatchStatus.ABSTAIN; } return doMatches(profiles); } protected abstract MatchStatus doMatches(Set<String> profiles); } /** * {@link ProfilesMatcher} that matches when a value in {@code spring.profiles} is * also in {@code spring.profiles.active}. */ private static class ActiveProfilesMatcher extends ProfilesMatcher { private final Set<String> activeProfiles; ActiveProfilesMatcher(Set<String> activeProfiles) { this.activeProfiles = activeProfiles; } @Override protected MatchStatus doMatches(Set<String> profiles) { if (profiles.isEmpty()) { return MatchStatus.NOT_FOUND; } for (String activeProfile : this.activeProfiles) { if (profiles.contains(activeProfile)) { return MatchStatus.FOUND; } } return MatchStatus.NOT_FOUND; } } /** * {@link ProfilesMatcher} that matches when {@code * spring.profiles} is empty or contains a value with no text. * * @see StringUtils#hasText(String) */ private static class EmptyProfilesMatcher extends ProfilesMatcher { @Override public MatchStatus doMatches(Set<String> springProfiles) { if (springProfiles.isEmpty()) { return MatchStatus.FOUND; } for (String profile : springProfiles) { if (!StringUtils.hasText(profile)) { return MatchStatus.FOUND; } } return MatchStatus.NOT_FOUND; } } /** * Class for binding {@code spring.profiles} property. */ static class SpringProperties { private List<String> profiles = new ArrayList<>(); public List<String> getProfiles() { return this.profiles; } public void setProfiles(List<String> profiles) { this.profiles = profiles; } } }