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; 023 024import org.apache.commons.beanutils.DynaBean; 025import org.apache.commons.beanutils.DynaClass; 026import org.apache.commons.configuration2.Configuration; 027import org.apache.commons.configuration2.ConfigurationMap; 028import org.apache.commons.configuration2.SubsetConfiguration; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032/** 033 * The {@code ConfigurationDynaBean} dynamically reads and writes 034 * configurations properties from a wrapped configuration-collection 035 * {@link org.apache.commons.configuration2.Configuration} instance. It also 036 * implements a {@link java.util.Map} interface so that it can be used in 037 * JSP 2.0 Expression Language expressions. 038 * 039 * <p>The {@code ConfigurationDynaBean} maps nested and mapped properties 040 * to the appropriate {@code Configuration} subset using the 041 * {@link org.apache.commons.configuration2.Configuration#subset} 042 * method. Similarly, indexed properties reference lists of configuration 043 * properties using the 044 * {@link org.apache.commons.configuration2.Configuration#getList(String)} 045 * method. Setting an indexed property is supported, too.</p> 046 * 047 * <p>Note: Some of the methods expect that a dot (".") is used as 048 * property delimiter for the wrapped configuration. This is true for most of 049 * the default configurations. Hierarchical configurations, for which a specific 050 * expression engine is set, may cause problems.</p> 051 * 052 * @author <a href="mailto:ricardo.gladwell@btinternet.com">Ricardo Gladwell</a> 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 086 if (value == null) 087 { 088 throw new NullPointerException("Error trying to set property to null."); 089 } 090 091 if (value instanceof Collection) 092 { 093 final Collection<?> collection = (Collection<?>) value; 094 for (final Object v : collection) 095 { 096 getConfiguration().addProperty(name, v); 097 } 098 } 099 else if (value.getClass().isArray()) 100 { 101 final int length = Array.getLength(value); 102 for (int i = 0; i < length; i++) 103 { 104 getConfiguration().addProperty(name, Array.get(value, i)); 105 } 106 } 107 else 108 { 109 getConfiguration().setProperty(name, value); 110 } 111 } 112 113 @Override 114 public Object get(final String name) 115 { 116 if (LOG.isTraceEnabled()) 117 { 118 LOG.trace("get(" + name + ")"); 119 } 120 121 // get configuration property 122 Object result = getConfiguration().getProperty(name); 123 if (result == null) 124 { 125 // otherwise attempt to create bean from configuration subset 126 final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER); 127 if (!subset.isEmpty()) 128 { 129 result = new ConfigurationDynaBean(subset); 130 } 131 } 132 133 if (LOG.isDebugEnabled()) 134 { 135 LOG.debug(name + "=[" + result + "]"); 136 } 137 138 if (result == null) 139 { 140 throw new IllegalArgumentException("Property '" + name + "' does not exist."); 141 } 142 return result; 143 } 144 145 @Override 146 public boolean contains(final String name, final String key) 147 { 148 final Configuration subset = getConfiguration().subset(name); 149 if (subset == null) 150 { 151 throw new IllegalArgumentException("Mapped property '" + name + "' does not exist."); 152 } 153 154 return subset.containsKey(key); 155 } 156 157 @Override 158 public Object get(final String name, final int index) 159 { 160 if (!checkIndexedProperty(name)) 161 { 162 throw new IllegalArgumentException("Property '" + name 163 + "' is not indexed."); 164 } 165 166 final List<Object> list = getConfiguration().getList(name); 167 return list.get(index); 168 } 169 170 @Override 171 public Object get(final String name, final String key) 172 { 173 final Configuration subset = getConfiguration().subset(name); 174 if (subset == null) 175 { 176 throw new IllegalArgumentException("Mapped property '" + name + "' does not exist."); 177 } 178 179 return subset.getProperty(key); 180 } 181 182 @Override 183 public DynaClass getDynaClass() 184 { 185 return new ConfigurationDynaClass(getConfiguration()); 186 } 187 188 @Override 189 public void remove(final String name, final String key) 190 { 191 final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER); 192 subset.setProperty(key, null); 193 } 194 195 @Override 196 public void set(final String name, final int index, final Object value) 197 { 198 if (!checkIndexedProperty(name) && index > 0) 199 { 200 throw new IllegalArgumentException("Property '" + name 201 + "' is not indexed."); 202 } 203 204 final Object property = getConfiguration().getProperty(name); 205 206 if (property instanceof List) 207 { 208 // This is safe because multiple values of a configuration property 209 // are always stored as lists of type Object. 210 @SuppressWarnings("unchecked") 211 final 212 List<Object> list = (List<Object>) property; 213 list.set(index, value); 214 getConfiguration().setProperty(name, list); 215 } 216 else if (property.getClass().isArray()) 217 { 218 Array.set(property, index, value); 219 } 220 else if (index == 0) 221 { 222 getConfiguration().setProperty(name, value); 223 } 224 } 225 226 @Override 227 public void set(final String name, final String key, final Object value) 228 { 229 getConfiguration().setProperty(name + "." + key, value); 230 } 231 232 /** 233 * Tests whether the given name references an indexed property. This 234 * implementation tests for properties of type list or array. If the 235 * property does not exist, an exception is thrown. 236 * 237 * @param name the name of the property to check 238 * @return a flag whether this is an indexed property 239 * @throws IllegalArgumentException if the property does not exist 240 */ 241 private boolean checkIndexedProperty(final String name) 242 { 243 final Object property = getConfiguration().getProperty(name); 244 245 if (property == null) 246 { 247 throw new IllegalArgumentException("Property '" + name 248 + "' does not exist."); 249 } 250 251 return (property instanceof List) || property.getClass().isArray(); 252 } 253}