001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.condition;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024
025import org.springframework.beans.BeanUtils;
026import org.springframework.context.annotation.Condition;
027import org.springframework.context.annotation.ConditionContext;
028import org.springframework.context.annotation.Conditional;
029import org.springframework.context.annotation.ConfigurationCondition;
030import org.springframework.core.type.AnnotatedTypeMetadata;
031import org.springframework.core.type.AnnotationMetadata;
032import org.springframework.core.type.classreading.MetadataReaderFactory;
033import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
034import org.springframework.util.Assert;
035import org.springframework.util.ClassUtils;
036import org.springframework.util.LinkedMultiValueMap;
037import org.springframework.util.MultiValueMap;
038
039/**
040 * Abstract base class for nested conditions.
041 *
042 * @author Phillip Webb
043 * @since 2.0.1
044 */
045public abstract class AbstractNestedCondition extends SpringBootCondition
046                implements ConfigurationCondition {
047
048        private final ConfigurationPhase configurationPhase;
049
050        AbstractNestedCondition(ConfigurationPhase configurationPhase) {
051                Assert.notNull(configurationPhase, "ConfigurationPhase must not be null");
052                this.configurationPhase = configurationPhase;
053        }
054
055        @Override
056        public ConfigurationPhase getConfigurationPhase() {
057                return this.configurationPhase;
058        }
059
060        @Override
061        public ConditionOutcome getMatchOutcome(ConditionContext context,
062                        AnnotatedTypeMetadata metadata) {
063                String className = getClass().getName();
064                MemberConditions memberConditions = new MemberConditions(context, className);
065                MemberMatchOutcomes memberOutcomes = new MemberMatchOutcomes(memberConditions);
066                return getFinalMatchOutcome(memberOutcomes);
067        }
068
069        protected abstract ConditionOutcome getFinalMatchOutcome(
070                        MemberMatchOutcomes memberOutcomes);
071
072        protected static class MemberMatchOutcomes {
073
074                private final List<ConditionOutcome> all;
075
076                private final List<ConditionOutcome> matches;
077
078                private final List<ConditionOutcome> nonMatches;
079
080                public MemberMatchOutcomes(MemberConditions memberConditions) {
081                        this.all = Collections.unmodifiableList(memberConditions.getMatchOutcomes());
082                        List<ConditionOutcome> matches = new ArrayList<>();
083                        List<ConditionOutcome> nonMatches = new ArrayList<>();
084                        for (ConditionOutcome outcome : this.all) {
085                                (outcome.isMatch() ? matches : nonMatches).add(outcome);
086                        }
087                        this.matches = Collections.unmodifiableList(matches);
088                        this.nonMatches = Collections.unmodifiableList(nonMatches);
089                }
090
091                public List<ConditionOutcome> getAll() {
092                        return this.all;
093                }
094
095                public List<ConditionOutcome> getMatches() {
096                        return this.matches;
097                }
098
099                public List<ConditionOutcome> getNonMatches() {
100                        return this.nonMatches;
101                }
102
103        }
104
105        private static class MemberConditions {
106
107                private final ConditionContext context;
108
109                private final MetadataReaderFactory readerFactory;
110
111                private final Map<AnnotationMetadata, List<Condition>> memberConditions;
112
113                MemberConditions(ConditionContext context, String className) {
114                        this.context = context;
115                        this.readerFactory = new SimpleMetadataReaderFactory(
116                                        context.getResourceLoader());
117                        String[] members = getMetadata(className).getMemberClassNames();
118                        this.memberConditions = getMemberConditions(members);
119                }
120
121                private Map<AnnotationMetadata, List<Condition>> getMemberConditions(
122                                String[] members) {
123                        MultiValueMap<AnnotationMetadata, Condition> memberConditions = new LinkedMultiValueMap<>();
124                        for (String member : members) {
125                                AnnotationMetadata metadata = getMetadata(member);
126                                for (String[] conditionClasses : getConditionClasses(metadata)) {
127                                        for (String conditionClass : conditionClasses) {
128                                                Condition condition = getCondition(conditionClass);
129                                                memberConditions.add(metadata, condition);
130                                        }
131                                }
132                        }
133                        return Collections.unmodifiableMap(memberConditions);
134                }
135
136                private AnnotationMetadata getMetadata(String className) {
137                        try {
138                                return this.readerFactory.getMetadataReader(className)
139                                                .getAnnotationMetadata();
140                        }
141                        catch (IOException ex) {
142                                throw new IllegalStateException(ex);
143                        }
144                }
145
146                @SuppressWarnings("unchecked")
147                private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
148                        MultiValueMap<String, Object> attributes = metadata
149                                        .getAllAnnotationAttributes(Conditional.class.getName(), true);
150                        Object values = (attributes != null) ? attributes.get("value") : null;
151                        return (List<String[]>) ((values != null) ? values : Collections.emptyList());
152                }
153
154                private Condition getCondition(String conditionClassName) {
155                        Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName,
156                                        this.context.getClassLoader());
157                        return (Condition) BeanUtils.instantiateClass(conditionClass);
158                }
159
160                public List<ConditionOutcome> getMatchOutcomes() {
161                        List<ConditionOutcome> outcomes = new ArrayList<>();
162                        this.memberConditions.forEach((metadata, conditions) -> outcomes
163                                        .add(new MemberOutcomes(this.context, metadata, conditions)
164                                                        .getUltimateOutcome()));
165                        return Collections.unmodifiableList(outcomes);
166                }
167
168        }
169
170        private static class MemberOutcomes {
171
172                private final ConditionContext context;
173
174                private final AnnotationMetadata metadata;
175
176                private final List<ConditionOutcome> outcomes;
177
178                MemberOutcomes(ConditionContext context, AnnotationMetadata metadata,
179                                List<Condition> conditions) {
180                        this.context = context;
181                        this.metadata = metadata;
182                        this.outcomes = new ArrayList<>(conditions.size());
183                        for (Condition condition : conditions) {
184                                this.outcomes.add(getConditionOutcome(metadata, condition));
185                        }
186                }
187
188                private ConditionOutcome getConditionOutcome(AnnotationMetadata metadata,
189                                Condition condition) {
190                        if (condition instanceof SpringBootCondition) {
191                                return ((SpringBootCondition) condition).getMatchOutcome(this.context,
192                                                metadata);
193                        }
194                        return new ConditionOutcome(condition.matches(this.context, metadata),
195                                        ConditionMessage.empty());
196                }
197
198                public ConditionOutcome getUltimateOutcome() {
199                        ConditionMessage.Builder message = ConditionMessage
200                                        .forCondition("NestedCondition on "
201                                                        + ClassUtils.getShortName(this.metadata.getClassName()));
202                        if (this.outcomes.size() == 1) {
203                                ConditionOutcome outcome = this.outcomes.get(0);
204                                return new ConditionOutcome(outcome.isMatch(),
205                                                message.because(outcome.getMessage()));
206                        }
207                        List<ConditionOutcome> match = new ArrayList<>();
208                        List<ConditionOutcome> nonMatch = new ArrayList<>();
209                        for (ConditionOutcome outcome : this.outcomes) {
210                                (outcome.isMatch() ? match : nonMatch).add(outcome);
211                        }
212                        if (nonMatch.isEmpty()) {
213                                return ConditionOutcome
214                                                .match(message.found("matching nested conditions").items(match));
215                        }
216                        return ConditionOutcome.noMatch(
217                                        message.found("non-matching nested conditions").items(nonMatch));
218                }
219
220        }
221
222}