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.actuate.autoconfigure.condition;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import com.fasterxml.jackson.annotation.JsonInclude;
027import com.fasterxml.jackson.annotation.JsonInclude.Include;
028import com.fasterxml.jackson.annotation.JsonPropertyOrder;
029
030import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
031import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
032import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
033import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
034import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
035import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
036import org.springframework.context.ApplicationContext;
037import org.springframework.context.ConfigurableApplicationContext;
038import org.springframework.context.annotation.Condition;
039import org.springframework.util.ClassUtils;
040import org.springframework.util.LinkedMultiValueMap;
041import org.springframework.util.MultiValueMap;
042import org.springframework.util.StringUtils;
043
044/**
045 * {@link Endpoint} to expose the {@link ConditionEvaluationReport}.
046 *
047 * @author Greg Turnquist
048 * @author Phillip Webb
049 * @author Dave Syer
050 * @author Andy Wilkinson
051 * @since 2.0.0
052 */
053@Endpoint(id = "conditions")
054public class ConditionsReportEndpoint {
055
056        private final ConfigurableApplicationContext context;
057
058        public ConditionsReportEndpoint(ConfigurableApplicationContext context) {
059                this.context = context;
060        }
061
062        @ReadOperation
063        public ApplicationConditionEvaluation applicationConditionEvaluation() {
064                Map<String, ContextConditionEvaluation> contextConditionEvaluations = new HashMap<>();
065                ConfigurableApplicationContext target = this.context;
066                while (target != null) {
067                        contextConditionEvaluations.put(target.getId(),
068                                        new ContextConditionEvaluation(target));
069                        target = getConfigurableParent(target);
070                }
071                return new ApplicationConditionEvaluation(contextConditionEvaluations);
072        }
073
074        private ConfigurableApplicationContext getConfigurableParent(
075                        ConfigurableApplicationContext context) {
076                ApplicationContext parent = context.getParent();
077                if (parent instanceof ConfigurableApplicationContext) {
078                        return (ConfigurableApplicationContext) parent;
079                }
080                return null;
081        }
082
083        /**
084         * A description of an application's condition evaluation, primarily intended for
085         * serialization to JSON.
086         */
087        public static final class ApplicationConditionEvaluation {
088
089                private final Map<String, ContextConditionEvaluation> contexts;
090
091                private ApplicationConditionEvaluation(
092                                Map<String, ContextConditionEvaluation> contexts) {
093                        this.contexts = contexts;
094                }
095
096                public Map<String, ContextConditionEvaluation> getContexts() {
097                        return this.contexts;
098                }
099
100        }
101
102        /**
103         * A description of an application context's condition evaluation, primarily intended
104         * for serialization to JSON.
105         */
106        @JsonInclude(Include.NON_EMPTY)
107        public static final class ContextConditionEvaluation {
108
109                private final MultiValueMap<String, MessageAndCondition> positiveMatches;
110
111                private final Map<String, MessageAndConditions> negativeMatches;
112
113                private final List<String> exclusions;
114
115                private final Set<String> unconditionalClasses;
116
117                private final String parentId;
118
119                public ContextConditionEvaluation(ConfigurableApplicationContext context) {
120                        ConditionEvaluationReport report = ConditionEvaluationReport
121                                        .get(context.getBeanFactory());
122                        this.positiveMatches = new LinkedMultiValueMap<>();
123                        this.negativeMatches = new LinkedHashMap<>();
124                        this.exclusions = report.getExclusions();
125                        this.unconditionalClasses = report.getUnconditionalClasses();
126                        report.getConditionAndOutcomesBySource().forEach(this::add);
127                        this.parentId = (context.getParent() != null) ? context.getParent().getId()
128                                        : null;
129                }
130
131                private void add(String source, ConditionAndOutcomes conditionAndOutcomes) {
132                        String name = ClassUtils.getShortName(source);
133                        if (conditionAndOutcomes.isFullMatch()) {
134                                conditionAndOutcomes.forEach((conditionAndOutcome) -> this.positiveMatches
135                                                .add(name, new MessageAndCondition(conditionAndOutcome)));
136                        }
137                        else {
138                                this.negativeMatches.put(name,
139                                                new MessageAndConditions(conditionAndOutcomes));
140                        }
141                }
142
143                public Map<String, List<MessageAndCondition>> getPositiveMatches() {
144                        return this.positiveMatches;
145                }
146
147                public Map<String, MessageAndConditions> getNegativeMatches() {
148                        return this.negativeMatches;
149                }
150
151                public List<String> getExclusions() {
152                        return this.exclusions;
153                }
154
155                public Set<String> getUnconditionalClasses() {
156                        return this.unconditionalClasses;
157                }
158
159                public String getParentId() {
160                        return this.parentId;
161                }
162
163        }
164
165        /**
166         * Adapts {@link ConditionAndOutcomes} to a JSON friendly structure.
167         */
168        @JsonPropertyOrder({ "notMatched", "matched" })
169        public static class MessageAndConditions {
170
171                private final List<MessageAndCondition> notMatched = new ArrayList<>();
172
173                private final List<MessageAndCondition> matched = new ArrayList<>();
174
175                public MessageAndConditions(ConditionAndOutcomes conditionAndOutcomes) {
176                        for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
177                                List<MessageAndCondition> target = (conditionAndOutcome.getOutcome()
178                                                .isMatch() ? this.matched : this.notMatched);
179                                target.add(new MessageAndCondition(conditionAndOutcome));
180                        }
181                }
182
183                public List<MessageAndCondition> getNotMatched() {
184                        return this.notMatched;
185                }
186
187                public List<MessageAndCondition> getMatched() {
188                        return this.matched;
189                }
190
191        }
192
193        /**
194         * Adapts {@link ConditionAndOutcome} to a JSON friendly structure.
195         */
196        @JsonPropertyOrder({ "condition", "message" })
197        public static class MessageAndCondition {
198
199                private final String condition;
200
201                private final String message;
202
203                public MessageAndCondition(ConditionAndOutcome conditionAndOutcome) {
204                        Condition condition = conditionAndOutcome.getCondition();
205                        ConditionOutcome outcome = conditionAndOutcome.getOutcome();
206                        this.condition = ClassUtils.getShortName(condition.getClass());
207                        if (StringUtils.hasLength(outcome.getMessage())) {
208                                this.message = outcome.getMessage();
209                        }
210                        else {
211                                this.message = outcome.isMatch() ? "matched" : "did not match";
212                        }
213                }
214
215                public String getCondition() {
216                        return this.condition;
217                }
218
219                public String getMessage() {
220                        return this.message;
221                }
222
223        }
224
225}