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