001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php
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 */
016package org.kuali.rice.krad.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021import org.apache.log4j.Logger;
022import org.kuali.rice.core.api.config.property.ConfigContext;
023import org.kuali.rice.core.api.config.property.ConfigurationService;
024import org.kuali.rice.core.api.util.Truth;
025import org.kuali.rice.kns.web.struts.action.KualiPropertyMessageResources;
026import org.kuali.rice.kns.web.struts.action.KualiPropertyMessageResourcesFactory;
027import org.kuali.rice.krad.exception.DuplicateKeyException;
028import org.kuali.rice.krad.exception.PropertiesException;
029
030import java.io.IOException;
031import java.io.InputStream;
032import java.net.URL;
033import java.util.Collections;
034import java.util.Iterator;
035import java.util.Map;
036import java.util.Properties;
037
038/**
039 * ConfigurationService implementation which is a thin wrapper over the core Config object, and which
040 * merges in Kuali message resource properties.
041 */
042public class ConfigurationServiceImpl implements ConfigurationService {
043    private final PropertyHolder propertyHolder = new PropertyHolder();
044
045    /**
046     * Harcoding the configFileName, by request.
047     */
048    public ConfigurationServiceImpl() {
049        this.propertyHolder.getHeldProperties().putAll(ConfigContext.getCurrentContextConfig().getProperties());
050
051        KualiPropertyMessageResourcesFactory propertyMessageFactory = new KualiPropertyMessageResourcesFactory();
052
053        // create default KualiPropertyMessageResources
054        KualiPropertyMessageResources messageResources =
055                (KualiPropertyMessageResources) propertyMessageFactory.createResources("");
056
057        //Add Kuali Properties to property holder
058        this.propertyHolder.getHeldProperties().putAll(messageResources.getKualiProperties(null));
059    }
060
061    /**
062     * @see org.kuali.rice.core.api.config.property.ConfigurationService#getPropertyValueAsString(java.lang.String)
063     */
064    @Override
065    public String getPropertyValueAsString(String key) {
066        if (key == null) {
067            throw new IllegalArgumentException("invalid (null) key");
068        }
069
070        return this.propertyHolder.getProperty(key);
071    }
072
073    /**
074     * @see org.kuali.rice.core.api.config.property.ConfigurationService#getPropertyValueAsBoolean(java.lang.String)
075     */
076    @Override
077    public boolean getPropertyValueAsBoolean(String key) {
078        if (key == null) {
079            throw new IllegalArgumentException("invalid (null) key");
080        }
081        String property = this.propertyHolder.getProperty(key);
082        Boolean b = Truth.strToBooleanIgnoreCase(property);
083        if (b == null) {
084            return false;
085        }
086        return b;
087    }
088
089    /**
090     * @see org.kuali.rice.core.api.config.property.ConfigurationService#getAllProperties()
091     */
092    @Override
093    public Map<String, String> getAllProperties() {
094        return (Map) Collections.unmodifiableMap(propertyHolder.getHeldProperties());
095    }
096
097    /**
098     * This is an interface for a source for properties
099     *
100     *
101     */
102    static interface PropertySource {
103        /**
104         * @return Properties loaded from this PropertySource
105         * @throws org.kuali.rice.krad.exception.PropertiesException if there's a problem loading the properties
106         */
107        public Properties loadProperties();
108    }
109
110    /**
111     * This class is a Property container. It is able to load properties from various property-sources.
112     *
113     *
114     */
115    static class PropertyHolder {
116        private static Logger LOG = Logger.getLogger(PropertyHolder.class);
117
118        Properties heldProperties;
119
120        /**
121         * Default constructor.
122         */
123        public PropertyHolder() {
124            this.heldProperties = new Properties();
125        }
126
127
128        /**
129         * @return true if this container currently has no properties
130         */
131        public boolean isEmpty() {
132            return this.heldProperties.isEmpty();
133        }
134
135        /**
136         * @param key
137         * @return true if a property with the given key exists in this container
138         * @throws IllegalArgumentException if the given key is null
139         */
140        public boolean containsKey(String key) {
141            validateKey(key);
142
143            return this.heldProperties.containsKey(key);
144        }
145
146        /**
147         * @param key
148         * @return the current value of the property with the given key, or null if no property exists with that key
149         * @throws IllegalArgumentException if the given key is null
150         */
151        public String getProperty(String key) {
152            validateKey(key);
153
154            return this.heldProperties.getProperty(key);
155        }
156
157
158        /**
159         * Associates the given value with the given key
160         *
161         * @param key
162         * @param value
163         * @throws IllegalArgumentException if the given key is null
164         * @throws IllegalArgumentException if the given value is null
165         * @throws org.kuali.rice.krad.exception.DuplicateKeyException if a property with the given key already exists
166         */
167        public void setProperty(String key, String value) {
168        setProperty(null, key, value);
169        }
170
171        /**
172         * Associates the given value with the given key
173         *
174         * @param source
175         * @param key
176         * @param value
177         * @throws IllegalArgumentException if the given key is null
178         * @throws IllegalArgumentException if the given value is null
179         * @throws org.kuali.rice.krad.exception.DuplicateKeyException if a property with the given key already exists
180         */
181        public void setProperty(PropertySource source, String key, String value) {
182            validateKey(key);
183            validateValue(value);
184
185            if (containsKey(key)) {
186                if (source != null && source instanceof FilePropertySource && ((FilePropertySource)source).isAllowOverrides()) {
187                    LOG.info("Duplicate Key: Override is enabled [key=" + key + ", new value=" + value + ", old value=" + this.heldProperties.getProperty(key) + "]");
188                } else {
189                    throw new DuplicateKeyException("duplicate key '" + key + "'");
190                }
191            }
192            this.heldProperties.setProperty(key, value);
193        }
194
195        /**
196         * Removes the property with the given key from this container
197         *
198         * @param key
199         * @throws IllegalArgumentException if the given key is null
200         */
201        public void clearProperty(String key) {
202            validateKey(key);
203
204            this.heldProperties.remove(key);
205        }
206
207
208        /**
209         * Copies all name,value pairs from the given PropertySource instance into this container.
210         *
211         * @param source
212         * @throws IllegalStateException if the source is invalid (improperly initialized)
213         * @throws org.kuali.rice.krad.exception.DuplicateKeyException the first time a given property has the same key as an existing property
214         * @throws org.kuali.rice.krad.exception.PropertiesException if unable to load properties from the given source
215         */
216        public void loadProperties(PropertySource source) {
217            if (source == null) {
218                throw new IllegalArgumentException("invalid (null) source");
219            }
220
221            Properties newProperties = source.loadProperties();
222
223            for (Iterator i = newProperties.keySet().iterator(); i.hasNext();) {
224                String key = (String) i.next();
225                setProperty(source, key, newProperties.getProperty(key));
226            }
227        }
228
229        /**
230         * Removes all properties from this container.
231         */
232        public void clearProperties() {
233            this.heldProperties.clear();
234        }
235
236
237        /**
238         * @return iterator over the keys of all properties in this container
239         */
240        public Iterator getKeys() {
241            return this.heldProperties.keySet().iterator();
242        }
243
244
245        /**
246         * @param key
247         * @throws IllegalArgumentException if the given key is null
248         */
249        private void validateKey(String key) {
250            if (key == null) {
251                throw new IllegalArgumentException("invalid (null) key");
252            }
253        }
254
255        /**
256         * @param value
257         * @throws IllegalArgumentException if the given value is null
258         */
259        private void validateValue(String value) {
260            if (value == null) {
261                throw new IllegalArgumentException("invalid (null) value");
262            }
263        }
264
265
266        public Properties getHeldProperties() {
267            return heldProperties;
268        }
269
270
271        public void setHeldProperties(Properties heldProperties) {
272            this.heldProperties = heldProperties;
273        }
274    }
275
276    /**
277     * This class is used to obtain properties from a properites file.
278     *
279     *
280     */
281    static class FilePropertySource implements PropertySource {
282        private static Log log = LogFactory.getLog(FilePropertySource.class);
283
284
285        private String fileName;
286        private boolean allowOverrides;
287
288        /**
289         * Set source fileName.
290         *
291         * @param fileName
292         */
293        public void setFileName(String fileName) {
294            this.fileName = fileName;
295        }
296
297        /**
298         * @return source fileName
299         */
300        public String getFileName() {
301            return this.fileName;
302        }
303
304        public boolean isAllowOverrides() {
305            return this.allowOverrides;
306        }
307
308        public void setAllowOverrides(boolean allowOverrides) {
309            this.allowOverrides = allowOverrides;
310        }
311
312        /**
313         * Attempts to load properties from a properties file which has the current fileName and is located on the classpath.
314         *
315         * @see org.kuali.rice.krad.service.impl.ConfigurationServiceImpl.PropertySource#loadProperties()
316         * @throws IllegalStateException if the fileName is null or empty
317         */
318        public Properties loadProperties() {
319            if (StringUtils.isBlank(getFileName())) {
320                throw new IllegalStateException("invalid (blank) fileName");
321            }
322
323            Properties properties = new Properties();
324
325            ClassLoader loader = Thread.currentThread().getContextClassLoader();
326            URL url = loader.getResource(getFileName());
327            if (url == null) {
328                throw new PropertiesException("unable to locate properties file '" + getFileName() + "'");
329            }
330
331            InputStream in = null;
332
333            try {
334                in = url.openStream();
335                properties.load(in);
336            }
337            catch (IOException e) {
338                throw new PropertiesException("error loading from properties file '" + getFileName() + "'", e);
339            }
340            finally {
341                if (in != null) {
342                    try {
343                        in.close();
344                    }
345                    catch (IOException e) {
346                        log.error("caught exception closing InputStream: " + e);
347                    }
348
349                }
350            }
351
352            return properties;
353        }
354
355    }
356}