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.beanutils; 019 020import java.beans.PropertyDescriptor; 021import java.lang.ref.Reference; 022import java.lang.ref.SoftReference; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.Map; 028import java.util.Set; 029import java.util.WeakHashMap; 030 031/** 032 * <p>Implements <code>DynaClass</code> for DynaBeans that wrap 033 * standard JavaBean instances.</p> 034 * 035 * <p> 036 * It is suggested that this class should not usually need to be used directly 037 * to create new <code>WrapDynaBean</code> instances. 038 * It's usually better to call the <code>WrapDynaBean</code> constructor directly. 039 * For example:</p> 040 * <pre> 041 * Object javaBean = ...; 042 * DynaBean wrapper = new WrapDynaBean(javaBean); 043 * </pre> 044 */ 045public class WrapDynaClass implements DynaClass { 046 047 /** 048 * A class representing the combined key for the cache of {@code WrapDynaClass} 049 * instances. A single key consists of a bean class and an instance of 050 * {@code PropertyUtilsBean}. Instances are immutable. 051 */ 052 private static class CacheKey { 053 /** The bean class. */ 054 private final Class<?> beanClass; 055 056 /** The instance of PropertyUtilsBean. */ 057 private final PropertyUtilsBean propUtils; 058 059 /** 060 * Creates a new instance of {@code CacheKey}. 061 * 062 * @param beanCls the bean class 063 * @param pu the instance of {@code PropertyUtilsBean} 064 */ 065 public CacheKey(final Class<?> beanCls, final PropertyUtilsBean pu) { 066 beanClass = beanCls; 067 propUtils = pu; 068 } 069 070 @Override 071 public boolean equals(final Object obj) { 072 if (this == obj) { 073 return true; 074 } 075 if (!(obj instanceof CacheKey)) { 076 return false; 077 } 078 079 final CacheKey c = (CacheKey) obj; 080 return beanClass.equals(c.beanClass) && propUtils.equals(c.propUtils); 081 } 082 083 @Override 084 public int hashCode() { 085 final int factor = 31; 086 int result = 17; 087 result = factor * beanClass.hashCode() + result; 088 return factor * propUtils.hashCode() + result; 089 } 090 } 091 092 private static final ContextClassLoaderLocal<Map<CacheKey, WrapDynaClass>> CLASSLOADER_CACHE = 093 new ContextClassLoaderLocal<Map<CacheKey, WrapDynaClass>>() { 094 @Override 095 protected Map<CacheKey, WrapDynaClass> initialValue() { 096 return new WeakHashMap<>(); 097 } 098 }; 099 100 /** 101 * The set of <code>WrapDynaClass</code> instances that have ever been 102 * created, keyed by the underlying bean Class. The keys to this map 103 * are Class objects, and the values are corresponding WrapDynaClass 104 * objects. 105 * <p> 106 * This static variable is safe even when this code is deployed via a 107 * shared classloader because it is keyed via a Class object. The same 108 * class loaded via two different classloaders will result in different 109 * entries in this map. 110 * <p> 111 * Note, however, that this HashMap can result in a memory leak. When 112 * this class is in a shared classloader it will retain references to 113 * classes loaded via a webapp classloader even after the webapp has been 114 * undeployed. That will prevent the entire classloader and all the classes 115 * it refers to and all their static members from being freed. 116 * 117 ************* !!!!!!!!!!!! PLEASE NOTE !!!!!!!!!!!! ************* 118 * 119 * THE FOLLOWING IS A NASTY HACK TO SO THAT BEANUTILS REMAINS BINARY 120 * COMPATIBLE WITH PREVIOUS RELEASES. 121 * 122 * There are two issues here: 123 * 124 * 1) Memory Issues: The static HashMap caused memory problems (See BEANUTILS-59) 125 * to resolve this it has been moved into a ContextClassLoaderLocal instance 126 * (named CLASSLOADER_CACHE above) which holds one copy per 127 * ClassLoader in a WeakHashMap. 128 * 129 * 2) Binary Compatibility: As the "dynaClasses" static HashMap is "protected" 130 * removing it breaks BeanUtils binary compatibility with previous versions. 131 * To resolve this all the methods have been overriden to delegate to the 132 * Map for the ClassLoader in the ContextClassLoaderLocal. 133 * 134 * @deprecated The dynaClasses Map will be removed in a subsequent release 135 */ 136 @Deprecated 137 protected static HashMap<Object, Object> dynaClasses = new HashMap<Object, Object>() { 138 private static final long serialVersionUID = 1L; 139 @Override 140 public void clear() { 141 getDynaClassesMap().clear(); 142 } 143 @Override 144 public boolean containsKey(final Object key) { 145 return getDynaClassesMap().containsKey(key); 146 } 147 @Override 148 public boolean containsValue(final Object value) { 149 return getDynaClassesMap().containsValue(value); 150 } 151 @Override 152 public Set<Map.Entry<Object, Object>> entrySet() { 153 return getDynaClassesMap().entrySet(); 154 } 155 @Override 156 public boolean equals(final Object o) { 157 return getDynaClassesMap().equals(o); 158 } 159 @Override 160 public Object get(final Object key) { 161 return getDynaClassesMap().get(key); 162 } 163 @Override 164 public int hashCode() { 165 return getDynaClassesMap().hashCode(); 166 } 167 @Override 168 public boolean isEmpty() { 169 return getDynaClassesMap().isEmpty(); 170 } 171 @Override 172 public Set<Object> keySet() { 173 // extract the classes from the key to stay backwards compatible 174 final Set<Object> result = new HashSet<>(); 175 for (final CacheKey k : getClassesCache().keySet()) { 176 result.add(k.beanClass); 177 } 178 return result; 179 } 180 @Override 181 public Object put(final Object key, final Object value) { 182 return getClassesCache().put( 183 new CacheKey((Class<?>) key, PropertyUtilsBean.getInstance()), 184 (WrapDynaClass) value); 185 } 186 @Override 187 public void putAll(final Map<? extends Object, ? extends Object> m) { 188 for (final Map.Entry<? extends Object, ? extends Object> e : m.entrySet()) { 189 put(e.getKey(), e.getValue()); 190 } 191 } 192 @Override 193 public Object remove(final Object key) { 194 return getDynaClassesMap().remove(key); 195 } 196 @Override 197 public int size() { 198 return getDynaClassesMap().size(); 199 } 200 @Override 201 public Collection<Object> values() { 202 return getDynaClassesMap().values(); 203 } 204 }; 205 206 /** 207 * Clear our cache of WrapDynaClass instances. 208 */ 209 public static void clear() { 210 getClassesCache().clear(); 211 } 212 213 /** 214 * Create (if necessary) and return a new <code>WrapDynaClass</code> 215 * instance for the specified bean class. 216 * 217 * @param beanClass Bean class for which a WrapDynaClass is requested 218 * @return A new <em>Wrap</em> {@link DynaClass} 219 */ 220 public static WrapDynaClass createDynaClass(final Class<?> beanClass) { 221 return createDynaClass(beanClass, null); 222 } 223 224 /** 225 * Create (if necessary) and return a new {@code WrapDynaClass} instance 226 * for the specified bean class using the given {@code PropertyUtilsBean} 227 * instance for introspection. Using this method a specially configured 228 * {@code PropertyUtilsBean} instance can be hooked into the introspection 229 * mechanism of the managed bean. The argument is optional; if no 230 * {@code PropertyUtilsBean} object is provided, the default instance is used. 231 * @param beanClass Bean class for which a WrapDynaClass is requested 232 * @param pu the optional {@code PropertyUtilsBean} to be used for introspection 233 * @return A new <em>Wrap</em> {@link DynaClass} 234 * @since 1.9 235 */ 236 public static WrapDynaClass createDynaClass(final Class<?> beanClass, final PropertyUtilsBean pu) { 237 final PropertyUtilsBean propUtils = pu != null ? pu : PropertyUtilsBean.getInstance(); 238 final CacheKey key = new CacheKey(beanClass, propUtils); 239 WrapDynaClass dynaClass = getClassesCache().get(key); 240 if (dynaClass == null) { 241 dynaClass = new WrapDynaClass(beanClass, propUtils); 242 getClassesCache().put(key, dynaClass); 243 } 244 return dynaClass; 245 } 246 247 /** 248 * Returns the cache for the already created class instances. For each 249 * combination of bean class and {@code PropertyUtilsBean} instance an 250 * entry is created in the cache. 251 * @return the cache for already created {@code WrapDynaClass} instances 252 */ 253 private static Map<CacheKey, WrapDynaClass> getClassesCache() { 254 return CLASSLOADER_CACHE.get(); 255 } 256 257 /** 258 * Get the wrap dyna classes cache. Note: This method only exists to 259 * satisfy the deprecated {@code dynaClasses} hash map. 260 */ 261 @SuppressWarnings("unchecked") 262 private static Map<Object, Object> getDynaClassesMap() { 263 @SuppressWarnings("rawtypes") 264 final 265 Map cache = CLASSLOADER_CACHE.get(); 266 return cache; 267 } 268 269 /** 270 * Name of the JavaBean class represented by this WrapDynaClass. 271 */ 272 private final String beanClassName; 273 274 /** 275 * Reference to the JavaBean class represented by this WrapDynaClass. 276 */ 277 private final Reference<Class<?>> beanClassRef; 278 279 /** Stores the associated {@code PropertyUtilsBean} instance. */ 280 private final PropertyUtilsBean propertyUtilsBean; 281 282 /** 283 * The JavaBean <code>Class</code> which is represented by this 284 * <code>WrapDynaClass</code>. 285 * 286 * @deprecated No longer initialized, use getBeanClass() method instead 287 */ 288 @Deprecated 289 protected Class<?> beanClass; 290 291 /** 292 * The set of PropertyDescriptors for this bean class. 293 */ 294 protected PropertyDescriptor[] descriptors; 295 296 /** 297 * The set of PropertyDescriptors for this bean class, keyed by the 298 * property name. Individual descriptor instances will be the same 299 * instances as those in the <code>descriptors</code> list. 300 */ 301 protected HashMap<String, PropertyDescriptor> descriptorsMap = new HashMap<>(); 302 303 /** 304 * The set of dynamic properties that are part of this DynaClass. 305 */ 306 protected DynaProperty[] properties; 307 308 /** 309 * The set of dynamic properties that are part of this DynaClass, 310 * keyed by the property name. Individual descriptor instances will 311 * be the same instances as those in the <code>properties</code> list. 312 */ 313 protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>(); 314 315 /** 316 * Construct a new WrapDynaClass for the specified JavaBean class. This 317 * constructor is private; WrapDynaClass instances will be created as 318 * needed via calls to the <code>createDynaClass(Class)</code> method. 319 * 320 * @param beanClass JavaBean class to be introspected around 321 * @param propUtils the {@code PropertyUtilsBean} associated with this class 322 */ 323 private WrapDynaClass(final Class<?> beanClass, final PropertyUtilsBean propUtils) { 324 // Compiler needs generic. 325 this.beanClassRef = new SoftReference<>(beanClass); 326 this.beanClassName = beanClass.getName(); 327 propertyUtilsBean = propUtils; 328 introspect(); 329 } 330 331 /** 332 * Return the class of the underlying wrapped bean. 333 * 334 * @return the class of the underlying wrapped bean 335 * @since 1.8.0 336 */ 337 protected Class<?> getBeanClass() { 338 return beanClassRef.get(); 339 } 340 341 /** 342 * <p>Return an array of <code>ProperyDescriptors</code> for the properties 343 * currently defined in this DynaClass. If no properties are defined, a 344 * zero-length array will be returned.</p> 345 * 346 * <p><strong>FIXME</strong> - Should we really be implementing 347 * <code>getBeanInfo()</code> instead, which returns property descriptors 348 * and a bunch of other stuff?</p> 349 * 350 * @return the set of properties for this DynaClass 351 */ 352 @Override 353 public DynaProperty[] getDynaProperties() { 354 return properties; 355 } 356 357 /** 358 * Return a property descriptor for the specified property, if it exists; 359 * otherwise, return <code>null</code>. 360 * 361 * @param name Name of the dynamic property for which a descriptor 362 * is requested 363 * @return The descriptor for the specified property 364 * @throws IllegalArgumentException if no property name is specified 365 */ 366 @Override 367 public DynaProperty getDynaProperty(final String name) { 368 if (name == null) { 369 throw new IllegalArgumentException 370 ("No property name specified"); 371 } 372 return propertiesMap.get(name); 373 } 374 375 /** 376 * Return the name of this DynaClass (analogous to the 377 * <code>getName()</code> method of <code>java.lang.Class</code>), which 378 * allows the same <code>DynaClass</code> implementation class to support 379 * different dynamic classes, with different sets of properties. 380 * 381 * @return the name of the DynaClass 382 */ 383 @Override 384 public String getName() { 385 return beanClassName; 386 } 387 388 /** 389 * Return the PropertyDescriptor for the specified property name, if any; 390 * otherwise return <code>null</code>. 391 * 392 * @param name Name of the property to be retrieved 393 * @return The descriptor for the specified property 394 */ 395 public PropertyDescriptor getPropertyDescriptor(final String name) { 396 return descriptorsMap.get(name); 397 } 398 399 /** 400 * Returns the {@code PropertyUtilsBean} instance associated with this class. This 401 * bean is used for introspection. 402 * 403 * @return the associated {@code PropertyUtilsBean} instance 404 * @since 1.9 405 */ 406 protected PropertyUtilsBean getPropertyUtilsBean() { 407 return propertyUtilsBean; 408 } 409 410 /** 411 * Introspect our bean class to identify the supported properties. 412 */ 413 protected void introspect() { 414 // Look up the property descriptors for this bean class 415 final Class<?> beanClass = getBeanClass(); 416 PropertyDescriptor[] regulars = 417 getPropertyUtilsBean().getPropertyDescriptors(beanClass); 418 if (regulars == null) { 419 regulars = new PropertyDescriptor[0]; 420 } 421 @SuppressWarnings("deprecation") 422 Map<?, ?> mappeds = 423 PropertyUtils.getMappedPropertyDescriptors(beanClass); 424 if (mappeds == null) { 425 mappeds = new HashMap<>(); 426 } 427 // Construct corresponding DynaProperty information 428 properties = new DynaProperty[regulars.length + mappeds.size()]; 429 for (int i = 0; i < regulars.length; i++) { 430 descriptorsMap.put(regulars[i].getName(), 431 regulars[i]); 432 properties[i] = 433 new DynaProperty(regulars[i].getName(), 434 regulars[i].getPropertyType()); 435 propertiesMap.put(properties[i].getName(), 436 properties[i]); 437 } 438 int j = regulars.length; 439 final Iterator<?> names = mappeds.keySet().iterator(); 440 while (names.hasNext()) { 441 final String name = (String) names.next(); 442 final PropertyDescriptor descriptor = 443 (PropertyDescriptor) mappeds.get(name); 444 properties[j] = 445 new DynaProperty(descriptor.getName(), 446 Map.class); 447 propertiesMap.put(properties[j].getName(), 448 properties[j]); 449 j++; 450 } 451 } 452 453 /** 454 * <p>Instantiates a new standard JavaBean instance associated with 455 * this DynaClass and return it wrapped in a new WrapDynaBean 456 * instance. <strong>NOTE</strong> the JavaBean should have a 457 * no argument constructor.</p> 458 * <p> 459 * <strong>NOTE</strong> - Most common use cases should not need to use 460 * this method. It is usually better to create new 461 * <code>WrapDynaBean</code> instances by calling its constructor. 462 * For example: 463 * </p> 464 * <pre> 465 * Object javaBean = ...; 466 * DynaBean wrapper = new WrapDynaBean(javaBean); 467 * </pre> 468 * <p> 469 * (This method is needed for some kinds of <code>DynaBean</code> framework.) 470 * </p> 471 * 472 * @return A new <code>DynaBean</code> instance 473 * @throws IllegalAccessException if the Class or the appropriate 474 * constructor is not accessible 475 * @throws InstantiationException if this Class represents an abstract 476 * class, an array class, a primitive type, or void; or if instantiation 477 * fails for some other reason 478 */ 479 @Override 480 public DynaBean newInstance() 481 throws IllegalAccessException, InstantiationException { 482 return new WrapDynaBean(getBeanClass().newInstance()); 483 484 } 485}