View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.core.impl.services;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.log4j.Logger;
22  import org.kuali.rice.core.api.config.property.ConfigContext;
23  import org.kuali.rice.core.api.config.property.ConfigurationService;
24  import org.kuali.rice.core.api.exception.RiceRuntimeException;
25  import org.kuali.rice.core.api.util.Truth;
26  
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.net.URL;
30  import java.util.Collections;
31  import java.util.Iterator;
32  import java.util.Map;
33  import java.util.Properties;
34  import java.util.ResourceBundle;
35  
36  /**
37   * Implementation of the {@link org.kuali.rice.core.api.config.property.ConfigurationService} that loads
38   * messages from the configured rice config files and stores them in an internal property holder
39   *
40   * @author Kuali Rice Team (rice.collab@kuali.org)
41   */
42  public class ConfigurationServiceImpl implements ConfigurationService {
43  
44      // TODO - this was copied from KRADConstants to avoid a dependency between KRAD and the core, this needs to be
45      // examined to determine if we really want to be doing something like this in the configuration service
46      private static final String MESSAGE_RESOURCES = "rice.struts.message.resources";
47  
48      private final PropertyHolder propertyHolder = new PropertyHolder();
49  
50      /**
51       * Default constructor
52       */
53      public ConfigurationServiceImpl() {
54          this.propertyHolder.getHeldProperties().putAll(ConfigContext.getCurrentContextConfig().getProperties());
55  
56          // TODO: remove loading of property files once KNS legacy code is removed
57          String propertyConfig = (String) ConfigContext.getCurrentContextConfig().getProperties().get(MESSAGE_RESOURCES);
58          propertyConfig = removeSpacesAround(propertyConfig);
59  
60          String[] bundleNames = StringUtils.split(propertyConfig, ",");
61          for (String bundleName : bundleNames) {
62              ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
63  
64              for (String key : bundle.keySet()) {
65                  String message = bundle.getString(key);
66                  this.propertyHolder.getHeldProperties().put(key, message);
67              }
68          }
69      }
70  
71      /**
72       * @see org.kuali.rice.core.api.config.property.ConfigurationService#getPropertyValueAsString(String)
73       */
74      @Override
75      public String getPropertyValueAsString(String key) {
76          if (key == null) {
77              throw new IllegalArgumentException("invalid (null) key");
78          }
79  
80          return this.propertyHolder.getProperty(key);
81      }
82  
83      /**
84       * @see org.kuali.rice.core.api.config.property.ConfigurationService#getPropertyValueAsBoolean(String)
85       */
86      @Override
87      public boolean getPropertyValueAsBoolean(String key) {
88          if (key == null) {
89              throw new IllegalArgumentException("invalid (null) key");
90          }
91  
92          String property = this.propertyHolder.getProperty(key);
93          Boolean b = Truth.strToBooleanIgnoreCase(property);
94          if (b == null) {
95              return false;
96          }
97  
98          return b;
99      }
100 
101     /**
102      * @see org.kuali.rice.core.api.config.property.ConfigurationService#getAllProperties()
103      */
104     @Override
105     public Map<String, String> getAllProperties() {
106         return (Map) Collections.unmodifiableMap(propertyHolder.getHeldProperties());
107     }
108 
109     /**
110      * Removes the spaces around the elements on a csv list of elements
111      *
112      * <p>
113      * A null input will return a null output.
114      * </p>
115      *
116      * @param csv a list of elements in csv format e.g. foo, bar, baz
117      * @return a list of elements in csv format without spaces e.g. foo,bar,baz
118      */
119     private String removeSpacesAround(String csv) {
120         if (csv == null) {
121             return null;
122         }
123 
124         final StringBuilder result = new StringBuilder();
125         for (final String value : csv.split(",")) {
126             if (!"".equals(value.trim())) {
127                 result.append(value.trim());
128                 result.append(",");
129             }
130         }
131 
132         //remove trailing comma
133         int i = result.lastIndexOf(",");
134         if (i != -1) {
135             result.deleteCharAt(i);
136         }
137 
138         return result.toString();
139     }
140 
141     /**
142      * Interface for a source for properties
143      */
144     protected static interface PropertySource {
145 
146         /**
147          * @return Properties loaded from this PropertySource
148          */
149         public Properties loadProperties();
150     }
151 
152     /**
153      * This class is a Property container. It is able to load properties from various property-sources.
154      */
155     protected static class PropertyHolder {
156         private static Logger LOG = Logger.getLogger(PropertyHolder.class);
157 
158         Properties heldProperties;
159 
160         /**
161          * Default constructor.
162          */
163         public PropertyHolder() {
164             this.heldProperties = new Properties();
165         }
166 
167         /**
168          * @return true if this container currently has no properties
169          */
170         public boolean isEmpty() {
171             return this.heldProperties.isEmpty();
172         }
173 
174         /**
175          * @param key
176          * @return true if a property with the given key exists in this container
177          * @throws IllegalArgumentException if the given key is null
178          */
179         public boolean containsKey(String key) {
180             validateKey(key);
181 
182             return this.heldProperties.containsKey(key);
183         }
184 
185         /**
186          * @param key
187          * @return the current value of the property with the given key, or null if no property exists with that key
188          * @throws IllegalArgumentException if the given key is null
189          */
190         public String getProperty(String key) {
191             validateKey(key);
192 
193             return this.heldProperties.getProperty(key);
194         }
195 
196         /**
197          * Associates the given value with the given key
198          *
199          * @param key
200          * @param value
201          * @throws IllegalArgumentException if the given key is null
202          * @throws IllegalArgumentException if the given value is null
203          */
204         public void setProperty(String key, String value) {
205             setProperty(null, key, value);
206         }
207 
208         /**
209          * Associates the given value with the given key
210          *
211          * @param source
212          * @param key
213          * @param value
214          * @throws IllegalArgumentException if the given key is null
215          * @throws IllegalArgumentException if the given value is null
216          */
217         public void setProperty(PropertySource source, String key, String value) {
218             validateKey(key);
219             validateValue(value);
220 
221             if (containsKey(key)) {
222                 if (source != null && source instanceof FilePropertySource && ((FilePropertySource) source)
223                         .isAllowOverrides()) {
224                     LOG.info("Duplicate Key: Override is enabled [key="
225                             + key
226                             + ", new value="
227                             + value
228                             + ", old value="
229                             + this.heldProperties.getProperty(key)
230                             + "]");
231                 } else {
232                     throw new RiceRuntimeException("duplicate key '" + key + "'");
233                 }
234             }
235             this.heldProperties.setProperty(key, value);
236         }
237 
238         /**
239          * Removes the property with the given key from this container
240          *
241          * @param key
242          * @throws IllegalArgumentException if the given key is null
243          */
244         public void clearProperty(String key) {
245             validateKey(key);
246 
247             this.heldProperties.remove(key);
248         }
249 
250         /**
251          * Copies all name,value pairs from the given PropertySource instance into this container.
252          *
253          * @param source
254          * @throws IllegalStateException if the source is invalid (improperly initialized)
255          * as an existing property
256          */
257         public void loadProperties(PropertySource source) {
258             if (source == null) {
259                 throw new IllegalArgumentException("invalid (null) source");
260             }
261 
262             Properties newProperties = source.loadProperties();
263 
264             for (Iterator i = newProperties.keySet().iterator(); i.hasNext(); ) {
265                 String key = (String) i.next();
266                 setProperty(source, key, newProperties.getProperty(key));
267             }
268         }
269 
270         /**
271          * Removes all properties from this container.
272          */
273         public void clearProperties() {
274             this.heldProperties.clear();
275         }
276 
277         /**
278          * @return iterator over the keys of all properties in this container
279          */
280         public Iterator getKeys() {
281             return this.heldProperties.keySet().iterator();
282         }
283 
284         /**
285          * @param key
286          * @throws IllegalArgumentException if the given key is null
287          */
288         private void validateKey(String key) {
289             if (key == null) {
290                 throw new IllegalArgumentException("invalid (null) key");
291             }
292         }
293 
294         /**
295          * @throws IllegalArgumentException if the given value is null
296          */
297         private void validateValue(String value) {
298             if (value == null) {
299                 throw new IllegalArgumentException("invalid (null) value");
300             }
301         }
302 
303         public Properties getHeldProperties() {
304             return heldProperties;
305         }
306 
307         public void setHeldProperties(Properties heldProperties) {
308             this.heldProperties = heldProperties;
309         }
310     }
311 
312     /**
313      * Used to obtain properties from a properties file
314      */
315     protected static class FilePropertySource implements PropertySource {
316         private static Log log = LogFactory.getLog(FilePropertySource.class);
317 
318         private String fileName;
319         private boolean allowOverrides;
320 
321         public void setFileName(String fileName) {
322             this.fileName = fileName;
323         }
324 
325         public String getFileName() {
326             return this.fileName;
327         }
328 
329         public boolean isAllowOverrides() {
330             return this.allowOverrides;
331         }
332 
333         public void setAllowOverrides(boolean allowOverrides) {
334             this.allowOverrides = allowOverrides;
335         }
336 
337         /**
338          * Attempts to load properties from a properties file which has the current fileName and is located on the
339          * classpath
340          *
341          * @throws IllegalStateException if the fileName is null or empty
342          * @see ConfigurationServiceImpl.PropertySource#loadProperties()
343          */
344         public Properties loadProperties() {
345             if (StringUtils.isBlank(getFileName())) {
346                 throw new IllegalStateException("invalid (blank) fileName");
347             }
348 
349             Properties properties = new Properties();
350 
351             ClassLoader loader = Thread.currentThread().getContextClassLoader();
352             URL url = loader.getResource(getFileName());
353             if (url == null) {
354                 throw new RiceRuntimeException("unable to locate properties file '" + getFileName() + "'");
355             }
356 
357             InputStream in = null;
358 
359             try {
360                 in = url.openStream();
361                 properties.load(in);
362             } catch (IOException e) {
363                 throw new RiceRuntimeException("error loading from properties file '" + getFileName() + "'", e);
364             } finally {
365                 if (in != null) {
366                     try {
367                         in.close();
368                     } catch (IOException e) {
369                         log.error("caught exception closing InputStream: " + e);
370                     }
371 
372                 }
373             }
374 
375             return properties;
376         }
377     }
378 }