001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans; 018 019import java.beans.PropertyDescriptor; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.security.AccessControlContext; 023import java.security.AccessController; 024import java.security.PrivilegedAction; 025import java.security.PrivilegedActionException; 026import java.security.PrivilegedExceptionAction; 027 028import org.springframework.core.ResolvableType; 029import org.springframework.core.convert.Property; 030import org.springframework.core.convert.TypeDescriptor; 031import org.springframework.util.Assert; 032 033/** 034 * Default {@link BeanWrapper} implementation that should be sufficient 035 * for all typical use cases. Caches introspection results for efficiency. 036 * 037 * <p>Note: Auto-registers default property editors from the 038 * {@code org.springframework.beans.propertyeditors} package, which apply 039 * in addition to the JDK's standard PropertyEditors. Applications can call 040 * the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method 041 * to register an editor for a particular instance (i.e. they are not shared 042 * across the application). See the base class 043 * {@link PropertyEditorRegistrySupport} for details. 044 * 045 * <p><b>NOTE: As of Spring 2.5, this is - for almost all purposes - an 046 * internal class.</b> It is just public in order to allow for access from 047 * other framework packages. For standard application access purposes, use the 048 * {@link PropertyAccessorFactory#forBeanPropertyAccess} factory method instead. 049 * 050 * @author Rod Johnson 051 * @author Juergen Hoeller 052 * @author Rob Harrop 053 * @author Stephane Nicoll 054 * @since 15 April 2001 055 * @see #registerCustomEditor 056 * @see #setPropertyValues 057 * @see #setPropertyValue 058 * @see #getPropertyValue 059 * @see #getPropertyType 060 * @see BeanWrapper 061 * @see PropertyEditorRegistrySupport 062 */ 063public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper { 064 065 /** 066 * Cached introspections results for this object, to prevent encountering 067 * the cost of JavaBeans introspection every time. 068 */ 069 private CachedIntrospectionResults cachedIntrospectionResults; 070 071 /** 072 * The security context used for invoking the property methods. 073 */ 074 private AccessControlContext acc; 075 076 077 /** 078 * Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. 079 * Registers default editors. 080 * @see #setWrappedInstance 081 */ 082 public BeanWrapperImpl() { 083 this(true); 084 } 085 086 /** 087 * Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. 088 * @param registerDefaultEditors whether to register default editors 089 * (can be suppressed if the BeanWrapper won't need any type conversion) 090 * @see #setWrappedInstance 091 */ 092 public BeanWrapperImpl(boolean registerDefaultEditors) { 093 super(registerDefaultEditors); 094 } 095 096 /** 097 * Create a new BeanWrapperImpl for the given object. 098 * @param object the object wrapped by this BeanWrapper 099 */ 100 public BeanWrapperImpl(Object object) { 101 super(object); 102 } 103 104 /** 105 * Create a new BeanWrapperImpl, wrapping a new instance of the specified class. 106 * @param clazz class to instantiate and wrap 107 */ 108 public BeanWrapperImpl(Class<?> clazz) { 109 super(clazz); 110 } 111 112 /** 113 * Create a new BeanWrapperImpl for the given object, 114 * registering a nested path that the object is in. 115 * @param object the object wrapped by this BeanWrapper 116 * @param nestedPath the nested path of the object 117 * @param rootObject the root object at the top of the path 118 */ 119 public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) { 120 super(object, nestedPath, rootObject); 121 } 122 123 /** 124 * Create a new BeanWrapperImpl for the given object, 125 * registering a nested path that the object is in. 126 * @param object the object wrapped by this BeanWrapper 127 * @param nestedPath the nested path of the object 128 * @param parent the containing BeanWrapper (must not be {@code null}) 129 */ 130 private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) { 131 super(object, nestedPath, parent); 132 setSecurityContext(parent.acc); 133 } 134 135 136 /** 137 * Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}. 138 * @param object the actual target object 139 * @since 4.3 140 * @see #setWrappedInstance(Object) 141 */ 142 public void setBeanInstance(Object object) { 143 this.wrappedObject = object; 144 this.rootObject = object; 145 this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject); 146 setIntrospectionClass(object.getClass()); 147 } 148 149 @Override 150 public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { 151 super.setWrappedInstance(object, nestedPath, rootObject); 152 setIntrospectionClass(getWrappedClass()); 153 } 154 155 /** 156 * Set the class to introspect. 157 * Needs to be called when the target object changes. 158 * @param clazz the class to introspect 159 */ 160 protected void setIntrospectionClass(Class<?> clazz) { 161 if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) { 162 this.cachedIntrospectionResults = null; 163 } 164 } 165 166 /** 167 * Obtain a lazily initialized CachedIntrospectionResults instance 168 * for the wrapped object. 169 */ 170 private CachedIntrospectionResults getCachedIntrospectionResults() { 171 Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance"); 172 if (this.cachedIntrospectionResults == null) { 173 this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass()); 174 } 175 return this.cachedIntrospectionResults; 176 } 177 178 /** 179 * Set the security context used during the invocation of the wrapped instance methods. 180 * Can be null. 181 */ 182 public void setSecurityContext(AccessControlContext acc) { 183 this.acc = acc; 184 } 185 186 /** 187 * Return the security context used during the invocation of the wrapped instance methods. 188 * Can be null. 189 */ 190 public AccessControlContext getSecurityContext() { 191 return this.acc; 192 } 193 194 195 /** 196 * Convert the given value for the specified property to the latter's type. 197 * <p>This method is only intended for optimizations in a BeanFactory. 198 * Use the {@code convertIfNecessary} methods for programmatic conversion. 199 * @param value the value to convert 200 * @param propertyName the target property 201 * (note that nested or indexed properties are not supported here) 202 * @return the new value, possibly the result of type conversion 203 * @throws TypeMismatchException if type conversion failed 204 */ 205 public Object convertForProperty(Object value, String propertyName) throws TypeMismatchException { 206 CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults(); 207 PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName); 208 if (pd == null) { 209 throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, 210 "No property '" + propertyName + "' found"); 211 } 212 TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); 213 if (td == null) { 214 td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd))); 215 } 216 return convertForProperty(propertyName, null, value, td); 217 } 218 219 private Property property(PropertyDescriptor pd) { 220 GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd; 221 return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName()); 222 } 223 224 @Override 225 protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) { 226 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); 227 return (pd != null ? new BeanPropertyHandler(pd) : null); 228 } 229 230 @Override 231 protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) { 232 return new BeanWrapperImpl(object, nestedPath, this); 233 } 234 235 @Override 236 protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) { 237 PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); 238 throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName, 239 matches.buildErrorMessage(), matches.getPossibleMatches()); 240 } 241 242 @Override 243 public PropertyDescriptor[] getPropertyDescriptors() { 244 return getCachedIntrospectionResults().getPropertyDescriptors(); 245 } 246 247 @Override 248 public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException { 249 BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName); 250 String finalPath = getFinalPath(nestedBw, propertyName); 251 PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath); 252 if (pd == null) { 253 throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, 254 "No property '" + propertyName + "' found"); 255 } 256 return pd; 257 } 258 259 260 private class BeanPropertyHandler extends PropertyHandler { 261 262 private final PropertyDescriptor pd; 263 264 public BeanPropertyHandler(PropertyDescriptor pd) { 265 super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null); 266 this.pd = pd; 267 } 268 269 @Override 270 public ResolvableType getResolvableType() { 271 return ResolvableType.forMethodReturnType(this.pd.getReadMethod()); 272 } 273 274 @Override 275 public TypeDescriptor toTypeDescriptor() { 276 return new TypeDescriptor(property(this.pd)); 277 } 278 279 @Override 280 public TypeDescriptor nested(int level) { 281 return TypeDescriptor.nested(property(pd), level); 282 } 283 284 @Override 285 public Object getValue() throws Exception { 286 final Method readMethod = this.pd.getReadMethod(); 287 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && !readMethod.isAccessible()) { 288 if (System.getSecurityManager() != null) { 289 AccessController.doPrivileged(new PrivilegedAction<Object>() { 290 @Override 291 public Object run() { 292 readMethod.setAccessible(true); 293 return null; 294 } 295 }); 296 } 297 else { 298 readMethod.setAccessible(true); 299 } 300 } 301 if (System.getSecurityManager() != null) { 302 try { 303 return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 304 @Override 305 public Object run() throws Exception { 306 return readMethod.invoke(getWrappedInstance(), (Object[]) null); 307 } 308 }, acc); 309 } 310 catch (PrivilegedActionException pae) { 311 throw pae.getException(); 312 } 313 } 314 else { 315 return readMethod.invoke(getWrappedInstance(), (Object[]) null); 316 } 317 } 318 319 @Override 320 public void setValue(final Object object, Object valueToApply) throws Exception { 321 final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ? 322 ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() : 323 this.pd.getWriteMethod()); 324 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { 325 if (System.getSecurityManager() != null) { 326 AccessController.doPrivileged(new PrivilegedAction<Object>() { 327 @Override 328 public Object run() { 329 writeMethod.setAccessible(true); 330 return null; 331 } 332 }); 333 } 334 else { 335 writeMethod.setAccessible(true); 336 } 337 } 338 final Object value = valueToApply; 339 if (System.getSecurityManager() != null) { 340 try { 341 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 342 @Override 343 public Object run() throws Exception { 344 writeMethod.invoke(object, value); 345 return null; 346 } 347 }, acc); 348 } 349 catch (PrivilegedActionException ex) { 350 throw ex.getException(); 351 } 352 } 353 else { 354 writeMethod.invoke(getWrappedInstance(), value); 355 } 356 } 357 } 358 359}