    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 configurations properties from a wrapped
    035 * configuration-collection {@link org.apache.commons.configuration2.Configuration} instance. It also implements a
    036 * {@link java.util.Map} interface so that it can be used in JSP 2.0 Expression Language expressions.
    037 *
    038 * <p>
    039 * The {@code ConfigurationDynaBean} maps nested and mapped properties to the appropriate {@code Configuration} subset
    040 * using the {@link org.apache.commons.configuration2.Configuration#subset} method. Similarly, indexed properties
    041 * reference lists of configuration properties using the
    042 * {@link org.apache.commons.configuration2.Configuration#getList(String)} method. Setting an indexed property is
    043 * supported, too.
    044 * </p>
    045 *
    046 * <p>
    047 * Note: Some of the methods expect that a dot (&quot;.&quot;) is used as property delimiter for the wrapped
    048 * configuration. This is true for most of the default configurations. Hierarchical configurations, for which a specific
    049 * expression engine is set, may cause problems.
    050 * </p>
    051 *
    052 * @since 1.0-rc1
    053 */
    054public class ConfigurationDynaBean extends ConfigurationMap implements DynaBean {
    055
    056    /** Constant for the property delimiter. */
    057    private static final String PROPERTY_DELIMITER = ".";
    058
    059    /** The logger. */
    060    private static final Log LOG = LogFactory.getLog(ConfigurationDynaBean.class);
    061
    062    /**
    063     * Constructs a new instance of {@code ConfigurationDynaBean} and sets the configuration this bean is associated with.
    064     *
    065     * @param configuration the configuration
    066     */
    067    public ConfigurationDynaBean(final Configuration configuration) {
    068        super(configuration);
    069        if (LOG.isTraceEnabled()) {
    070            LOG.trace("ConfigurationDynaBean(" + configuration + ")");
    071        }
    072    }
    073
    074    @Override
    075    public void set(final String name, final Object value) {
    076        if (LOG.isTraceEnabled()) {
    077            LOG.trace("set(" + name + "," + value + ")");
    078        }
    079        Objects.requireNonNull(value, "Error trying to set property to null.");
    080
    081        if (value instanceof Collection) {
    082            final Collection<?> collection = (Collection<?>) value;
    083            for (final Object v : collection) {
    084                getConfiguration().addProperty(name, v);
    085            }
    086        } else if (value.getClass().isArray()) {
    087            final int length = Array.getLength(value);
    088            for (int i = 0; i < length; i++) {
    089                getConfiguration().addProperty(name, Array.get(value, i));
    090            }
    091        } else {
    092            getConfiguration().setProperty(name, value);
    093        }
    094    }
    095
    096    @Override
    097    public Object get(final String name) {
    098        if (LOG.isTraceEnabled()) {
    099            LOG.trace("get(" + name + ")");
    100        }
    101
    102        // get configuration property
    103        Object result = getConfiguration().getProperty(name);
    104        if (result == null) {
    105            // otherwise attempt to create bean from configuration subset
    106            final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    107            if (!subset.isEmpty()) {
    108                result = new ConfigurationDynaBean(subset);
    109            }
    110        }
    111
    112        if (LOG.isDebugEnabled()) {
    113            LOG.debug(name + "=[" + result + "]");
    114        }
    115
    116        if (result == null) {
    117            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    118        }
    119        return result;
    120    }
    121
    122    @Override
    123    public boolean contains(final String name, final String key) {
    124        final Configuration subset = getConfiguration().subset(name);
    125        if (subset == null) {
    126            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    127        }
    128
    129        return subset.containsKey(key);
    130    }
    131
    132    @Override
    133    public Object get(final String name, final int index) {
    134        if (!checkIndexedProperty(name)) {
    135            throw new IllegalArgumentException("Property '" + name + "' is not indexed.");
    136        }
    137
    138        final List<Object> list = getConfiguration().getList(name);
    139        return list.get(index);
    140    }
    141
    142    @Override
    143    public Object get(final String name, final String key) {
    144        final Configuration subset = getConfiguration().subset(name);
    145        if (subset == null) {
    146            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    147        }
    148
    149        return subset.getProperty(key);
    150    }
    151
    152    @Override
    153    public DynaClass getDynaClass() {
    154        return new ConfigurationDynaClass(getConfiguration());
    155    }
    156
    157    @Override
    158    public void remove(final String name, final String key) {
    159        final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    160        subset.setProperty(key, null);
    161    }
    162
    163    @Override
    164    public void set(final String name, final int index, final Object value) {
    165        if (!checkIndexedProperty(name) && index > 0) {
    166            throw new IllegalArgumentException("Property '" + name + "' is not indexed.");
    167        }
    168
    169        final Object property = getConfiguration().getProperty(name);
    170
    171        if (property instanceof List) {
    172            // This is safe because multiple values of a configuration property
    173            // are always stored as lists of type Object.
    174            @SuppressWarnings("unchecked")
    175            final List<Object> list = (List<Object>) property;
    176            list.set(index, value);
    177            getConfiguration().setProperty(name, list);
    178        } else if (property.getClass().isArray()) {
    179            Array.set(property, index, value);
    180        } else if (index == 0) {
    181            getConfiguration().setProperty(name, value);
    182        }
    183    }
    184
    185    @Override
    186    public void set(final String name, final String key, final Object value) {
    187        getConfiguration().setProperty(name + "." + key, value);
    188    }
    189
    190    /**
    191     * Checks whether the given name references an indexed property. This implementation tests for properties of type list or
    192     * array. If the property does not exist, an exception is thrown.
    193     *
    194     * @param name the name of the property to check
    195     * @return a flag whether this is an indexed property
    196     * @throws IllegalArgumentException if the property does not exist
    197     */
    198    private boolean checkIndexedProperty(final String name) {
    199        final Object property = getConfiguration().getProperty(name);
    200
    201        if (property == null) {
    202            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    203        }
    204
    205        return property instanceof List || property.getClass().isArray();
    206    }
    207}