    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.configuration2.beanutils;
    019
    020import java.lang.reflect.Array;
    021import java.util.Collection;
    022import java.util.List;
    023import java.util.Objects;
    024
    025import org.apache.commons.beanutils.DynaBean;
    026import org.apache.commons.beanutils.DynaClass;
    027import org.apache.commons.configuration2.Configuration;
    028import org.apache.commons.configuration2.ConfigurationMap;
    029import org.apache.commons.configuration2.SubsetConfiguration;
    030import org.apache.commons.logging.Log;
    031import org.apache.commons.logging.LogFactory;
    032
    033/**
    034 * The {@code ConfigurationDynaBean} dynamically reads and writes
    035 * configurations properties from a wrapped configuration-collection
    036 * {@link org.apache.commons.configuration2.Configuration} instance. It also
    037 * implements a {@link java.util.Map} interface so that it can be used in
    038 * JSP 2.0 Expression Language expressions.
    039 *
    040 * <p>The {@code ConfigurationDynaBean} maps nested and mapped properties
    041 * to the appropriate {@code Configuration} subset using the
    042 * {@link org.apache.commons.configuration2.Configuration#subset}
    043 * method. Similarly, indexed properties reference lists of configuration
    044 * properties using the
    045 * {@link org.apache.commons.configuration2.Configuration#getList(String)}
    046 * method. Setting an indexed property is supported, too.</p>
    047 *
    048 * <p>Note: Some of the methods expect that a dot (&quot;.&quot;) is used as
    049 * property delimiter for the wrapped configuration. This is true for most of
    050 * the default configurations. Hierarchical configurations, for which a specific
    051 * expression engine is set, may cause problems.</p>
    052 *
    053 * @since 1.0-rc1
    054 */
    055public class ConfigurationDynaBean extends ConfigurationMap implements DynaBean
    056{
    057    /** Constant for the property delimiter.*/
    058    private static final String PROPERTY_DELIMITER = ".";
    059
    060    /** The logger.*/
    061    private static final Log LOG = LogFactory.getLog(ConfigurationDynaBean.class);
    062
    063    /**
    064     * Creates a new instance of {@code ConfigurationDynaBean} and sets
    065     * the configuration this bean is associated with.
    066     *
    067     * @param configuration the configuration
    068     */
    069    public ConfigurationDynaBean(final Configuration configuration)
    070    {
    071        super(configuration);
    072        if (LOG.isTraceEnabled())
    073        {
    074            LOG.trace("ConfigurationDynaBean(" + configuration + ")");
    075        }
    076    }
    077
    078    @Override
    079    public void set(final String name, final Object value)
    080    {
    081        if (LOG.isTraceEnabled())
    082        {
    083            LOG.trace("set(" + name + "," + value + ")");
    084        }
    085        Objects.requireNonNull(value, "Error trying to set property to null.");
    086
    087        if (value instanceof Collection)
    088        {
    089            final Collection<?> collection = (Collection<?>) value;
    090            for (final Object v : collection)
    091            {
    092                getConfiguration().addProperty(name, v);
    093            }
    094        }
    095        else if (value.getClass().isArray())
    096        {
    097            final int length = Array.getLength(value);
    098            for (int i = 0; i < length; i++)
    099            {
    100                getConfiguration().addProperty(name, Array.get(value, i));
    101            }
    102        }
    103        else
    104        {
    105            getConfiguration().setProperty(name, value);
    106        }
    107    }
    108
    109    @Override
    110    public Object get(final String name)
    111    {
    112        if (LOG.isTraceEnabled())
    113        {
    114            LOG.trace("get(" + name + ")");
    115        }
    116
    117        // get configuration property
    118        Object result = getConfiguration().getProperty(name);
    119        if (result == null)
    120        {
    121            // otherwise attempt to create bean from configuration subset
    122            final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    123            if (!subset.isEmpty())
    124            {
    125                result = new ConfigurationDynaBean(subset);
    126            }
    127        }
    128
    129        if (LOG.isDebugEnabled())
    130        {
    131            LOG.debug(name + "=[" + result + "]");
    132        }
    133
    134        if (result == null)
    135        {
    136            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    137        }
    138        return result;
    139    }
    140
    141    @Override
    142    public boolean contains(final String name, final String key)
    143    {
    144        final Configuration subset = getConfiguration().subset(name);
    145        if (subset == null)
    146        {
    147            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    148        }
    149
    150        return subset.containsKey(key);
    151    }
    152
    153    @Override
    154    public Object get(final String name, final int index)
    155    {
    156        if (!checkIndexedProperty(name))
    157        {
    158            throw new IllegalArgumentException("Property '" + name
    159                    + "' is not indexed.");
    160        }
    161
    162        final List<Object> list = getConfiguration().getList(name);
    163        return list.get(index);
    164    }
    165
    166    @Override
    167    public Object get(final String name, final String key)
    168    {
    169        final Configuration subset = getConfiguration().subset(name);
    170        if (subset == null)
    171        {
    172            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    173        }
    174
    175        return subset.getProperty(key);
    176    }
    177
    178    @Override
    179    public DynaClass getDynaClass()
    180    {
    181        return new ConfigurationDynaClass(getConfiguration());
    182    }
    183
    184    @Override
    185    public void remove(final String name, final String key)
    186    {
    187        final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    188        subset.setProperty(key, null);
    189    }
    190
    191    @Override
    192    public void set(final String name, final int index, final Object value)
    193    {
    194        if (!checkIndexedProperty(name) && index > 0)
    195        {
    196            throw new IllegalArgumentException("Property '" + name
    197                    + "' is not indexed.");
    198        }
    199
    200        final Object property = getConfiguration().getProperty(name);
    201
    202        if (property instanceof List)
    203        {
    204            // This is safe because multiple values of a configuration property
    205            // are always stored as lists of type Object.
    206            @SuppressWarnings("unchecked")
    207            final
    208            List<Object> list = (List<Object>) property;
    209            list.set(index, value);
    210            getConfiguration().setProperty(name, list);
    211        }
    212        else if (property.getClass().isArray())
    213        {
    214            Array.set(property, index, value);
    215        }
    216        else if (index == 0)
    217        {
    218            getConfiguration().setProperty(name, value);
    219        }
    220    }
    221
    222    @Override
    223    public void set(final String name, final String key, final Object value)
    224    {
    225        getConfiguration().setProperty(name + "." + key, value);
    226    }
    227
    228    /**
    229     * Tests whether the given name references an indexed property. This
    230     * implementation tests for properties of type list or array. If the
    231     * property does not exist, an exception is thrown.
    232     *
    233     * @param name the name of the property to check
    234     * @return a flag whether this is an indexed property
    235     * @throws IllegalArgumentException if the property does not exist
    236     */
    237    private boolean checkIndexedProperty(final String name)
    238    {
    239        final Object property = getConfiguration().getProperty(name);
    240
    241        if (property == null)
    242        {
    243            throw new IllegalArgumentException("Property '" + name
    244                    + "' does not exist.");
    245        }
    246
    247        return property instanceof List || property.getClass().isArray();
    248    }
    249}