001/*
002 * Copyright 2002-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 *        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.core.env;
018
019import org.springframework.core.convert.ConversionException;
020import org.springframework.util.ClassUtils;
021
022/**
023 * {@link PropertyResolver} implementation that resolves property values against
024 * an underlying set of {@link PropertySources}.
025 *
026 * @author Chris Beams
027 * @author Juergen Hoeller
028 * @since 3.1
029 * @see PropertySource
030 * @see PropertySources
031 * @see AbstractEnvironment
032 */
033public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
034
035        private final PropertySources propertySources;
036
037
038        /**
039         * Create a new resolver against the given property sources.
040         * @param propertySources the set of {@link PropertySource} objects to use
041         */
042        public PropertySourcesPropertyResolver(PropertySources propertySources) {
043                this.propertySources = propertySources;
044        }
045
046
047        @Override
048        public boolean containsProperty(String key) {
049                if (this.propertySources != null) {
050                        for (PropertySource<?> propertySource : this.propertySources) {
051                                if (propertySource.containsProperty(key)) {
052                                        return true;
053                                }
054                        }
055                }
056                return false;
057        }
058
059        @Override
060        public String getProperty(String key) {
061                return getProperty(key, String.class, true);
062        }
063
064        @Override
065        public <T> T getProperty(String key, Class<T> targetValueType) {
066                return getProperty(key, targetValueType, true);
067        }
068
069        @Override
070        protected String getPropertyAsRawString(String key) {
071                return getProperty(key, String.class, false);
072        }
073
074        protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
075                if (this.propertySources != null) {
076                        for (PropertySource<?> propertySource : this.propertySources) {
077                                if (logger.isTraceEnabled()) {
078                                        logger.trace("Searching for key '" + key + "' in PropertySource '" +
079                                                        propertySource.getName() + "'");
080                                }
081                                Object value = propertySource.getProperty(key);
082                                if (value != null) {
083                                        if (resolveNestedPlaceholders && value instanceof String) {
084                                                value = resolveNestedPlaceholders((String) value);
085                                        }
086                                        logKeyFound(key, propertySource, value);
087                                        return convertValueIfNecessary(value, targetValueType);
088                                }
089                        }
090                }
091                if (logger.isDebugEnabled()) {
092                        logger.debug("Could not find key '" + key + "' in any property source");
093                }
094                return null;
095        }
096
097        @Override
098        @Deprecated
099        public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
100                if (this.propertySources != null) {
101                        for (PropertySource<?> propertySource : this.propertySources) {
102                                if (logger.isTraceEnabled()) {
103                                        logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
104                                }
105                                Object value = propertySource.getProperty(key);
106                                if (value != null) {
107                                        logKeyFound(key, propertySource, value);
108                                        Class<?> clazz;
109                                        if (value instanceof String) {
110                                                try {
111                                                        clazz = ClassUtils.forName((String) value, null);
112                                                }
113                                                catch (Exception ex) {
114                                                        throw new ClassConversionException((String) value, targetValueType, ex);
115                                                }
116                                        }
117                                        else if (value instanceof Class) {
118                                                clazz = (Class<?>) value;
119                                        }
120                                        else {
121                                                clazz = value.getClass();
122                                        }
123                                        if (!targetValueType.isAssignableFrom(clazz)) {
124                                                throw new ClassConversionException(clazz, targetValueType);
125                                        }
126                                        @SuppressWarnings("unchecked")
127                                        Class<T> targetClass = (Class<T>) clazz;
128                                        return targetClass;
129                                }
130                        }
131                }
132                if (logger.isDebugEnabled()) {
133                        logger.debug(String.format("Could not find key '%s' in any property source", key));
134                }
135                return null;
136        }
137
138        /**
139         * Log the given key as found in the given {@link PropertySource}, resulting in
140         * the given value.
141         * <p>The default implementation writes a debug log message with key and source.
142         * As of 4.3.3, this does not log the value anymore in order to avoid accidental
143         * logging of sensitive settings. Subclasses may override this method to change
144         * the log level and/or log message, including the property's value if desired.
145         * @param key the key found
146         * @param propertySource the {@code PropertySource} that the key has been found in
147         * @param value the corresponding value
148         * @since 4.3.1
149         */
150        protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
151                if (logger.isDebugEnabled()) {
152                        logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName() +
153                                        "' with value of type " + value.getClass().getSimpleName());
154                }
155        }
156
157
158        @SuppressWarnings("serial")
159        @Deprecated
160        private static class ClassConversionException extends ConversionException {
161
162                public ClassConversionException(Class<?> actual, Class<?> expected) {
163                        super(String.format("Actual type %s is not assignable to expected type %s",
164                                        actual.getName(), expected.getName()));
165                }
166
167                public ClassConversionException(String actual, Class<?> expected, Exception ex) {
168                        super(String.format("Could not find/load class %s during attempt to convert to %s",
169                                        actual, expected.getName()), ex);
170                }
171        }
172
173}