001/*
002 * Copyright 2002-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 *      https://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.test.annotation;
018
019import java.lang.reflect.Method;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023
024import org.springframework.core.annotation.AnnotatedElementUtils;
025import org.springframework.core.annotation.AnnotationUtils;
026import org.springframework.lang.Nullable;
027import org.springframework.util.Assert;
028import org.springframework.util.ObjectUtils;
029import org.springframework.util.ReflectionUtils;
030import org.springframework.util.StringUtils;
031
032/**
033 * General utility methods for working with <em>profile values</em>.
034 *
035 * @author Sam Brannen
036 * @author Juergen Hoeller
037 * @since 2.5
038 * @see ProfileValueSource
039 * @see ProfileValueSourceConfiguration
040 * @see IfProfileValue
041 */
042public abstract class ProfileValueUtils {
043
044        private static final Log logger = LogFactory.getLog(ProfileValueUtils.class);
045
046
047        /**
048         * Retrieves the {@link ProfileValueSource} type for the specified
049         * {@link Class test class} as configured via the
050         * {@link ProfileValueSourceConfiguration
051         * &#064;ProfileValueSourceConfiguration} annotation and instantiates a new
052         * instance of that type.
053         * <p>If {@link ProfileValueSourceConfiguration
054         * &#064;ProfileValueSourceConfiguration} is not present on the specified
055         * class or if a custom {@link ProfileValueSource} is not declared, the
056         * default {@link SystemProfileValueSource} will be returned instead.
057         * @param testClass the test class for which the ProfileValueSource should
058         * be retrieved
059         * @return the configured (or default) ProfileValueSource for the specified
060         * class
061         * @see SystemProfileValueSource
062         */
063        @SuppressWarnings("unchecked")
064        public static ProfileValueSource retrieveProfileValueSource(Class<?> testClass) {
065                Assert.notNull(testClass, "testClass must not be null");
066
067                Class<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class;
068                ProfileValueSourceConfiguration config = AnnotatedElementUtils.findMergedAnnotation(testClass, annotationType);
069                if (logger.isDebugEnabled()) {
070                        logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class [" +
071                                        testClass.getName() + "]");
072                }
073
074                Class<? extends ProfileValueSource> profileValueSourceType;
075                if (config != null) {
076                        profileValueSourceType = config.value();
077                }
078                else {
079                        profileValueSourceType = (Class<? extends ProfileValueSource>) AnnotationUtils.getDefaultValue(annotationType);
080                        Assert.state(profileValueSourceType != null, "No default ProfileValueSource class");
081                }
082                if (logger.isDebugEnabled()) {
083                        logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class [" +
084                                        testClass.getName() + "]");
085                }
086
087                ProfileValueSource profileValueSource;
088                if (SystemProfileValueSource.class == profileValueSourceType) {
089                        profileValueSource = SystemProfileValueSource.getInstance();
090                }
091                else {
092                        try {
093                                profileValueSource = ReflectionUtils.accessibleConstructor(profileValueSourceType).newInstance();
094                        }
095                        catch (Exception ex) {
096                                if (logger.isWarnEnabled()) {
097                                        logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType +
098                                                        "] for class [" + testClass.getName() + "]: using default.", ex);
099                                }
100                                profileValueSource = SystemProfileValueSource.getInstance();
101                        }
102                }
103
104                return profileValueSource;
105        }
106
107        /**
108         * Determine if the supplied {@code testClass} is <em>enabled</em> in
109         * the current environment, as specified by the {@link IfProfileValue
110         * &#064;IfProfileValue} annotation at the class level.
111         * <p>Defaults to {@code true} if no {@link IfProfileValue
112         * &#064;IfProfileValue} annotation is declared.
113         * @param testClass the test class
114         * @return {@code true} if the test is <em>enabled</em> in the current
115         * environment
116         */
117        public static boolean isTestEnabledInThisEnvironment(Class<?> testClass) {
118                IfProfileValue ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testClass, IfProfileValue.class);
119                return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue);
120        }
121
122        /**
123         * Determine if the supplied {@code testMethod} is <em>enabled</em> in
124         * the current environment, as specified by the {@link IfProfileValue
125         * &#064;IfProfileValue} annotation, which may be declared on the test
126         * method itself or at the class level. Class-level usage overrides
127         * method-level usage.
128         * <p>Defaults to {@code true} if no {@link IfProfileValue
129         * &#064;IfProfileValue} annotation is declared.
130         * @param testMethod the test method
131         * @param testClass the test class
132         * @return {@code true} if the test is <em>enabled</em> in the current
133         * environment
134         */
135        public static boolean isTestEnabledInThisEnvironment(Method testMethod, Class<?> testClass) {
136                return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), testMethod, testClass);
137        }
138
139        /**
140         * Determine if the supplied {@code testMethod} is <em>enabled</em> in
141         * the current environment, as specified by the {@link IfProfileValue
142         * &#064;IfProfileValue} annotation, which may be declared on the test
143         * method itself or at the class level. Class-level usage overrides
144         * method-level usage.
145         * <p>Defaults to {@code true} if no {@link IfProfileValue
146         * &#064;IfProfileValue} annotation is declared.
147         * @param profileValueSource the ProfileValueSource to use to determine if
148         * the test is enabled
149         * @param testMethod the test method
150         * @param testClass the test class
151         * @return {@code true} if the test is <em>enabled</em> in the current
152         * environment
153         */
154        public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
155                        Class<?> testClass) {
156
157                IfProfileValue ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testClass, IfProfileValue.class);
158                boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
159
160                if (classLevelEnabled) {
161                        ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testMethod, IfProfileValue.class);
162                        return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
163                }
164
165                return false;
166        }
167
168        /**
169         * Determine if the {@code value} (or one of the {@code values})
170         * in the supplied {@link IfProfileValue &#064;IfProfileValue} annotation is
171         * <em>enabled</em> in the current environment.
172         * @param profileValueSource the ProfileValueSource to use to determine if
173         * the test is enabled
174         * @param ifProfileValue the annotation to introspect; may be
175         * {@code null}
176         * @return {@code true} if the test is <em>enabled</em> in the current
177         * environment or if the supplied {@code ifProfileValue} is
178         * {@code null}
179         */
180        private static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource,
181                        @Nullable IfProfileValue ifProfileValue) {
182
183                if (ifProfileValue == null) {
184                        return true;
185                }
186
187                String environmentValue = profileValueSource.get(ifProfileValue.name());
188                String[] annotatedValues = ifProfileValue.values();
189                if (StringUtils.hasLength(ifProfileValue.value())) {
190                        Assert.isTrue(annotatedValues.length == 0, () -> "Setting both the 'value' and 'values' attributes " +
191                                                "of @IfProfileValue is not allowed: choose one or the other.");
192                        annotatedValues = new String[] { ifProfileValue.value() };
193                }
194
195                for (String value : annotatedValues) {
196                        if (ObjectUtils.nullSafeEquals(value, environmentValue)) {
197                                return true;
198                        }
199                }
200                return false;
201        }
202
203}