/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.isis.core.commons.config; import java.awt.Color; import java.awt.Font; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.core.commons.exceptions.IsisException; import org.apache.isis.core.commons.resource.ResourceStreamSource; import org.apache.isis.core.metamodel.services.ServicesInjector; import org.apache.isis.core.metamodel.services.configinternal.ConfigurationServiceInternal; /** * This object will typically be registered as the implementation of the {@link ConfigurationServiceInternal} * (internal) domain service, using * {@link ServicesInjector#addFallbackIfRequired(Class, Object)}. * * <p> * If an integration test is running, then the <code>IsisConfigurationForJdoIntegTests</code> will be used instead. * </p> */ public class IsisConfigurationDefault implements ConfigurationServiceInternal { private static final Logger LOG = LoggerFactory.getLogger(IsisConfigurationDefault.class); private final ResourceStreamSource resourceStreamSource; private final Properties properties = new Properties(); /** * derived lazily from {@link #properties}. */ private Properties applicationProperties; // //////////////////////////////////////////////// // Constructor // //////////////////////////////////////////////// public IsisConfigurationDefault() { this(null); } public IsisConfigurationDefault(final ResourceStreamSource resourceStreamSource) { this.resourceStreamSource = resourceStreamSource; LOG.debug("configuration initialised with stream: " + nameOf(resourceStreamSource)); } private String nameOf(final ResourceStreamSource resourceStreamSource) { return resourceStreamSource != null ? resourceStreamSource.getName() : null; } // //////////////////////////////////////////////// // ResourceStreamSource // //////////////////////////////////////////////// @Override public ResourceStreamSource getResourceStreamSource() { return resourceStreamSource; } // //////////////////////////////////////////////// // add // //////////////////////////////////////////////// /** * How to handle the case when the configuration already contains the key being added. */ public enum ContainsPolicy { /** * If the configuration already contains the key, then ignore the new value. */ IGNORE, /** * If the configuration already contains the key, then overwrite with the new. */ OVERWRITE, /** * If the configuration already contains the key, then throw an exception. */ EXCEPTION } /** * Add the properties from an existing Properties object. */ @Programmatic public void add(final Properties properties, final ContainsPolicy containsPolicy) { for(Object key: properties.keySet()) { Object value = properties.get(key); addPerPolicy((String) key, (String) value, containsPolicy); } } /** * Adds a key-value pair to this set of properties; if the key exists in the configuration then will be ignored. * * <p> * @see #addPerPolicy(String, String, ContainsPolicy) * @see #put(String, String) */ @Programmatic public void add(final String key, final String value) { addPerPolicy(key, value, ContainsPolicy.IGNORE); } /** * Adds a key-value pair to this set of properties; if the key exists in the configuration then will be replaced. * * <p> * @see #add(String, String) * @see #addPerPolicy(String, String, ContainsPolicy) */ @Programmatic public void put(final String key, final String value) { addPerPolicy(key, value, ContainsPolicy.OVERWRITE); } /** * Adds a key-value pair to this set of properties; if the key exists in the configuration then the * {@link ContainsPolicy} will be applied. * * @see #add(String, String) * @see #put(String, String) */ private void addPerPolicy(final String key, final String value, final ContainsPolicy policy) { if (value == null) { LOG.debug("ignoring {} as value is null", key); return; } if (key == null) { return; } if (properties.containsKey(key)) { switch (policy) { case IGNORE: LOG.info("ignoring {}={} as value already set (with {})", key, value, properties.get(key)); break; case OVERWRITE: LOG.info("overwriting {}={} (previous value was {})", key, value, properties.get(key)); properties.put(key, value); break; case EXCEPTION: throw new IllegalStateException(String.format( "Configuration already has a key {}, value of {}%s, value of %s", key, properties.get(key))); } } else { LOG.info("adding {} = {}", key , safe(key, value)); properties.put(key, value); } } static String safe(final String key, final String value) { return Strings.isNullOrEmpty(key) ? value : (key.toLowerCase().contains("password") ? "*******" : value); } @Override public IsisConfiguration createSubset(final String prefix) { final IsisConfigurationDefault subset = new IsisConfigurationDefault(resourceStreamSource); String startsWith = prefix; if (!startsWith.endsWith(".")) { startsWith = startsWith + '.'; } final int prefixLength = startsWith.length(); for(Object keyObj: properties.keySet()) { final String key = (String)keyObj; if (key.startsWith(startsWith)) { final String modifiedKey = key.substring(prefixLength); subset.properties.put(modifiedKey, properties.get(key)); } } return subset; } // //////////////////////////////////////////////// // getXxx // //////////////////////////////////////////////// /** * Gets the boolean value for the specified name where no value or 'on' will * result in true being returned; anything gives false. If no boolean * property is specified with this name then false is returned. * * @param name * the property name */ @Override public boolean getBoolean(final String name) { return getBoolean(name, false); } /** * Gets the boolean value for the specified name. If no property is * specified with this name then the specified default boolean value is * returned. * * @param name * the property name * @param defaultValue * the value to use as a default */ @Override public boolean getBoolean(final String name, final boolean defaultValue) { String value = getPropertyElseNull(name); if (value == null) { return defaultValue; } value = value.toLowerCase(); if (value.equals("on") || value.equals("yes") || value.equals("true") || value.equals("")) { return true; } if (value.equals("off") || value.equals("no") || value.equals("false")) { return false; } throw new IsisConfigurationException("Illegal flag for " + name + "; must be one of on, off, yes, no, true or false"); } /** * Gets the color for the specified name. If no color property is specified * with this name then null is returned. * * @param name * the property name */ @Override public Color getColor(final String name) { return getColor(name, null); } /** * Gets the color for the specified name. If no color property is specified * with this name then the specified default color is returned. * * @param name * the property name * @param defaultValue * the value to use as a default */ @Override public Color getColor(final String name, final Color defaultValue) { final String color = getPropertyElseNull(name); if (color == null) { return defaultValue; } return Color.decode(color); } /** * Gets the font for the specified name. If no font property is specified * with this name then null is returned. * * @param name * the property name */ @Override public Font getFont(final String name) { return getFont(name, null); } /** * Gets the font for the specified name. If no font property is specified * with this name then the specified default font is returned. * * @param name * the property name * @param defaultValue * the color to use as a default */ @Override public Font getFont(final String name, final Font defaultValue) { final String font = getPropertyElseNull(name); if (font == null) { return defaultValue; } return Font.decode(font); } /** * Gets the number value for the specified name. If no property is specified * with this name then 0 is returned. * * @param name * the property name */ @Override public int getInteger(final String name) { return getInteger(name, 0); } /** * Gets the number value for the specified name. If no property is specified * with this name then the specified default number value is returned. * * @param name * the property name * @param defaultValue * the value to use as a default */ @Override public int getInteger(final String name, final int defaultValue) { final String value = getPropertyElseNull(name); if (value == null) { return defaultValue; } return Integer.valueOf(value).intValue(); } @Override public String[] getList(final String name) { final String listAsCommaSeparatedArray = getString(name); return stringAsList(listAsCommaSeparatedArray); } @Override public String[] getList(String name, String defaultListAsCommaSeparatedArray) { final String listAsCommaSeparatedArray = getString(name, defaultListAsCommaSeparatedArray); return stringAsList(listAsCommaSeparatedArray); } private String[] stringAsList(String list) { if (list == null) { return new String[0]; } else { final StringTokenizer tokens = new StringTokenizer(list, ConfigurationConstants.LIST_SEPARATOR); final String array[] = new String[tokens.countTokens()]; int i = 0; while (tokens.hasMoreTokens()) { array[i++] = tokens.nextToken().trim(); } return array; } } @Override public IsisConfiguration getProperties(final String withPrefix) { final int prefixLength = "".length(); final Properties properties = new Properties(); final Enumeration<?> e = this.properties.keys(); while (e.hasMoreElements()) { final String key = (String) e.nextElement(); if (key.startsWith(withPrefix)) { final String modifiedKey = key.substring(prefixLength); properties.put(modifiedKey, this.properties.get(key)); } } final IsisConfigurationDefault isisConfigurationDefault = new IsisConfigurationDefault(resourceStreamSource); isisConfigurationDefault.add(properties, ContainsPolicy.IGNORE); return isisConfigurationDefault; } private String getPropertyElseNull(final String name) { return getProperty(name, null); } private String getProperty(final String name, final String defaultValue) { final String key = referedToAs(name); if (key.indexOf("..") >= 0) { throw new IsisException("property names should not have '..' within them: " + name); } String property = properties.getProperty(key, defaultValue); property = property != null ? property.trim() : null; LOG.debug("get property: '" + key + "' = '" + property + "'"); return property; } /** * Returns the configuration property with the specified name. If there is * no matching property then null is returned. */ @Override public String getString(final String name) { return getPropertyElseNull(name); } @Override public String getString(final String name, final String defaultValue) { return getProperty(name, defaultValue); } @Override public boolean hasProperty(final String name) { final String key = referedToAs(name); return properties.containsKey(key); } @Override public boolean isEmpty() { return properties.isEmpty(); } @Override public Iterator<String> iterator() { return asIterable().iterator(); } @Override public Iterable<String> asIterable() { return properties.stringPropertyNames(); } /** * Returns as a String that the named property is refered to as. For example * in a simple properties file the property z might be specified in the file * as x.y.z. */ private String referedToAs(final String name) { return name; } @Override public int size() { return properties.size(); } @Override public String toString() { return "ConfigurationParameters [properties=" + properties + "]"; } // //////////////////////////////////////////////////////////////////// // injectInto // //////////////////////////////////////////////////////////////////// @Override public Map<String,String> asMap() { final Map<String, String> map = Maps.newHashMap(); for(String propertyName: this.asIterable()) { final String propertyValue = this.getPropertyElseNull(propertyName); map.put(propertyName, propertyValue); } return map; } //region > ConfigurationService impl @Override public String getProperty(final String name) { initAppPropertiesIfRequired(); return applicationProperties.getProperty(name); } private void initAppPropertiesIfRequired() { if(applicationProperties == null) { applicationProperties = deriveApplicationProperties(); } } private Properties deriveApplicationProperties() { final Properties applicationProperties = new Properties(); final IsisConfiguration applicationConfiguration = getProperties("application"); for (final String key : applicationConfiguration.asIterable()) { final String value = applicationConfiguration.getString(key); final String newKey = key.substring("application.".length()); applicationProperties.setProperty(newKey, value); } return applicationProperties; } @Override public List<String> getPropertyNames() { initAppPropertiesIfRequired(); final List<String> list = Lists.newArrayList(); for (final Object key : applicationProperties.keySet()) { list.add((String) key); } return list; } //endregion }