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 (".") 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}