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.io.support;
018
019import java.io.FileNotFoundException;
020import java.io.IOException;
021import java.net.SocketException;
022import java.net.UnknownHostException;
023import java.util.Properties;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028import org.springframework.core.io.Resource;
029import org.springframework.lang.Nullable;
030import org.springframework.util.CollectionUtils;
031import org.springframework.util.DefaultPropertiesPersister;
032import org.springframework.util.PropertiesPersister;
033
034/**
035 * Base class for JavaBean-style components that need to load properties
036 * from one or more resources. Supports local properties as well, with
037 * configurable overriding.
038 *
039 * @author Juergen Hoeller
040 * @since 1.2.2
041 */
042public abstract class PropertiesLoaderSupport {
043
044        /** Logger available to subclasses. */
045        protected final Log logger = LogFactory.getLog(getClass());
046
047        @Nullable
048        protected Properties[] localProperties;
049
050        protected boolean localOverride = false;
051
052        @Nullable
053        private Resource[] locations;
054
055        private boolean ignoreResourceNotFound = false;
056
057        @Nullable
058        private String fileEncoding;
059
060        private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
061
062
063        /**
064         * Set local properties, e.g. via the "props" tag in XML bean definitions.
065         * These can be considered defaults, to be overridden by properties
066         * loaded from files.
067         */
068        public void setProperties(Properties properties) {
069                this.localProperties = new Properties[] {properties};
070        }
071
072        /**
073         * Set local properties, e.g. via the "props" tag in XML bean definitions,
074         * allowing for merging multiple properties sets into one.
075         */
076        public void setPropertiesArray(Properties... propertiesArray) {
077                this.localProperties = propertiesArray;
078        }
079
080        /**
081         * Set a location of a properties file to be loaded.
082         * <p>Can point to a classic properties file or to an XML file
083         * that follows JDK 1.5's properties XML format.
084         */
085        public void setLocation(Resource location) {
086                this.locations = new Resource[] {location};
087        }
088
089        /**
090         * Set locations of properties files to be loaded.
091         * <p>Can point to classic properties files or to XML files
092         * that follow JDK 1.5's properties XML format.
093         * <p>Note: Properties defined in later files will override
094         * properties defined earlier files, in case of overlapping keys.
095         * Hence, make sure that the most specific files are the last
096         * ones in the given list of locations.
097         */
098        public void setLocations(Resource... locations) {
099                this.locations = locations;
100        }
101
102        /**
103         * Set whether local properties override properties from files.
104         * <p>Default is "false": Properties from files override local defaults.
105         * Can be switched to "true" to let local properties override defaults
106         * from files.
107         */
108        public void setLocalOverride(boolean localOverride) {
109                this.localOverride = localOverride;
110        }
111
112        /**
113         * Set if failure to find the property resource should be ignored.
114         * <p>"true" is appropriate if the properties file is completely optional.
115         * Default is "false".
116         */
117        public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
118                this.ignoreResourceNotFound = ignoreResourceNotFound;
119        }
120
121        /**
122         * Set the encoding to use for parsing properties files.
123         * <p>Default is none, using the {@code java.util.Properties}
124         * default encoding.
125         * <p>Only applies to classic properties files, not to XML files.
126         * @see org.springframework.util.PropertiesPersister#load
127         */
128        public void setFileEncoding(String encoding) {
129                this.fileEncoding = encoding;
130        }
131
132        /**
133         * Set the PropertiesPersister to use for parsing properties files.
134         * The default is DefaultPropertiesPersister.
135         * @see org.springframework.util.DefaultPropertiesPersister
136         */
137        public void setPropertiesPersister(@Nullable PropertiesPersister propertiesPersister) {
138                this.propertiesPersister =
139                                (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
140        }
141
142
143        /**
144         * Return a merged Properties instance containing both the
145         * loaded properties and properties set on this FactoryBean.
146         */
147        protected Properties mergeProperties() throws IOException {
148                Properties result = new Properties();
149
150                if (this.localOverride) {
151                        // Load properties from file upfront, to let local properties override.
152                        loadProperties(result);
153                }
154
155                if (this.localProperties != null) {
156                        for (Properties localProp : this.localProperties) {
157                                CollectionUtils.mergePropertiesIntoMap(localProp, result);
158                        }
159                }
160
161                if (!this.localOverride) {
162                        // Load properties from file afterwards, to let those properties override.
163                        loadProperties(result);
164                }
165
166                return result;
167        }
168
169        /**
170         * Load properties into the given instance.
171         * @param props the Properties instance to load into
172         * @throws IOException in case of I/O errors
173         * @see #setLocations
174         */
175        protected void loadProperties(Properties props) throws IOException {
176                if (this.locations != null) {
177                        for (Resource location : this.locations) {
178                                if (logger.isTraceEnabled()) {
179                                        logger.trace("Loading properties file from " + location);
180                                }
181                                try {
182                                        PropertiesLoaderUtils.fillProperties(
183                                                        props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
184                                }
185                                catch (FileNotFoundException | UnknownHostException | SocketException ex) {
186                                        if (this.ignoreResourceNotFound) {
187                                                if (logger.isDebugEnabled()) {
188                                                        logger.debug("Properties resource not found: " + ex.getMessage());
189                                                }
190                                        }
191                                        else {
192                                                throw ex;
193                                        }
194                                }
195                        }
196                }
197        }
198
199}