001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.commons.config;
021
022import java.awt.Color;
023import java.awt.Font;
024import java.util.Enumeration;
025import java.util.Iterator;
026import java.util.Map;
027import java.util.Properties;
028import java.util.StringTokenizer;
029
030import com.google.common.collect.Maps;
031
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import org.apache.isis.core.commons.debug.DebugBuilder;
036import org.apache.isis.core.commons.exceptions.IsisException;
037import org.apache.isis.core.commons.resource.ResourceStreamSource;
038
039public class IsisConfigurationDefault implements IsisConfiguration {
040    
041    private static final Logger LOG = LoggerFactory.getLogger(IsisConfigurationDefault.class);
042    private final Properties properties = new Properties();
043    private final ResourceStreamSource resourceStreamSource;
044
045    // ////////////////////////////////////////////////
046    // Constructor
047    // ////////////////////////////////////////////////
048
049    public IsisConfigurationDefault() {
050        this(null);
051    }
052
053    public IsisConfigurationDefault(final ResourceStreamSource resourceStreamSource) {
054        this.resourceStreamSource = resourceStreamSource;
055        LOG.debug("configuration initialised with stream: " + nameOf(resourceStreamSource));
056    }
057
058    private String nameOf(final ResourceStreamSource resourceStreamSource) {
059        return resourceStreamSource != null ? resourceStreamSource.getName() : null;
060    }
061
062    // ////////////////////////////////////////////////
063    // ResourceStreamSource
064    // ////////////////////////////////////////////////
065
066    @Override
067    public ResourceStreamSource getResourceStreamSource() {
068        return resourceStreamSource;
069    }
070
071    // ////////////////////////////////////////////////
072    // add
073    // ////////////////////////////////////////////////
074
075    /**
076     * How to handle the case when the configuration already contains the key being added.
077     */
078    public enum ContainsPolicy {
079        /**
080         * If the configuration already contains the key, then ignore the new value.
081         */
082        IGNORE,
083        /**
084         * If the configuration already contains the key, then overwrite with the new.
085         */
086        OVERWRITE,
087        /**
088         * If the configuration already contains the key, then throw an exception.
089         */
090        EXCEPTION
091    }
092    
093    /**
094     * Add the properties from an existing Properties object; if the key exists in the configuration then will be ignored.
095     * 
096     * @see #add(Properties, ContainsPolicy)
097     * @see #put(Properties)
098     */
099    public void add(final Properties properties) {
100        add(properties, ContainsPolicy.IGNORE);
101    }
102    
103    /**
104     * Add the properties from an existing Properties object; if the key exists in the configuration then will be overwritten.
105     * 
106     * @see #add(Properties)
107     * @see #add(Properties, ContainsPolicy)
108     */
109    public void put(final Properties properties) {
110        add(properties, ContainsPolicy.OVERWRITE);
111    }
112    
113    /**
114     * Add the properties from an existing Properties object; if the key exists in the configuration then the
115     * {@link ContainsPolicy} will be applied.
116     * 
117     * @see #add(Properties)
118     * @see #put(Properties)
119     */
120    public void add(final Properties properties, final ContainsPolicy policy) {
121        for(Object key: properties.keySet()) {
122            Object value = properties.get(key);
123            add((String)key, (String)value);
124        }
125    }
126    
127    /**
128     * Adds a key-value pair to this set of properties; if the key exists in the configuration then will be ignored.
129     * 
130     * <p>
131     * @see #add(String, String, ContainsPolicy)
132     * @see #put(String, String)
133     */
134    public void add(final String key, final String value) {
135        add(key, value, ContainsPolicy.IGNORE);
136    }
137
138    /**
139     * Adds a key-value pair to this set of properties; if the key exists in the configuration then will be replaced.
140     * 
141     * <p>
142     * @see #add(String, String)
143     * @see #add(String, String, ContainsPolicy)
144     */
145    public void put(final String key, final String value) {
146        add(key, value, ContainsPolicy.OVERWRITE);
147    }
148
149    /**
150     * Adds a key-value pair to this set of properties; if the key exists in the configuration then the
151     * {@link ContainsPolicy} will be applied.
152     * 
153     * @see #add(String, String)
154     * @see #put(String, String)
155     */
156    public void add(final String key, final String value, final ContainsPolicy policy) {
157        if (value == null) {
158            LOG.debug("ignoring " + key + " as value is null");
159            return;
160        }
161        if (key == null) {
162            return;
163        }
164        if (properties.containsKey(key)) {
165            switch (policy) {
166            case IGNORE:
167                LOG.info("ignoring " + key + "=" + value + " as value already set (with " + properties.get(key) + ")" );
168                break;
169            case OVERWRITE:
170                LOG.info("overwriting " + key + "=" + value + " (previous value was " + properties.get(key) + ")" );
171                properties.put(key, value);
172                break;
173            case EXCEPTION:
174                throw new IllegalStateException("Configuration already has a key " + key + ", value of " + properties.get(key) );
175            }
176        } else {
177            LOG.info("adding " + key + "=" + value);
178            properties.put(key, value);
179        }
180    }
181
182    @Override
183    public IsisConfiguration createSubset(final String prefix) {
184        final IsisConfigurationDefault subset = new IsisConfigurationDefault(resourceStreamSource);
185
186        String startsWith = prefix;
187        if (!startsWith.endsWith(".")) {
188            startsWith = startsWith + '.';
189        }
190        final int prefixLength = startsWith.length();
191
192        for(Object keyObj: properties.keySet()) {
193            final String key = (String)keyObj;
194            if (key.startsWith(startsWith)) {
195                final String modifiedKey = key.substring(prefixLength);
196                subset.properties.put(modifiedKey, properties.get(key));
197            }
198        }
199        return subset;
200    }
201
202    // ////////////////////////////////////////////////
203    // getXxx
204    // ////////////////////////////////////////////////
205
206    /**
207     * Gets the boolean value for the specified name where no value or 'on' will
208     * result in true being returned; anything gives false. If no boolean
209     * property is specified with this name then false is returned.
210     * 
211     * @param name
212     *            the property name
213     */
214    @Override
215    public boolean getBoolean(final String name) {
216        return getBoolean(name, false);
217    }
218
219    /**
220     * Gets the boolean value for the specified name. If no property is
221     * specified with this name then the specified default boolean value is
222     * returned.
223     * 
224     * @param name
225     *            the property name
226     * @param defaultValue
227     *            the value to use as a default
228     */
229    @Override
230    public boolean getBoolean(final String name, final boolean defaultValue) {
231        String value = getProperty(name);
232        if (value == null) {
233            return defaultValue;
234        }
235        value = value.toLowerCase();
236        if (value.equals("on") || value.equals("yes") || value.equals("true") || value.equals("")) {
237            return true;
238        }
239        if (value.equals("off") || value.equals("no") || value.equals("false")) {
240            return false;
241        }
242
243        throw new IsisConfigurationException("Illegal flag for " + name + "; must be one of on, off, yes, no, true or false");
244    }
245
246    /**
247     * Gets the color for the specified name. If no color property is specified
248     * with this name then null is returned.
249     * 
250     * @param name
251     *            the property name
252     */
253    @Override
254    public Color getColor(final String name) {
255        return getColor(name, null);
256    }
257
258    /**
259     * Gets the color for the specified name. If no color property is specified
260     * with this name then the specified default color is returned.
261     * 
262     * @param name
263     *            the property name
264     * @param defaultValue
265     *            the value to use as a default
266     */
267    @Override
268    public Color getColor(final String name, final Color defaultValue) {
269        final String color = getProperty(name);
270
271        if (color == null) {
272            return defaultValue;
273        }
274
275        return Color.decode(color);
276    }
277
278    @Override
279    public void debugData(final DebugBuilder str) {
280        str.appendln("Resource Stream Source", resourceStreamSource);
281        str.appendln();
282        final Enumeration<?> names = properties.propertyNames();
283        while (names.hasMoreElements()) {
284            final String name = (String) names.nextElement();
285            str.appendln(name, properties.getProperty(name));
286        }
287    }
288
289    @Override
290    public String debugTitle() {
291        return "Properties Configuration";
292    }
293
294    /**
295     * Gets the font for the specified name. If no font property is specified
296     * with this name then null is returned.
297     * 
298     * @param name
299     *            the property name
300     */
301    @Override
302    public Font getFont(final String name) {
303        return getFont(name, null);
304    }
305
306    /**
307     * Gets the font for the specified name. If no font property is specified
308     * with this name then the specified default font is returned.
309     * 
310     * @param name
311     *            the property name
312     * @param defaultValue
313     *            the color to use as a default
314     */
315    @Override
316    public Font getFont(final String name, final Font defaultValue) {
317        final String font = getProperty(name);
318
319        if (font == null) {
320            return defaultValue;
321        }
322
323        return Font.decode(font);
324    }
325
326    /**
327     * Gets the number value for the specified name. If no property is specified
328     * with this name then 0 is returned.
329     * 
330     * @param name
331     *            the property name
332     */
333    @Override
334    public int getInteger(final String name) {
335        return getInteger(name, 0);
336    }
337
338    /**
339     * Gets the number value for the specified name. If no property is specified
340     * with this name then the specified default number value is returned.
341     * 
342     * @param name
343     *            the property name
344     * @param defaultValue
345     *            the value to use as a default
346     */
347    @Override
348    public int getInteger(final String name, final int defaultValue) {
349        final String value = getProperty(name);
350
351        if (value == null) {
352            return defaultValue;
353        }
354
355        return Integer.valueOf(value).intValue();
356    }
357
358    @Override
359    public String[] getList(final String name) {
360        final String list = getString(name);
361        if (list == null) {
362            return new String[0];
363        } else {
364            final StringTokenizer tokens = new StringTokenizer(list, ConfigurationConstants.LIST_SEPARATOR);
365            final String array[] = new String[tokens.countTokens()];
366            int i = 0;
367            while (tokens.hasMoreTokens()) {
368                array[i++] = tokens.nextToken().trim();
369            }
370            return array;
371        }
372    }
373
374    @Override
375    public IsisConfiguration getProperties(final String withPrefix) {
376        final int prefixLength = "".length();
377
378        final Properties pp = new Properties();
379        final Enumeration<?> e = properties.keys();
380        while (e.hasMoreElements()) {
381            final String key = (String) e.nextElement();
382            if (key.startsWith(withPrefix)) {
383                final String modifiedKey = key.substring(prefixLength);
384                pp.put(modifiedKey, properties.get(key));
385            }
386        }
387        final IsisConfigurationDefault isisConfigurationDefault = new IsisConfigurationDefault(resourceStreamSource);
388        isisConfigurationDefault.add(pp);
389        return isisConfigurationDefault;
390    }
391
392    private String getProperty(final String name) {
393        return getProperty(name, null);
394    }
395
396    private String getProperty(final String name, final String defaultValue) {
397        final String key = referedToAs(name);
398        if (key.indexOf("..") >= 0) {
399            throw new IsisException("property names should not have '..' within them: " + name);
400        }
401        String property = properties.getProperty(key, defaultValue);
402        property = property != null ? property.trim() : null;
403        LOG.debug("get property: '" + key + "' =  '" + property + "'");
404        return property;
405    }
406
407    /**
408     * Returns the configuration property with the specified name. If there is
409     * no matching property then null is returned.
410     */
411    @Override
412    public String getString(final String name) {
413        return getProperty(name);
414    }
415
416    @Override
417    public String getString(final String name, final String defaultValue) {
418        return getProperty(name, defaultValue);
419    }
420
421    @Override
422    public boolean hasProperty(final String name) {
423        final String key = referedToAs(name);
424        return properties.containsKey(key);
425    }
426
427    @Override
428    public boolean isEmpty() {
429        return properties.isEmpty();
430    }
431
432    @Override
433    public Iterator<String> iterator() {
434        return properties.stringPropertyNames().iterator();
435    }
436
437    /**
438     * Returns as a String that the named property is refered to as. For example
439     * in a simple properties file the property z might be specified in the file
440     * as x.y.z.
441     */
442    private String referedToAs(final String name) {
443        return name;
444    }
445
446    @Override
447    public int size() {
448        return properties.size();
449    }
450
451    @Override
452    public String toString() {
453        return "ConfigurationParameters [properties=" + properties + "]";
454    }
455
456    // ////////////////////////////////////////////////////////////////////
457    // injectInto
458    // ////////////////////////////////////////////////////////////////////
459
460    @Override
461    public void injectInto(final Object candidate) {
462        if (IsisConfigurationAware.class.isAssignableFrom(candidate.getClass())) {
463            final IsisConfigurationAware cast = IsisConfigurationAware.class.cast(candidate);
464            cast.setConfiguration(this);
465        }
466    }
467
468    @Override
469    public Map<String,String> asMap() {
470        final Map<String, String> map = Maps.newHashMap();
471        for(String propertyName: this) {
472            final String propertyValue = this.getProperty(propertyName);
473            map.put(propertyName, propertyValue);
474        }
475        return map;
476    }
477
478}