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.security.AccessControlContext;
022import java.security.AccessController;
023import java.security.PrivilegedAction;
024import java.security.PrivilegedActionException;
025import java.security.PrivilegedExceptionAction;
026
027import org.springframework.core.ResolvableType;
028import org.springframework.core.convert.Property;
029import org.springframework.core.convert.TypeDescriptor;
030import org.springframework.lang.Nullable;
031import org.springframework.util.ReflectionUtils;
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        @Nullable
070        private CachedIntrospectionResults cachedIntrospectionResults;
071
072        /**
073         * The security context used for invoking the property methods.
074         */
075        @Nullable
076        private AccessControlContext acc;
077
078
079        /**
080         * Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
081         * Registers default editors.
082         * @see #setWrappedInstance
083         */
084        public BeanWrapperImpl() {
085                this(true);
086        }
087
088        /**
089         * Create a new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
090         * @param registerDefaultEditors whether to register default editors
091         * (can be suppressed if the BeanWrapper won't need any type conversion)
092         * @see #setWrappedInstance
093         */
094        public BeanWrapperImpl(boolean registerDefaultEditors) {
095                super(registerDefaultEditors);
096        }
097
098        /**
099         * Create a new BeanWrapperImpl for the given object.
100         * @param object the object wrapped by this BeanWrapper
101         */
102        public BeanWrapperImpl(Object object) {
103                super(object);
104        }
105
106        /**
107         * Create a new BeanWrapperImpl, wrapping a new instance of the specified class.
108         * @param clazz class to instantiate and wrap
109         */
110        public BeanWrapperImpl(Class<?> clazz) {
111                super(clazz);
112        }
113
114        /**
115         * Create a new BeanWrapperImpl for the given object,
116         * registering a nested path that the object is in.
117         * @param object the object wrapped by this BeanWrapper
118         * @param nestedPath the nested path of the object
119         * @param rootObject the root object at the top of the path
120         */
121        public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
122                super(object, nestedPath, rootObject);
123        }
124
125        /**
126         * Create a new BeanWrapperImpl for the given object,
127         * registering a nested path that the object is in.
128         * @param object the object wrapped by this BeanWrapper
129         * @param nestedPath the nested path of the object
130         * @param parent the containing BeanWrapper (must not be {@code null})
131         */
132        private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {
133                super(object, nestedPath, parent);
134                setSecurityContext(parent.acc);
135        }
136
137
138        /**
139         * Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
140         * @param object the actual target object
141         * @since 4.3
142         * @see #setWrappedInstance(Object)
143         */
144        public void setBeanInstance(Object object) {
145                this.wrappedObject = object;
146                this.rootObject = object;
147                this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
148                setIntrospectionClass(object.getClass());
149        }
150
151        @Override
152        public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
153                super.setWrappedInstance(object, nestedPath, rootObject);
154                setIntrospectionClass(getWrappedClass());
155        }
156
157        /**
158         * Set the class to introspect.
159         * Needs to be called when the target object changes.
160         * @param clazz the class to introspect
161         */
162        protected void setIntrospectionClass(Class<?> clazz) {
163                if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
164                        this.cachedIntrospectionResults = null;
165                }
166        }
167
168        /**
169         * Obtain a lazily initialized CachedIntrospectionResults instance
170         * for the wrapped object.
171         */
172        private CachedIntrospectionResults getCachedIntrospectionResults() {
173                if (this.cachedIntrospectionResults == null) {
174                        this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
175                }
176                return this.cachedIntrospectionResults;
177        }
178
179        /**
180         * Set the security context used during the invocation of the wrapped instance methods.
181         * Can be null.
182         */
183        public void setSecurityContext(@Nullable AccessControlContext acc) {
184                this.acc = acc;
185        }
186
187        /**
188         * Return the security context used during the invocation of the wrapped instance methods.
189         * Can be null.
190         */
191        @Nullable
192        public AccessControlContext getSecurityContext() {
193                return this.acc;
194        }
195
196
197        /**
198         * Convert the given value for the specified property to the latter's type.
199         * <p>This method is only intended for optimizations in a BeanFactory.
200         * Use the {@code convertIfNecessary} methods for programmatic conversion.
201         * @param value the value to convert
202         * @param propertyName the target property
203         * (note that nested or indexed properties are not supported here)
204         * @return the new value, possibly the result of type conversion
205         * @throws TypeMismatchException if type conversion failed
206         */
207        @Nullable
208        public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException {
209                CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
210                PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName);
211                if (pd == null) {
212                        throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
213                                        "No property '" + propertyName + "' found");
214                }
215                TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
216                if (td == null) {
217                        td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
218                }
219                return convertForProperty(propertyName, null, value, td);
220        }
221
222        private Property property(PropertyDescriptor pd) {
223                GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
224                return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
225        }
226
227        @Override
228        @Nullable
229        protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
230                PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
231                return (pd != null ? new BeanPropertyHandler(pd) : null);
232        }
233
234        @Override
235        protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
236                return new BeanWrapperImpl(object, nestedPath, this);
237        }
238
239        @Override
240        protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
241                PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
242                throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
243                                matches.buildErrorMessage(), matches.getPossibleMatches());
244        }
245
246        @Override
247        public PropertyDescriptor[] getPropertyDescriptors() {
248                return getCachedIntrospectionResults().getPropertyDescriptors();
249        }
250
251        @Override
252        public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {
253                BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
254                String finalPath = getFinalPath(nestedBw, propertyName);
255                PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);
256                if (pd == null) {
257                        throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
258                                        "No property '" + propertyName + "' found");
259                }
260                return pd;
261        }
262
263
264        private class BeanPropertyHandler extends PropertyHandler {
265
266                private final PropertyDescriptor pd;
267
268                public BeanPropertyHandler(PropertyDescriptor pd) {
269                        super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
270                        this.pd = pd;
271                }
272
273                @Override
274                public ResolvableType getResolvableType() {
275                        return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
276                }
277
278                @Override
279                public TypeDescriptor toTypeDescriptor() {
280                        return new TypeDescriptor(property(this.pd));
281                }
282
283                @Override
284                @Nullable
285                public TypeDescriptor nested(int level) {
286                        return TypeDescriptor.nested(property(this.pd), level);
287                }
288
289                @Override
290                @Nullable
291                public Object getValue() throws Exception {
292                        Method readMethod = this.pd.getReadMethod();
293                        if (System.getSecurityManager() != null) {
294                                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
295                                        ReflectionUtils.makeAccessible(readMethod);
296                                        return null;
297                                });
298                                try {
299                                        return AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
300                                                        () -> readMethod.invoke(getWrappedInstance(), (Object[]) null), acc);
301                                }
302                                catch (PrivilegedActionException pae) {
303                                        throw pae.getException();
304                                }
305                        }
306                        else {
307                                ReflectionUtils.makeAccessible(readMethod);
308                                return readMethod.invoke(getWrappedInstance(), (Object[]) null);
309                        }
310                }
311
312                @Override
313                public void setValue(@Nullable Object value) throws Exception {
314                        Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
315                                        ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
316                                        this.pd.getWriteMethod());
317                        if (System.getSecurityManager() != null) {
318                                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
319                                        ReflectionUtils.makeAccessible(writeMethod);
320                                        return null;
321                                });
322                                try {
323                                        AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
324                                                        () -> writeMethod.invoke(getWrappedInstance(), value), acc);
325                                }
326                                catch (PrivilegedActionException ex) {
327                                        throw ex.getException();
328                                }
329                        }
330                        else {
331                                ReflectionUtils.makeAccessible(writeMethod);
332                                writeMethod.invoke(getWrappedInstance(), value);
333                        }
334                }
335        }
336
337}