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.logging;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
029import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
030import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
031import org.springframework.util.ClassUtils;
032import org.springframework.util.LinkedMultiValueMap;
033import org.springframework.util.MultiValueMap;
034import org.springframework.util.StringUtils;
035
036/**
037 * A condition evaluation report message that can logged or printed.
038 *
039 * @author Phillip Webb
040 * @author Andy Wilkinson
041 * @since 1.4.0
042 */
043public class ConditionEvaluationReportMessage {
044
045        private StringBuilder message;
046
047        public ConditionEvaluationReportMessage(ConditionEvaluationReport report) {
048                this(report, "CONDITIONS EVALUATION REPORT");
049        }
050
051        public ConditionEvaluationReportMessage(ConditionEvaluationReport report,
052                        String title) {
053                this.message = getLogMessage(report, title);
054        }
055
056        private StringBuilder getLogMessage(ConditionEvaluationReport report, String title) {
057                StringBuilder message = new StringBuilder();
058                message.append(String.format("%n%n%n"));
059                StringBuilder separator = new StringBuilder();
060                for (int i = 0; i < title.length(); i++) {
061                        separator.append("=");
062                }
063                message.append(String.format("%s%n", separator));
064                message.append(String.format("%s%n", title));
065                message.append(String.format("%s%n%n%n", separator));
066                Map<String, ConditionAndOutcomes> shortOutcomes = orderByName(
067                                report.getConditionAndOutcomesBySource());
068                logPositiveMatches(message, shortOutcomes);
069                logNegativeMatches(message, shortOutcomes);
070                logExclusions(report, message);
071                logUnconditionalClasses(report, message);
072                message.append(String.format("%n%n"));
073                return message;
074        }
075
076        private void logPositiveMatches(StringBuilder message,
077                        Map<String, ConditionAndOutcomes> shortOutcomes) {
078                message.append(String.format("Positive matches:%n"));
079                message.append(String.format("-----------------%n"));
080                List<Entry<String, ConditionAndOutcomes>> matched = shortOutcomes.entrySet()
081                                .stream().filter((entry) -> entry.getValue().isFullMatch())
082                                .collect(Collectors.toList());
083                if (matched.isEmpty()) {
084                        message.append(String.format("%n    None%n"));
085                }
086                else {
087                        matched.forEach((entry) -> addMatchLogMessage(message, entry.getKey(),
088                                        entry.getValue()));
089                }
090                message.append(String.format("%n%n"));
091        }
092
093        private void logNegativeMatches(StringBuilder message,
094                        Map<String, ConditionAndOutcomes> shortOutcomes) {
095                message.append(String.format("Negative matches:%n"));
096                message.append(String.format("-----------------%n"));
097                List<Entry<String, ConditionAndOutcomes>> nonMatched = shortOutcomes.entrySet()
098                                .stream().filter((entry) -> !entry.getValue().isFullMatch())
099                                .collect(Collectors.toList());
100                if (nonMatched.isEmpty()) {
101                        message.append(String.format("%n    None%n"));
102                }
103                else {
104                        nonMatched.forEach((entry) -> addNonMatchLogMessage(message, entry.getKey(),
105                                        entry.getValue()));
106                }
107                message.append(String.format("%n%n"));
108        }
109
110        private void logExclusions(ConditionEvaluationReport report, StringBuilder message) {
111                message.append(String.format("Exclusions:%n"));
112                message.append(String.format("-----------%n"));
113                if (report.getExclusions().isEmpty()) {
114                        message.append(String.format("%n    None%n"));
115                }
116                else {
117                        for (String exclusion : report.getExclusions()) {
118                                message.append(String.format("%n    %s%n", exclusion));
119                        }
120                }
121                message.append(String.format("%n%n"));
122        }
123
124        private void logUnconditionalClasses(ConditionEvaluationReport report,
125                        StringBuilder message) {
126                message.append(String.format("Unconditional classes:%n"));
127                message.append(String.format("----------------------%n"));
128                if (report.getUnconditionalClasses().isEmpty()) {
129                        message.append(String.format("%n    None%n"));
130                }
131                else {
132                        for (String unconditionalClass : report.getUnconditionalClasses()) {
133                                message.append(String.format("%n    %s%n", unconditionalClass));
134                        }
135                }
136        }
137
138        private Map<String, ConditionAndOutcomes> orderByName(
139                        Map<String, ConditionAndOutcomes> outcomes) {
140                MultiValueMap<String, String> map = mapToFullyQualifiedNames(outcomes.keySet());
141                List<String> shortNames = new ArrayList<>(map.keySet());
142                Collections.sort(shortNames);
143                Map<String, ConditionAndOutcomes> result = new LinkedHashMap<>();
144                for (String shortName : shortNames) {
145                        List<String> fullyQualifiedNames = map.get(shortName);
146                        if (fullyQualifiedNames.size() > 1) {
147                                fullyQualifiedNames.forEach((fullyQualifiedName) -> result
148                                                .put(fullyQualifiedName, outcomes.get(fullyQualifiedName)));
149                        }
150                        else {
151                                result.put(shortName, outcomes.get(fullyQualifiedNames.get(0)));
152                        }
153                }
154                return result;
155        }
156
157        private MultiValueMap<String, String> mapToFullyQualifiedNames(Set<String> keySet) {
158                LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>();
159                keySet.forEach((fullyQualifiedName) -> map
160                                .add(ClassUtils.getShortName(fullyQualifiedName), fullyQualifiedName));
161                return map;
162        }
163
164        private void addMatchLogMessage(StringBuilder message, String source,
165                        ConditionAndOutcomes matches) {
166                message.append(String.format("%n   %s matched:%n", source));
167                for (ConditionAndOutcome match : matches) {
168                        logConditionAndOutcome(message, "      ", match);
169                }
170        }
171
172        private void addNonMatchLogMessage(StringBuilder message, String source,
173                        ConditionAndOutcomes conditionAndOutcomes) {
174                message.append(String.format("%n   %s:%n", source));
175                List<ConditionAndOutcome> matches = new ArrayList<>();
176                List<ConditionAndOutcome> nonMatches = new ArrayList<>();
177                for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
178                        if (conditionAndOutcome.getOutcome().isMatch()) {
179                                matches.add(conditionAndOutcome);
180                        }
181                        else {
182                                nonMatches.add(conditionAndOutcome);
183                        }
184                }
185                message.append(String.format("      Did not match:%n"));
186                for (ConditionAndOutcome nonMatch : nonMatches) {
187                        logConditionAndOutcome(message, "         ", nonMatch);
188                }
189                if (!matches.isEmpty()) {
190                        message.append(String.format("      Matched:%n"));
191                        for (ConditionAndOutcome match : matches) {
192                                logConditionAndOutcome(message, "         ", match);
193                        }
194                }
195        }
196
197        private void logConditionAndOutcome(StringBuilder message, String indent,
198                        ConditionAndOutcome conditionAndOutcome) {
199                message.append(String.format("%s- ", indent));
200                String outcomeMessage = conditionAndOutcome.getOutcome().getMessage();
201                if (StringUtils.hasLength(outcomeMessage)) {
202                        message.append(outcomeMessage);
203                }
204                else {
205                        message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched"
206                                        : "did not match");
207                }
208                message.append(" (");
209                message.append(
210                                ClassUtils.getShortName(conditionAndOutcome.getCondition().getClass()));
211                message.append(String.format(")%n"));
212        }
213
214        @Override
215        public String toString() {
216                return this.message.toString();
217        }
218
219}