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