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 java.util.LinkedHashSet;
020import java.util.Set;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.core.convert.ConversionService;
026import org.springframework.core.convert.support.ConfigurableConversionService;
027import org.springframework.core.convert.support.DefaultConversionService;
028import org.springframework.util.Assert;
029import org.springframework.util.ClassUtils;
030import org.springframework.util.PropertyPlaceholderHelper;
031import org.springframework.util.SystemPropertyUtils;
032
033/**
034 * Abstract base class for resolving properties against any underlying source.
035 *
036 * @author Chris Beams
037 * @author Juergen Hoeller
038 * @since 3.1
039 */
040public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
041
042        protected final Log logger = LogFactory.getLog(getClass());
043
044        private volatile ConfigurableConversionService conversionService;
045
046        private PropertyPlaceholderHelper nonStrictHelper;
047
048        private PropertyPlaceholderHelper strictHelper;
049
050        private boolean ignoreUnresolvableNestedPlaceholders = false;
051
052        private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
053
054        private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
055
056        private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
057
058        private final Set<String> requiredProperties = new LinkedHashSet<String>();
059
060
061        @Override
062        public ConfigurableConversionService getConversionService() {
063                // Need to provide an independent DefaultConversionService, not the
064                // shared DefaultConversionService used by PropertySourcesPropertyResolver.
065                if (this.conversionService == null) {
066                        synchronized (this) {
067                                if (this.conversionService == null) {
068                                        this.conversionService = new DefaultConversionService();
069                                }
070                        }
071                }
072                return conversionService;
073        }
074
075        @Override
076        public void setConversionService(ConfigurableConversionService conversionService) {
077                Assert.notNull(conversionService, "ConversionService must not be null");
078                this.conversionService = conversionService;
079        }
080
081        /**
082         * Set the prefix that placeholders replaced by this resolver must begin with.
083         * <p>The default is "${".
084         * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX
085         */
086        @Override
087        public void setPlaceholderPrefix(String placeholderPrefix) {
088                Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
089                this.placeholderPrefix = placeholderPrefix;
090        }
091
092        /**
093         * Set the suffix that placeholders replaced by this resolver must end with.
094         * <p>The default is "}".
095         * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX
096         */
097        @Override
098        public void setPlaceholderSuffix(String placeholderSuffix) {
099                Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
100                this.placeholderSuffix = placeholderSuffix;
101        }
102
103        /**
104         * Specify the separating character between the placeholders replaced by this
105         * resolver and their associated default value, or {@code null} if no such
106         * special character should be processed as a value separator.
107         * <p>The default is ":".
108         * @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR
109         */
110        @Override
111        public void setValueSeparator(String valueSeparator) {
112                this.valueSeparator = valueSeparator;
113        }
114
115        /**
116         * Set whether to throw an exception when encountering an unresolvable placeholder
117         * nested within the value of a given property. A {@code false} value indicates strict
118         * resolution, i.e. that an exception will be thrown. A {@code true} value indicates
119         * that unresolvable nested placeholders should be passed through in their unresolved
120         * ${...} form.
121         * <p>The default is {@code false}.
122         * @since 3.2
123         */
124        @Override
125        public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
126                this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders;
127        }
128
129        @Override
130        public void setRequiredProperties(String... requiredProperties) {
131                if (requiredProperties != null) {
132                        for (String key : requiredProperties) {
133                                this.requiredProperties.add(key);
134                        }
135                }
136        }
137
138        @Override
139        public void validateRequiredProperties() {
140                MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
141                for (String key : this.requiredProperties) {
142                        if (this.getProperty(key) == null) {
143                                ex.addMissingRequiredProperty(key);
144                        }
145                }
146                if (!ex.getMissingRequiredProperties().isEmpty()) {
147                        throw ex;
148                }
149        }
150
151        @Override
152        public boolean containsProperty(String key) {
153                return (getProperty(key) != null);
154        }
155
156        @Override
157        public String getProperty(String key) {
158                return getProperty(key, String.class);
159        }
160
161        @Override
162        public String getProperty(String key, String defaultValue) {
163                String value = getProperty(key);
164                return (value != null ? value : defaultValue);
165        }
166
167        @Override
168        public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
169                T value = getProperty(key, targetType);
170                return (value != null ? value : defaultValue);
171        }
172
173        @Override
174        @Deprecated
175        public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
176                throw new UnsupportedOperationException();
177        }
178
179        @Override
180        public String getRequiredProperty(String key) throws IllegalStateException {
181                String value = getProperty(key);
182                if (value == null) {
183                        throw new IllegalStateException("Required key '" + key + "' not found");
184                }
185                return value;
186        }
187
188        @Override
189        public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException {
190                T value = getProperty(key, valueType);
191                if (value == null) {
192                        throw new IllegalStateException("Required key '" + key + "' not found");
193                }
194                return value;
195        }
196
197        @Override
198        public String resolvePlaceholders(String text) {
199                if (this.nonStrictHelper == null) {
200                        this.nonStrictHelper = createPlaceholderHelper(true);
201                }
202                return doResolvePlaceholders(text, this.nonStrictHelper);
203        }
204
205        @Override
206        public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
207                if (this.strictHelper == null) {
208                        this.strictHelper = createPlaceholderHelper(false);
209                }
210                return doResolvePlaceholders(text, this.strictHelper);
211        }
212
213        /**
214         * Resolve placeholders within the given string, deferring to the value of
215         * {@link #setIgnoreUnresolvableNestedPlaceholders} to determine whether any
216         * unresolvable placeholders should raise an exception or be ignored.
217         * <p>Invoked from {@link #getProperty} and its variants, implicitly resolving
218         * nested placeholders. In contrast, {@link #resolvePlaceholders} and
219         * {@link #resolveRequiredPlaceholders} do <emphasis>not</emphasis> delegate
220         * to this method but rather perform their own handling of unresolvable
221         * placeholders, as specified by each of those methods.
222         * @since 3.2
223         * @see #setIgnoreUnresolvableNestedPlaceholders
224         */
225        protected String resolveNestedPlaceholders(String value) {
226                return (this.ignoreUnresolvableNestedPlaceholders ?
227                                resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
228        }
229
230        private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
231                return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
232                                this.valueSeparator, ignoreUnresolvablePlaceholders);
233        }
234
235        private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
236                return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
237                        @Override
238                        public String resolvePlaceholder(String placeholderName) {
239                                return getPropertyAsRawString(placeholderName);
240                        }
241                });
242        }
243
244        /**
245         * Convert the given value to the specified target type, if necessary.
246         * @param value the original property value
247         * @param targetType the specified target type for property retrieval
248         * @return the converted value, or the original value if no conversion
249         * is necessary
250         * @since 4.3.5
251         */
252        @SuppressWarnings("unchecked")
253        protected <T> T convertValueIfNecessary(Object value, Class<T> targetType) {
254                if (targetType == null) {
255                        return (T) value;
256                }
257                ConversionService conversionServiceToUse = this.conversionService;
258                if (conversionServiceToUse == null) {
259                        // Avoid initialization of shared DefaultConversionService if
260                        // no standard type conversion is needed in the first place...
261                        if (ClassUtils.isAssignableValue(targetType, value)) {
262                                return (T) value;
263                        }
264                        conversionServiceToUse = DefaultConversionService.getSharedInstance();
265                }
266                return conversionServiceToUse.convert(value, targetType);
267        }
268
269
270        /**
271         * Retrieve the specified property as a raw String,
272         * i.e. without resolution of nested placeholders.
273         * @param key the property name to resolve
274         * @return the property value or {@code null} if none found
275         */
276        protected abstract String getPropertyAsRawString(String key);
277
278}