/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
Default BeanWrapper
implementation that should be sufficient for all typical use cases. Caches introspection results for efficiency. Note: Auto-registers default property editors from the org.springframework.beans.propertyeditors
package, which apply in addition to the JDK's standard PropertyEditors. Applications can call the PropertyEditorRegistrySupport.registerCustomEditor(Class<?>, PropertyEditor)
method to register an editor for a particular instance (i.e. they are not shared across the application). See the base class PropertyEditorRegistrySupport
for details.
NOTE: As of Spring 2.5, this is - for almost all purposes - an
internal class. It is just public in order to allow for access from other framework packages. For standard application access purposes, use the PropertyAccessorFactory.forBeanPropertyAccess
factory method instead.
Author: Rod Johnson, Juergen Hoeller, Rob Harrop, Stephane Nicoll See Also: Since: 15 April 2001
/**
* Default {@link BeanWrapper} implementation that should be sufficient
* for all typical use cases. Caches introspection results for efficiency.
*
* <p>Note: Auto-registers default property editors from the
* {@code org.springframework.beans.propertyeditors} package, which apply
* in addition to the JDK's standard PropertyEditors. Applications can call
* the {@link #registerCustomEditor(Class, java.beans.PropertyEditor)} method
* to register an editor for a particular instance (i.e. they are not shared
* across the application). See the base class
* {@link PropertyEditorRegistrySupport} for details.
*
* <p><b>NOTE: As of Spring 2.5, this is - for almost all purposes - an
* internal class.</b> It is just public in order to allow for access from
* other framework packages. For standard application access purposes, use the
* {@link PropertyAccessorFactory#forBeanPropertyAccess} factory method instead.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Stephane Nicoll
* @since 15 April 2001
* @see #registerCustomEditor
* @see #setPropertyValues
* @see #setPropertyValue
* @see #getPropertyValue
* @see #getPropertyType
* @see BeanWrapper
* @see PropertyEditorRegistrySupport
*/
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {
Cached introspections results for this object, to prevent encountering
the cost of JavaBeans introspection every time.
/**
* Cached introspections results for this object, to prevent encountering
* the cost of JavaBeans introspection every time.
*/
@Nullable
private CachedIntrospectionResults cachedIntrospectionResults;
The security context used for invoking the property methods.
/**
* The security context used for invoking the property methods.
*/
@Nullable
private AccessControlContext acc;
Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
Registers default editors.
See Also: - setWrappedInstance
/**
* Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
* Registers default editors.
* @see #setWrappedInstance
*/
public BeanWrapperImpl() {
this(true);
}
Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
Params: - registerDefaultEditors – whether to register default editors
(can be suppressed if the BeanWrapper won't need any type conversion)
See Also:
/**
* Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
* @param registerDefaultEditors whether to register default editors
* (can be suppressed if the BeanWrapper won't need any type conversion)
* @see #setWrappedInstance
*/
public BeanWrapperImpl(boolean registerDefaultEditors) {
super(registerDefaultEditors);
}
Create a new BeanWrapperImpl for the given object.
Params: - object – the object wrapped by this BeanWrapper
/**
* Create a new BeanWrapperImpl for the given object.
* @param object the object wrapped by this BeanWrapper
*/
public BeanWrapperImpl(Object object) {
super(object);
}
Create a new BeanWrapperImpl, wrapping a new instance of the specified class.
Params: - clazz – class to instantiate and wrap
/**
* Create a new BeanWrapperImpl, wrapping a new instance of the specified class.
* @param clazz class to instantiate and wrap
*/
public BeanWrapperImpl(Class<?> clazz) {
super(clazz);
}
Create a new BeanWrapperImpl for the given object,
registering a nested path that the object is in.
Params: - object – the object wrapped by this BeanWrapper
- nestedPath – the nested path of the object
- rootObject – the root object at the top of the path
/**
* Create a new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
* @param object the object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param rootObject the root object at the top of the path
*/
public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
super(object, nestedPath, rootObject);
}
Create a new BeanWrapperImpl for the given object,
registering a nested path that the object is in.
Params: - object – the object wrapped by this BeanWrapper
- nestedPath – the nested path of the object
- parent – the containing BeanWrapper (must not be
null
)
/**
* Create a new BeanWrapperImpl for the given object,
* registering a nested path that the object is in.
* @param object the object wrapped by this BeanWrapper
* @param nestedPath the nested path of the object
* @param parent the containing BeanWrapper (must not be {@code null})
*/
private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {
super(object, nestedPath, parent);
setSecurityContext(parent.acc);
}
Set a bean instance to hold, without any unwrapping of Optional
. Params: - object – the actual target object
See Also: Since: 4.3
/**
* Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
* @param object the actual target object
* @since 4.3
* @see #setWrappedInstance(Object)
*/
public void setBeanInstance(Object object) {
this.wrappedObject = object;
this.rootObject = object;
this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
setIntrospectionClass(object.getClass());
}
@Override
public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
super.setWrappedInstance(object, nestedPath, rootObject);
setIntrospectionClass(getWrappedClass());
}
Set the class to introspect.
Needs to be called when the target object changes.
Params: - clazz – the class to introspect
/**
* Set the class to introspect.
* Needs to be called when the target object changes.
* @param clazz the class to introspect
*/
protected void setIntrospectionClass(Class<?> clazz) {
if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
this.cachedIntrospectionResults = null;
}
}
Obtain a lazily initialized CachedIntrospectionResults instance
for the wrapped object.
/**
* Obtain a lazily initialized CachedIntrospectionResults instance
* for the wrapped object.
*/
private CachedIntrospectionResults getCachedIntrospectionResults() {
if (this.cachedIntrospectionResults == null) {
this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
}
return this.cachedIntrospectionResults;
}
Set the security context used during the invocation of the wrapped instance methods.
Can be null.
/**
* Set the security context used during the invocation of the wrapped instance methods.
* Can be null.
*/
public void setSecurityContext(@Nullable AccessControlContext acc) {
this.acc = acc;
}
Return the security context used during the invocation of the wrapped instance methods.
Can be null.
/**
* Return the security context used during the invocation of the wrapped instance methods.
* Can be null.
*/
@Nullable
public AccessControlContext getSecurityContext() {
return this.acc;
}
Convert the given value for the specified property to the latter's type.
This method is only intended for optimizations in a BeanFactory. Use the convertIfNecessary
methods for programmatic conversion.
Params: - value – the value to convert
- propertyName – the target property
(note that nested or indexed properties are not supported here)
Throws: - TypeMismatchException – if type conversion failed
Returns: the new value, possibly the result of type conversion
/**
* Convert the given value for the specified property to the latter's type.
* <p>This method is only intended for optimizations in a BeanFactory.
* Use the {@code convertIfNecessary} methods for programmatic conversion.
* @param value the value to convert
* @param propertyName the target property
* (note that nested or indexed properties are not supported here)
* @return the new value, possibly the result of type conversion
* @throws TypeMismatchException if type conversion failed
*/
@Nullable
public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException {
CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName);
if (pd == null) {
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
"No property '" + propertyName + "' found");
}
TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
if (td == null) {
td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
}
return convertForProperty(propertyName, null, value, td);
}
private Property property(PropertyDescriptor pd) {
GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
}
@Override
@Nullable
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null);
}
@Override
protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
return new BeanWrapperImpl(object, nestedPath, this);
}
@Override
protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
matches.buildErrorMessage(), matches.getPossibleMatches());
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return getCachedIntrospectionResults().getPropertyDescriptors();
}
@Override
public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {
BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
String finalPath = getFinalPath(nestedBw, propertyName);
PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);
if (pd == null) {
throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
"No property '" + propertyName + "' found");
}
return pd;
}
private class BeanPropertyHandler extends PropertyHandler {
private final PropertyDescriptor pd;
public BeanPropertyHandler(PropertyDescriptor pd) {
super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
this.pd = pd;
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
}
@Override
public TypeDescriptor toTypeDescriptor() {
return new TypeDescriptor(property(this.pd));
}
@Override
@Nullable
public TypeDescriptor nested(int level) {
return TypeDescriptor.nested(property(this.pd), level);
}
@Override
@Nullable
public Object getValue() throws Exception {
Method readMethod = this.pd.getReadMethod();
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(readMethod);
return null;
});
try {
return AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
() -> readMethod.invoke(getWrappedInstance(), (Object[]) null), acc);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
ReflectionUtils.makeAccessible(readMethod);
return readMethod.invoke(getWrappedInstance(), (Object[]) null);
}
}
@Override
public void setValue(@Nullable Object value) throws Exception {
Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
this.pd.getWriteMethod());
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(writeMethod);
return null;
});
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
() -> writeMethod.invoke(getWrappedInstance(), value), acc);
}
catch (PrivilegedActionException ex) {
throw ex.getException();
}
}
else {
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(getWrappedInstance(), value);
}
}
}
}