Package: BeanELResolver$BPSoftReference
BeanELResolver$BPSoftReference
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
BeanELResolver.BPSoftReference(Class, BeanELResolver.BeanProperties, ReferenceQueue) |
|
|
|
|
|
Coverage
1: /*
2: * Copyright (c) 1997, 2021 Oracle and/or its affiliates and others.
3: * All rights reserved.
4: * Copyright 2004 The Apache Software Foundation
5: *
6: * Licensed under the Apache License, Version 2.0 (the "License");
7: * you may not use this file except in compliance with the License.
8: * You may obtain a copy of the License at
9: *
10: * http://www.apache.org/licenses/LICENSE-2.0
11: *
12: * Unless required by applicable law or agreed to in writing, software
13: * distributed under the License is distributed on an "AS IS" BASIS,
14: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15: * See the License for the specific language governing permissions and
16: * limitations under the License.
17: */
18:
19: package jakarta.el;
20:
21: import static java.lang.Boolean.TRUE;
22: import static jakarta.el.ELUtil.getExceptionMessageString;
23:
24: import java.beans.BeanInfo;
25: import java.beans.FeatureDescriptor;
26: import java.beans.IntrospectionException;
27: import java.beans.Introspector;
28: import java.beans.PropertyDescriptor;
29: import java.lang.ref.ReferenceQueue;
30: import java.lang.ref.SoftReference;
31: import java.lang.reflect.InvocationTargetException;
32: import java.lang.reflect.Method;
33: import java.util.ArrayList;
34: import java.util.HashMap;
35: import java.util.Iterator;
36: import java.util.Map;
37: import java.util.concurrent.ConcurrentHashMap;
38:
39: /**
40: * Defines property resolution behavior on objects using the JavaBeans component architecture.
41: *
42: * <p>
43: * This resolver handles base objects of any type, as long as the base is not <code>null</code>. It accepts any object
44: * as a property or method, and coerces it to a string.
45: *
46: * <p>
47: * For property resolution, the property string is used to find a JavaBeans compliant property on the base object. The
48: * value is accessed using JavaBeans getters and setters.
49: * </p>
50: *
51: * <p>
52: * For method resolution, the method string is the name of the method in the bean. The parameter types can be optionally
53: * specified to identify the method. If the parameter types are not specified, the parameter objects are used in the
54: * method resolution.
55: * </p>
56: *
57: * <p>
58: * This resolver can be constructed in read-only mode, which means that {@link #isReadOnly} will always return
59: * <code>true</code> and {@link #setValue} will always throw <code>PropertyNotWritableException</code>.
60: * </p>
61: *
62: * <p>
63: * <code>ELResolver</code>s are combined together using {@link CompositeELResolver}s, to define rich semantics for
64: * evaluating an expression. See the javadocs for {@link ELResolver} for details.
65: * </p>
66: *
67: * <p>
68: * Because this resolver handles base objects of any type, it should be placed near the end of a composite resolver.
69: * Otherwise, it will claim to have resolved a property before any resolvers that come after it get a chance to test if
70: * they can do so as well.
71: * </p>
72: *
73: * @see CompositeELResolver
74: * @see ELResolver
75: *
76: * @since Jakarta Server Pages 2.1
77: */
78: public class BeanELResolver extends ELResolver {
79:
80: static private class BPSoftReference extends SoftReference<BeanProperties> {
81: final Class<?> key;
82:
83: BPSoftReference(Class<?> key, BeanProperties beanProperties, ReferenceQueue<BeanProperties> refQ) {
84: super(beanProperties, refQ);
85: this.key = key;
86: }
87: }
88:
89: static private class SoftConcurrentHashMap extends ConcurrentHashMap<Class<?>, BeanProperties> {
90:
91: private static final long serialVersionUID = -178867497897782229L;
92: private static final int CACHE_INIT_SIZE = 1024;
93: private ConcurrentHashMap<Class<?>, BPSoftReference> map = new ConcurrentHashMap<>(CACHE_INIT_SIZE);
94: private ReferenceQueue<BeanProperties> refQ = new ReferenceQueue<>();
95:
96: // Remove map entries that have been placed on the queue by GC.
97: private void cleanup() {
98: BPSoftReference BPRef = null;
99: while ((BPRef = (BPSoftReference) refQ.poll()) != null) {
100: map.remove(BPRef.key);
101: }
102: }
103:
104: @Override
105: public BeanProperties put(Class<?> key, BeanProperties value) {
106: cleanup();
107: BPSoftReference prev = map.put(key, new BPSoftReference(key, value, refQ));
108: return prev == null ? null : prev.get();
109: }
110:
111: @Override
112: public BeanProperties putIfAbsent(Class<?> key, BeanProperties value) {
113: cleanup();
114: BPSoftReference prev = map.putIfAbsent(key, new BPSoftReference(key, value, refQ));
115: return prev == null ? null : prev.get();
116: }
117:
118: @Override
119: public BeanProperties get(Object key) {
120: cleanup();
121: BPSoftReference BPRef = map.get(key);
122: if (BPRef == null) {
123: return null;
124: }
125: if (BPRef.get() == null) {
126: // value has been garbage collected, remove entry in map
127: map.remove(key);
128: return null;
129: }
130: return BPRef.get();
131: }
132: }
133:
134: private boolean isReadOnly;
135:
136: private static final SoftConcurrentHashMap properties = new SoftConcurrentHashMap();
137:
138: /*
139: * Defines a property for a bean.
140: */
141: final static class BeanProperty {
142:
143: private Method readMethod;
144: private Method writeMethod;
145: private PropertyDescriptor descriptor;
146:
147: public BeanProperty(Class<?> baseClass, PropertyDescriptor descriptor) {
148: this.descriptor = descriptor;
149: readMethod = ELUtil.getMethod(baseClass, descriptor.getReadMethod());
150: writeMethod = ELUtil.getMethod(baseClass, descriptor.getWriteMethod());
151: }
152:
153: public Class<?> getPropertyType() {
154: return descriptor.getPropertyType();
155: }
156:
157: public boolean isReadOnly() {
158: return getWriteMethod() == null;
159: }
160:
161: public Method getReadMethod() {
162: return readMethod;
163: }
164:
165: public Method getWriteMethod() {
166: return writeMethod;
167: }
168: }
169:
170: /*
171: * Defines the properties for a bean.
172: */
173: final static class BeanProperties {
174:
175: private final Map<String, BeanProperty> propertyMap = new HashMap<>();
176:
177: public BeanProperties(Class<?> baseClass) {
178: PropertyDescriptor[] descriptors;
179: try {
180: BeanInfo info = Introspector.getBeanInfo(baseClass);
181: descriptors = info.getPropertyDescriptors();
182: } catch (IntrospectionException ie) {
183: throw new ELException(ie);
184: }
185:
186: for (PropertyDescriptor descriptor : descriptors) {
187: propertyMap.put(descriptor.getName(), new BeanProperty(baseClass, descriptor));
188: }
189: }
190:
191: public BeanProperty getBeanProperty(String property) {
192: return propertyMap.get(property);
193: }
194: }
195:
196: /**
197: * Creates a new read/write <code>BeanELResolver</code>.
198: */
199: public BeanELResolver() {
200: this.isReadOnly = false;
201: }
202:
203: /**
204: * Creates a new <code>BeanELResolver</code> whose read-only status is determined by the given parameter.
205: *
206: * @param isReadOnly <code>true</code> if this resolver cannot modify beans; <code>false</code> otherwise.
207: */
208: public BeanELResolver(boolean isReadOnly) {
209: this.isReadOnly = isReadOnly;
210: }
211:
212: /**
213: * If the base object is not <code>null</code>, returns the most general acceptable type that can be set on this bean
214: * property.
215: *
216: * <p>
217: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
218: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
219: * this method is called, the caller should ignore the return value.
220: * </p>
221: *
222: * <p>
223: * The provided property will first be coerced to a <code>String</code>. If there is a <code>BeanInfoProperty</code> for
224: * this property and there were no errors retrieving it, the <code>propertyType</code> of the
225: * <code>propertyDescriptor</code> is returned. Otherwise, a <code>PropertyNotFoundException</code> is thrown.
226: * </p>
227: *
228: * @param context The context of this evaluation.
229: * @param base The bean to analyze.
230: * @param property The name of the property to analyze. Will be coerced to a <code>String</code>.
231: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
232: * the most general acceptable type; otherwise undefined.
233: * @throws NullPointerException if context is <code>null</code>
234: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
235: * exist or is not readable.
236: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
237: * exception must be included as the cause property of this exception, if available.
238: */
239: @Override
240: public Class<?> getType(ELContext context, Object base, Object property) {
241: if (context == null) {
242: throw new NullPointerException();
243: }
244:
245: if (base == null || property == null) {
246: return null;
247: }
248:
249: BeanProperty beanProperty = getBeanProperty(context, base, property);
250: context.setPropertyResolved(true);
251: return beanProperty.getPropertyType();
252: }
253:
254: /**
255: * If the base object is not <code>null</code>, returns the current value of the given property on this bean.
256: *
257: * <p>
258: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
259: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
260: * this method is called, the caller should ignore the return value.
261: * </p>
262: *
263: * <p>
264: * The provided property name will first be coerced to a <code>String</code>. If the property is a readable property of
265: * the base object, as per the JavaBeans specification, then return the result of the getter call. If the getter throws
266: * an exception, it is propagated to the caller. If the property is not found or is not readable, a
267: * <code>PropertyNotFoundException</code> is thrown.
268: * </p>
269: *
270: * @param context The context of this evaluation.
271: * @param base The bean on which to get the property.
272: * @param property The name of the property to get. Will be coerced to a <code>String</code>.
273: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
274: * the value of the given property. Otherwise, undefined.
275: * @throws NullPointerException if context is <code>null</code>.
276: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
277: * exist or is not readable.
278: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
279: * exception must be included as the cause property of this exception, if available.
280: */
281: @Override
282: public Object getValue(ELContext context, Object base, Object property) {
283: if (context == null) {
284: throw new NullPointerException();
285: }
286:
287: if (base == null || property == null) {
288: return null;
289: }
290:
291: Method method = getBeanProperty(context, base, property).getReadMethod();
292: if (method == null) {
293: throw new PropertyNotFoundException(
294: getExceptionMessageString(context, "propertyNotReadable", new Object[] { base.getClass().getName(), property.toString() }));
295: }
296:
297: Object value;
298: try {
299: value = method.invoke(base, new Object[0]);
300: context.setPropertyResolved(base, property);
301: } catch (ELException ex) {
302: throw ex;
303: } catch (InvocationTargetException ite) {
304: throw new ELException(ite.getCause());
305: } catch (Exception ex) {
306: throw new ELException(ex);
307: }
308:
309: return value;
310: }
311:
312: /**
313: * If the base object is not <code>null</code>, attempts to set the value of the given property on this bean.
314: *
315: * <p>
316: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
317: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
318: * this method is called, the caller can safely assume no value was set.
319: * </p>
320: *
321: * <p>
322: * If this resolver was constructed in read-only mode, this method will always throw
323: * <code>PropertyNotWritableException</code>.
324: * </p>
325: *
326: * <p>
327: * The provided property name will first be coerced to a <code>String</code>. If property is a writable property of
328: * <code>base</code> (as per the JavaBeans Specification), the setter method is called (passing <code>value</code>). If
329: * the property exists but does not have a setter, then a <code>PropertyNotFoundException</code> is thrown. If the
330: * property does not exist, a <code>PropertyNotFoundException</code> is thrown.
331: * </p>
332: *
333: * @param context The context of this evaluation.
334: * @param base The bean on which to set the property.
335: * @param property The name of the property to set. Will be coerced to a <code>String</code>.
336: * @param val The value to be associated with the specified key.
337: * @throws NullPointerException if context is <code>null</code>.
338: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
339: * exist.
340: * @throws PropertyNotWritableException if this resolver was constructed in read-only mode, or if there is no setter for
341: * the property.
342: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
343: * exception must be included as the cause property of this exception, if available.
344: */
345: @Override
346: public void setValue(ELContext context, Object base, Object property, Object val) {
347: if (context == null) {
348: throw new NullPointerException();
349: }
350:
351: if (base == null || property == null) {
352: return;
353: }
354:
355: if (isReadOnly) {
356: throw new PropertyNotWritableException(getExceptionMessageString(context, "resolverNotwritable", new Object[] { base.getClass().getName() }));
357: }
358:
359: Method method = getBeanProperty(context, base, property).getWriteMethod();
360: if (method == null) {
361: throw new PropertyNotWritableException(
362: getExceptionMessageString(context, "propertyNotWritable", new Object[] { base.getClass().getName(), property.toString() }));
363: }
364:
365: try {
366: method.invoke(base, new Object[] { val });
367: context.setPropertyResolved(base, property);
368: } catch (ELException ex) {
369: throw ex;
370: } catch (InvocationTargetException ite) {
371: throw new ELException(ite.getCause());
372: } catch (Exception ex) {
373: if (null == val) {
374: val = "null";
375: }
376: String message = getExceptionMessageString(context, "setPropertyFailed", new Object[] { property.toString(), base.getClass().getName(), val });
377: throw new ELException(message, ex);
378: }
379: }
380:
381: /**
382: * If the base object is not <code>null</code>, invoke the method, with the given parameters on this bean. The return
383: * value from the method is returned.
384: *
385: * <p>
386: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
387: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
388: * this method is called, the caller should ignore the return value.
389: * </p>
390: *
391: * <p>
392: * The provided method object will first be coerced to a <code>String</code>. The methods in the bean is then examined
393: * and an attempt will be made to select one for invocation. If no suitable can be found, a
394: * <code>MethodNotFoundException</code> is thrown.
395: *
396: * If the given paramTypes is not <code>null</code>, select the method with the given name and parameter types.
397: *
398: * Else select the method with the given name that has the same number of parameters. If there are more than one such
399: * method, the method selection process is undefined.
400: *
401: * Else select the method with the given name that takes a variable number of arguments.
402: *
403: * Note the resolution for overloaded methods will likely be clarified in a future version of the spec.
404: *
405: * The provide parameters are coerced to the corresponding parameter types of the method, and the method is then
406: * invoked.
407: *
408: * @param context The context of this evaluation.
409: * @param base The bean on which to invoke the method
410: * @param methodName The simple name of the method to invoke. Will be coerced to a <code>String</code>. If method is
411: * "<init>"or "<clinit>" a MethodNotFoundException is thrown.
412: * @param paramTypes An array of Class objects identifying the method's formal parameter types, in declared order. Use
413: * an empty array if the method has no parameters. Can be <code>null</code>, in which case the method's formal parameter
414: * types are assumed to be unknown.
415: * @param params The parameters to pass to the method, or <code>null</code> if no parameters.
416: * @return The result of the method invocation (<code>null</code> if the method has a <code>void</code> return type).
417: * @throws MethodNotFoundException if no suitable method can be found.
418: * @throws ELException if an exception was thrown while performing (base, method) resolution. The thrown exception must
419: * be included as the cause property of this exception, if available. If the exception thrown is an
420: * <code>InvocationTargetException</code>, extract its <code>cause</code> and pass it to the <code>ELException</code>
421: * constructor.
422: * @since Jakarta Expression Language 2.2
423: */
424: @Override
425: public Object invoke(ELContext context, Object base, Object methodName, Class<?>[] paramTypes, Object[] params) {
426: if (base == null || methodName == null) {
427: return null;
428: }
429:
430: Method method = ELUtil.findMethod(base.getClass(), methodName.toString(), paramTypes, params, false);
431:
432: for (Object param : params) {
433: // If the parameters is a LambdaExpression, set the ELContext
434: // for its evaluation
435: if (param instanceof LambdaExpression) {
436: ((LambdaExpression) param).setELContext(context);
437: }
438: }
439:
440: Object ret = ELUtil.invokeMethod(context, method, base, params);
441: context.setPropertyResolved(base, methodName);
442: return ret;
443: }
444:
445: /**
446: * If the base object is not <code>null</code>, returns whether a call to {@link #setValue} will always fail.
447: *
448: * <p>
449: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
450: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
451: * this method is called, the caller can safely assume no value was set.
452: * </p>
453: *
454: * <p>
455: * If this resolver was constructed in read-only mode, this method will always return <code>true</code>.
456: * </p>
457: *
458: * <p>
459: * The provided property name will first be coerced to a <code>String</code>. If property is a writable property of
460: * <code>base</code>, <code>false</code> is returned. If the property is found but is not writable, <code>true</code> is
461: * returned. If the property is not found, a <code>PropertyNotFoundException</code> is thrown.
462: * </p>
463: *
464: * @param context The context of this evaluation.
465: * @param base The bean to analyze.
466: * @param property The name of the property to analyzed. Will be coerced to a <code>String</code>.
467: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
468: * <code>true</code> if calling the <code>setValue</code> method will always fail or <code>false</code> if it is
469: * possible that such a call may succeed; otherwise undefined.
470: * @throws NullPointerException if context is <code>null</code>
471: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
472: * exist.
473: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
474: * exception must be included as the cause property of this exception, if available.
475: */
476: @Override
477: public boolean isReadOnly(ELContext context, Object base, Object property) {
478: if (context == null) {
479: throw new NullPointerException();
480: }
481:
482: if (base == null || property == null) {
483: return false;
484: }
485:
486: context.setPropertyResolved(true);
487: if (isReadOnly) {
488: return true;
489: }
490:
491: return getBeanProperty(context, base, property).isReadOnly();
492: }
493:
494: /**
495: * If the base object is not <code>null</code>, returns an <code>Iterator</code> containing the set of JavaBeans
496: * properties available on the given object. Otherwise, returns <code>null</code>.
497: *
498: * <p>
499: * The <code>Iterator</code> returned must contain zero or more instances of {@link java.beans.FeatureDescriptor}. Each
500: * info object contains information about a property in the bean, as obtained by calling the
501: * <code>BeanInfo.getPropertyDescriptors</code> method. The <code>FeatureDescriptor</code> is initialized using the same
502: * fields as are present in the <code>PropertyDescriptor</code>, with the additional required named attributes
503: * "<code>type</code>" and "<code>resolvableAtDesignTime</code>" set as follows:
504: * <ul>
505: * <li>{@link ELResolver#TYPE} - The runtime type of the property, from
506: * <code>PropertyDescriptor.getPropertyType()</code>.</li>
507: * <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code>.</li>
508: * </ul>
509: *
510: *
511: * @param context The context of this evaluation.
512: * @param base The bean to analyze.
513: * @return An <code>Iterator</code> containing zero or more <code>FeatureDescriptor</code> objects, each representing a
514: * property on this bean, or <code>null</code> if the <code>base</code> object is <code>null</code>.
515: *
516: * @deprecated This method will be removed without replacement in EL 6.0
517: */
518: @Deprecated(forRemoval = true, since = "5.0")
519: @Override
520: public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
521: if (base == null) {
522: return null;
523: }
524:
525: BeanInfo info = null;
526: try {
527: info = Introspector.getBeanInfo(base.getClass());
528: } catch (Exception ex) {
529: }
530:
531: if (info == null) {
532: return null;
533: }
534:
535: ArrayList<FeatureDescriptor> featureDescriptors = new ArrayList<>(info.getPropertyDescriptors().length);
536: for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
537: propertyDescriptor.setValue("type", propertyDescriptor.getPropertyType());
538: propertyDescriptor.setValue("resolvableAtDesignTime", TRUE);
539: featureDescriptors.add(propertyDescriptor);
540: }
541:
542: return featureDescriptors.iterator();
543: }
544:
545: /**
546: * If the base object is not <code>null</code>, returns the most general type that this resolver accepts for the
547: * <code>property</code> argument. Otherwise, returns <code>null</code>.
548: *
549: * <p>
550: * Assuming the base is not <code>null</code>, this method will always return <code>Object.class</code>. This is because
551: * any object is accepted as a key and is coerced into a string.
552: * </p>
553: *
554: * @param context The context of this evaluation.
555: * @param base The bean to analyze.
556: * @return <code>null</code> if base is <code>null</code> otherwise <code>Object.class</code>.
557: */
558: @Override
559: public Class<?> getCommonPropertyType(ELContext context, Object base) {
560: if (base == null) {
561: return null;
562: }
563:
564: return Object.class;
565: }
566:
567: private BeanProperty getBeanProperty(ELContext context, Object base, Object prop) {
568: String property = prop.toString();
569: Class<?> baseClass = base.getClass();
570:
571: BeanProperties beanProperties = properties.get(baseClass);
572: if (beanProperties == null) {
573: beanProperties = new BeanProperties(baseClass);
574: properties.put(baseClass, beanProperties);
575: }
576:
577: BeanProperty beanProperty = beanProperties.getBeanProperty(property);
578: if (beanProperty == null) {
579: throw new PropertyNotFoundException(getExceptionMessageString(context, "propertyNotFound", new Object[] { baseClass.getName(), property }));
580: }
581:
582: return beanProperty;
583: }
584: }