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}