    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            collection.forEach(v -> getConfiguration().addProperty(name, v));
    084        } else if (value.getClass().isArray()) {
    085            final int length = Array.getLength(value);
    086            for (int i = 0; i < length; i++) {
    087                getConfiguration().addProperty(name, Array.get(value, i));
    088            }
    089        } else {
    090            getConfiguration().setProperty(name, value);
    091        }
    092    }
    093
    094    @Override
    095    public Object get(final String name) {
    096        if (LOG.isTraceEnabled()) {
    097            LOG.trace("get(" + name + ")");
    098        }
    099
    100        // get configuration property
    101        Object result = getConfiguration().getProperty(name);
    102        if (result == null) {
    103            // otherwise attempt to create bean from configuration subset
    104            final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    105            if (!subset.isEmpty()) {
    106                result = new ConfigurationDynaBean(subset);
    107            }
    108        }
    109
    110        if (LOG.isDebugEnabled()) {
    111            LOG.debug(name + "=[" + result + "]");
    112        }
    113
    114        if (result == null) {
    115            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    116        }
    117        return result;
    118    }
    119
    120    @Override
    121    public boolean contains(final String name, final String key) {
    122        final Configuration subset = getConfiguration().subset(name);
    123        if (subset == null) {
    124            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    125        }
    126
    127        return subset.containsKey(key);
    128    }
    129
    130    @Override
    131    public Object get(final String name, final int index) {
    132        if (!checkIndexedProperty(name)) {
    133            throw new IllegalArgumentException("Property '" + name + "' is not indexed.");
    134        }
    135
    136        final List<Object> list = getConfiguration().getList(name);
    137        return list.get(index);
    138    }
    139
    140    @Override
    141    public Object get(final String name, final String key) {
    142        final Configuration subset = getConfiguration().subset(name);
    143        if (subset == null) {
    144            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    145        }
    146
    147        return subset.getProperty(key);
    148    }
    149
    150    @Override
    151    public DynaClass getDynaClass() {
    152        return new ConfigurationDynaClass(getConfiguration());
    153    }
    154
    155    @Override
    156    public void remove(final String name, final String key) {
    157        final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    158        subset.setProperty(key, null);
    159    }
    160
    161    @Override
    162    public void set(final String name, final int index, final Object value) {
    163        if (!checkIndexedProperty(name) && index > 0) {
    164            throw new IllegalArgumentException("Property '" + name + "' is not indexed.");
    165        }
    166
    167        final Object property = getConfiguration().getProperty(name);
    168
    169        if (property instanceof List) {
    170            // This is safe because multiple values of a configuration property
    171            // are always stored as lists of type Object.
    172            @SuppressWarnings("unchecked")
    173            final List<Object> list = (List<Object>) property;
    174            list.set(index, value);
    175            getConfiguration().setProperty(name, list);
    176        } else if (property.getClass().isArray()) {
    177            Array.set(property, index, value);
    178        } else if (index == 0) {
    179            getConfiguration().setProperty(name, value);
    180        }
    181    }
    182
    183    @Override
    184    public void set(final String name, final String key, final Object value) {
    185        getConfiguration().setProperty(name + "." + key, value);
    186    }
    187
    188    /**
    189     * Checks whether the given name references an indexed property. This implementation tests for properties of type list or
    190     * array. If the property does not exist, an exception is thrown.
    191     *
    192     * @param name the name of the property to check
    193     * @return a flag whether this is an indexed property
    194     * @throws IllegalArgumentException if the property does not exist
    195     */
    196    private boolean checkIndexedProperty(final String name) {
    197        final Object property = getConfiguration().getProperty(name);
    198
    199        if (property == null) {
    200            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    201        }
    202
    203        return property instanceof List || property.getClass().isArray();
    204    }
    205}