001/*
002 * Copyright 2012-2017 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 org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021
022import org.springframework.context.annotation.Condition;
023import org.springframework.context.annotation.ConditionContext;
024import org.springframework.core.type.AnnotatedTypeMetadata;
025import org.springframework.core.type.AnnotationMetadata;
026import org.springframework.core.type.ClassMetadata;
027import org.springframework.core.type.MethodMetadata;
028import org.springframework.util.ClassUtils;
029import org.springframework.util.StringUtils;
030
031/**
032 * Base of all {@link Condition} implementations used with Spring Boot. Provides sensible
033 * logging to help the user diagnose what classes are loaded.
034 *
035 * @author Phillip Webb
036 * @author Greg Turnquist
037 */
038public abstract class SpringBootCondition implements Condition {
039
040        private final Log logger = LogFactory.getLog(getClass());
041
042        @Override
043        public final boolean matches(ConditionContext context,
044                        AnnotatedTypeMetadata metadata) {
045                String classOrMethodName = getClassOrMethodName(metadata);
046                try {
047                        ConditionOutcome outcome = getMatchOutcome(context, metadata);
048                        logOutcome(classOrMethodName, outcome);
049                        recordEvaluation(context, classOrMethodName, outcome);
050                        return outcome.isMatch();
051                }
052                catch (NoClassDefFoundError ex) {
053                        throw new IllegalStateException(
054                                        "Could not evaluate condition on " + classOrMethodName + " due to "
055                                                        + ex.getMessage() + " not "
056                                                        + "found. Make sure your own configuration does not rely on "
057                                                        + "that class. This can also happen if you are "
058                                                        + "@ComponentScanning a springframework package (e.g. if you "
059                                                        + "put a @ComponentScan in the default package by mistake)",
060                                        ex);
061                }
062                catch (RuntimeException ex) {
063                        throw new IllegalStateException(
064                                        "Error processing condition on " + getName(metadata), ex);
065                }
066        }
067
068        private String getName(AnnotatedTypeMetadata metadata) {
069                if (metadata instanceof AnnotationMetadata) {
070                        return ((AnnotationMetadata) metadata).getClassName();
071                }
072                if (metadata instanceof MethodMetadata) {
073                        MethodMetadata methodMetadata = (MethodMetadata) metadata;
074                        return methodMetadata.getDeclaringClassName() + "."
075                                        + methodMetadata.getMethodName();
076                }
077                return metadata.toString();
078        }
079
080        private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
081                if (metadata instanceof ClassMetadata) {
082                        ClassMetadata classMetadata = (ClassMetadata) metadata;
083                        return classMetadata.getClassName();
084                }
085                MethodMetadata methodMetadata = (MethodMetadata) metadata;
086                return methodMetadata.getDeclaringClassName() + "#"
087                                + methodMetadata.getMethodName();
088        }
089
090        protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
091                if (this.logger.isTraceEnabled()) {
092                        this.logger.trace(getLogMessage(classOrMethodName, outcome));
093                }
094        }
095
096        private StringBuilder getLogMessage(String classOrMethodName,
097                        ConditionOutcome outcome) {
098                StringBuilder message = new StringBuilder();
099                message.append("Condition ");
100                message.append(ClassUtils.getShortName(getClass()));
101                message.append(" on ");
102                message.append(classOrMethodName);
103                message.append(outcome.isMatch() ? " matched" : " did not match");
104                if (StringUtils.hasLength(outcome.getMessage())) {
105                        message.append(" due to ");
106                        message.append(outcome.getMessage());
107                }
108                return message;
109        }
110
111        private void recordEvaluation(ConditionContext context, String classOrMethodName,
112                        ConditionOutcome outcome) {
113                if (context.getBeanFactory() != null) {
114                        ConditionEvaluationReport.get(context.getBeanFactory())
115                                        .recordConditionEvaluation(classOrMethodName, this, outcome);
116                }
117        }
118
119        /**
120         * Determine the outcome of the match along with suitable log output.
121         * @param context the condition context
122         * @param metadata the annotation metadata
123         * @return the condition outcome
124         */
125        public abstract ConditionOutcome getMatchOutcome(ConditionContext context,
126                        AnnotatedTypeMetadata metadata);
127
128        /**
129         * Return true if any of the specified conditions match.
130         * @param context the context
131         * @param metadata the annotation meta-data
132         * @param conditions conditions to test
133         * @return {@code true} if any condition matches.
134         */
135        protected final boolean anyMatches(ConditionContext context,
136                        AnnotatedTypeMetadata metadata, Condition... conditions) {
137                for (Condition condition : conditions) {
138                        if (matches(context, metadata, condition)) {
139                                return true;
140                        }
141                }
142                return false;
143        }
144
145        /**
146         * Return true if any of the specified condition matches.
147         * @param context the context
148         * @param metadata the annotation meta-data
149         * @param condition condition to test
150         * @return {@code true} if the condition matches.
151         */
152        protected final boolean matches(ConditionContext context,
153                        AnnotatedTypeMetadata metadata, Condition condition) {
154                if (condition instanceof SpringBootCondition) {
155                        return ((SpringBootCondition) condition).getMatchOutcome(context, metadata)
156                                        .isMatch();
157                }
158                return condition.matches(context, metadata);
159        }
160
161}