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