/*
* Copyright 2002-2018 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
*
* http://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.lang.reflect.
Modifier;
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.util.
Assert;
/**
* 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.
*/
private
CachedIntrospectionResults cachedIntrospectionResults;
/**
* The security context used for invoking the property methods
*/
private
AccessControlContext acc;
/**
* 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.
* @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.
* @param object object wrapped by this BeanWrapper
*/
public
BeanWrapperImpl(
Object object) {
super(
object);
}
/**
* 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.
* @param object 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.
* @param object 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 {@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,
String nestedPath,
Object rootObject) {
super.setWrappedInstance(
object,
nestedPath,
rootObject);
setIntrospectionClass(
getWrappedClass());
}
/**
* 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 initializted CachedIntrospectionResults instance
* for the wrapped object.
*/
private
CachedIntrospectionResults getCachedIntrospectionResults() {
Assert.
state(
getWrappedInstance() != null, "BeanWrapper does not hold a bean instance");
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.
*/
public void
setSecurityContext(
AccessControlContext acc) {
this.
acc =
acc;
}
/**
* Return the security context used during the invocation of the wrapped instance methods.
* Can be null.
*/
public
AccessControlContext getSecurityContext() {
return this.
acc;
}
/**
* 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
*/
public
Object convertForProperty(
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
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
public
TypeDescriptor nested(int
level) {
return
TypeDescriptor.
nested(
property(
pd),
level);
}
@
Override
public
Object getValue() throws
Exception {
final
Method readMethod = this.
pd.
getReadMethod();
if (!
Modifier.
isPublic(
readMethod.
getDeclaringClass().
getModifiers()) && !
readMethod.
isAccessible()) {
if (
System.
getSecurityManager() != null) {
AccessController.
doPrivileged(new
PrivilegedAction<
Object>() {
@
Override
public
Object run() {
readMethod.
setAccessible(true);
return null;
}
});
}
else {
readMethod.
setAccessible(true);
}
}
if (
System.
getSecurityManager() != null) {
try {
return
AccessController.
doPrivileged(new
PrivilegedExceptionAction<
Object>() {
@
Override
public
Object run() throws
Exception {
return
readMethod.
invoke(
getWrappedInstance(), (
Object[]) null);
}
},
acc);
}
catch (
PrivilegedActionException pae) {
throw
pae.
getException();
}
}
else {
return
readMethod.
invoke(
getWrappedInstance(), (
Object[]) null);
}
}
@
Override
public void
setValue(final
Object object,
Object valueToApply) throws
Exception {
final
Method writeMethod = (this.
pd instanceof
GenericTypeAwarePropertyDescriptor ?
((
GenericTypeAwarePropertyDescriptor) this.
pd).
getWriteMethodForActualAccess() :
this.
pd.
getWriteMethod());
if (!
Modifier.
isPublic(
writeMethod.
getDeclaringClass().
getModifiers()) && !
writeMethod.
isAccessible()) {
if (
System.
getSecurityManager() != null) {
AccessController.
doPrivileged(new
PrivilegedAction<
Object>() {
@
Override
public
Object run() {
writeMethod.
setAccessible(true);
return null;
}
});
}
else {
writeMethod.
setAccessible(true);
}
}
final
Object value =
valueToApply;
if (
System.
getSecurityManager() != null) {
try {
AccessController.
doPrivileged(new
PrivilegedExceptionAction<
Object>() {
@
Override
public
Object run() throws
Exception {
writeMethod.
invoke(
object,
value);
return null;
}
},
acc);
}
catch (
PrivilegedActionException ex) {
throw
ex.
getException();
}
}
else {
writeMethod.
invoke(
getWrappedInstance(),
value);
}
}
}
}