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