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}