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