Skip to content

Package: BeanELResolver$BPSoftReference

BeanELResolver$BPSoftReference

nameinstructionbranchcomplexitylinemethod
BeanELResolver.BPSoftReference(Class, BeanELResolver.BeanProperties, ReferenceQueue)
M: 8 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%

Coverage

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