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}