View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal.introspection;
18  
19  import org.apache.commons.jexl3.JexlArithmetic;
20  import org.apache.commons.jexl3.JexlEngine;
21  import org.apache.commons.jexl3.JexlOperator;
22  import org.apache.commons.jexl3.introspection.JexlMethod;
23  import org.apache.commons.jexl3.introspection.JexlPermissions;
24  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
25  import org.apache.commons.jexl3.introspection.JexlPropertySet;
26  import org.apache.commons.jexl3.introspection.JexlUberspect;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import java.lang.reflect.Field;
32  import java.lang.reflect.Method;
33  
34  import java.util.EnumSet;
35  import java.util.Enumeration;
36  import java.util.Iterator;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.concurrent.atomic.AtomicInteger;
40  import java.util.concurrent.ConcurrentHashMap;
41  
42  import java.lang.ref.Reference;
43  import java.lang.ref.SoftReference;
44  import java.util.List;
45  
46  /**
47   * Implementation of Uberspect to provide the default introspective
48   * functionality of JEXL.
49   * <p>
50   * This is the class to derive to customize introspection.</p>
51   *
52   * @since 1.0
53   */
54  public class Uberspect implements JexlUberspect {
55      /** Publicly exposed special failure object returned by tryInvoke. */
56      public static final Object TRY_FAILED = JexlEngine.TRY_FAILED;
57      /** The logger to use for all warnings and errors. */
58      protected final Log logger;
59      /** The resolver strategy. */
60      private final JexlUberspect.ResolverStrategy strategy;
61      /** The permissions. */
62      private final JexlPermissions permissions;
63      /** The introspector version. */
64      private final AtomicInteger version;
65      /** The soft reference to the introspector currently in use. */
66      private volatile Reference<Introspector> ref;
67      /** The class loader reference; used to recreate the introspector when necessary. */
68      private volatile Reference<ClassLoader> loader;
69      /**
70       * The map from arithmetic classes to overloaded operator sets.
71       * <p>
72       * This map keeps track of which operator methods are overloaded per JexlArithmetic classes
73       * allowing a fail fast test during interpretation by avoiding seeking a method when there is none.
74       */
75      private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap;
76  
77      /**
78       * Creates a new Uberspect.
79       * @param runtimeLogger the logger used for all logging needs
80       * @param sty the resolver strategy
81       */
82      public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty) {
83          this(runtimeLogger, sty, null);
84      }
85  
86      /**
87       * Creates a new Uberspect.
88       * @param runtimeLogger the logger used for all logging needs
89       * @param sty the resolver strategy
90       * @param perms the introspector permissions
91       */
92      public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty, final JexlPermissions perms) {
93          logger = runtimeLogger == null? LogFactory.getLog(JexlEngine.class) : runtimeLogger;
94          strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty;
95          permissions = perms == null? JexlPermissions.RESTRICTED : perms;
96          ref = new SoftReference<>(null);
97          loader = new SoftReference<>(getClass().getClassLoader());
98          operatorMap = new ConcurrentHashMap<>();
99          version = new AtomicInteger(0);
100     }
101 
102     /**
103      * Gets the current introspector base.
104      * <p>
105      * If the reference has been collected, this method will recreate the underlying introspector.</p>
106      * @return the introspector
107      */
108     // CSOFF: DoubleCheckedLocking
109     protected final Introspector base() {
110         Introspector intro = ref.get();
111         if (intro == null) {
112             // double-checked locking is ok (fixed by Java 5 memory model).
113             synchronized (this) {
114                 intro = ref.get();
115                 if (intro == null) {
116                     intro = new Introspector(logger, loader.get(), permissions);
117                     ref = new SoftReference<>(intro);
118                     loader = new SoftReference<>(intro.getLoader());
119                     version.incrementAndGet();
120                 }
121             }
122         }
123         return intro;
124     }
125     // CSON: DoubleCheckedLocking
126 
127     @Override
128     public void setClassLoader(final ClassLoader nloader) {
129         synchronized (this) {
130             Introspector intro = ref.get();
131             if (intro != null) {
132                 intro.setLoader(nloader);
133             } else {
134                 intro = new Introspector(logger, nloader, permissions);
135                 ref = new SoftReference<>(intro);
136             }
137             loader = new SoftReference<>(intro.getLoader());
138             operatorMap.clear();
139             version.incrementAndGet();
140         }
141     }
142 
143     @Override
144     public ClassLoader getClassLoader() {
145         return loader.get();
146     }
147 
148     @Override
149     public int getVersion() {
150         return version.intValue();
151     }
152 
153     /**
154      * Gets a class by name through this introspector class loader.
155      * @param className the class name
156      * @return the class instance or null if it could not be found
157      */
158     @Override
159     public final Class<?> getClassByName(final String className) {
160         return base().getClassByName(className);
161     }
162 
163     /**
164      * Gets the field named by
165      * <code>key</code> for the class
166      * <code>c</code>.
167      *
168      * @param c   Class in which the field search is taking place
169      * @param key Name of the field being searched for
170      * @return a {@link java.lang.reflect.Field} or null if it does not exist or is not accessible
171      */
172     public final Field getField(final Class<?> c, final String key) {
173         return base().getField(c, key);
174     }
175 
176     /**
177      * Gets the accessible field names known for a given class.
178      * @param c the class
179      * @return the class field names
180      */
181     public final String[] getFieldNames(final Class<?> c) {
182         return base().getFieldNames(c);
183     }
184 
185     /**
186      * Gets the method defined by
187      * <code>name</code> and
188      * <code>params</code> for the Class
189      * <code>c</code>.
190      *
191      * @param c      Class in which the method search is taking place
192      * @param name   Name of the method being searched for
193      * @param params An array of Objects (not Classes) that describe the parameters
194      *
195      * @return a {@link java.lang.reflect.Method}
196      *         or null if no unambiguous method could be found through introspection.
197      */
198     public final Method getMethod(final Class<?> c, final String name, final Object[] params) {
199         return base().getMethod(c, new MethodKey(name, params));
200     }
201 
202     /**
203      * Gets the method defined by
204      * <code>key</code> and for the Class
205      * <code>c</code>.
206      *
207      * @param c   Class in which the method search is taking place
208      * @param key MethodKey of the method being searched for
209      *
210      * @return a {@link java.lang.reflect.Method}
211      *         or null if no unambiguous method could be found through introspection.
212      */
213     public final Method getMethod(final Class<?> c, final MethodKey key) {
214         return base().getMethod(c, key);
215     }
216 
217     /**
218      * Gets the accessible methods names known for a given class.
219      * @param c the class
220      * @return the class method names
221      */
222     public final String[] getMethodNames(final Class<?> c) {
223         return base().getMethodNames(c);
224     }
225 
226     /**
227      * Gets all the methods with a given name from this map.
228      * @param c          the class
229      * @param methodName the seeked methods name
230      * @return the array of methods
231      */
232     public final Method[] getMethods(final Class<?> c, final String methodName) {
233         return base().getMethods(c, methodName);
234     }
235 
236     @Override
237     public JexlMethod getMethod(final Object obj, final String method, final Object... args) {
238         return MethodExecutor.discover(base(), obj, method, args);
239     }
240 
241     @Override
242     public List<PropertyResolver> getResolvers(final JexlOperator op, final Object obj) {
243         return strategy.apply(op, obj);
244     }
245 
246     @Override
247     public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) {
248         return getPropertyGet(null, obj, identifier);
249     }
250 
251     @Override
252     public JexlPropertyGet getPropertyGet(
253             final List<PropertyResolver> resolvers, final Object obj, final Object identifier
254     ) {
255         final Class<?> claz = obj.getClass();
256         final String property = AbstractExecutor.castString(identifier);
257         final Introspector is = base();
258         final List<PropertyResolver> r = resolvers == null? strategy.apply(null, obj) : resolvers;
259         JexlPropertyGet executor = null;
260         for (final PropertyResolver resolver : r) {
261             if (resolver instanceof JexlResolver) {
262                 switch ((JexlResolver) resolver) {
263                     case PROPERTY:
264                         // first try for a getFoo() type of property (also getfoo() )
265                         executor = PropertyGetExecutor.discover(is, claz, property);
266                         if (executor == null) {
267                             executor = BooleanGetExecutor.discover(is, claz, property);
268                         }
269                         break;
270                     case MAP:
271                         // let's see if we are a map...
272                         executor = MapGetExecutor.discover(is, claz, identifier);
273                         break;
274                     case LIST:
275                         // let's see if this is a list or array
276                         final Integer index = AbstractExecutor.castInteger(identifier);
277                         if (index != null) {
278                             executor = ListGetExecutor.discover(is, claz, index);
279                         }
280                         break;
281                     case DUCK:
282                         // if that didn't work, look for get(foo)
283                         executor = DuckGetExecutor.discover(is, claz, identifier);
284                         if (executor == null && property != null && property != identifier) {
285                             // look for get("foo") if we did not try yet (just above)
286                             executor = DuckGetExecutor.discover(is, claz, property);
287                         }
288                         break;
289                     case FIELD:
290                         // a field may be? (can not be a number)
291                         executor = FieldGetExecutor.discover(is, claz, property);
292                         // static class fields (enums included)
293                         if (obj instanceof Class<?>) {
294                             executor = FieldGetExecutor.discover(is, (Class<?>) obj, property);
295                         }
296                         break;
297                     case CONTAINER:
298                         // or an indexed property?
299                         executor = IndexedType.discover(is, obj, property);
300                         break;
301                     default:
302                         continue; // in case we add new ones in enum
303                 }
304             } else {
305                 executor = resolver.getPropertyGet(this, obj, identifier);
306             }
307             if (executor != null) {
308                 return executor;
309             }
310         }
311         return null;
312     }
313 
314     @Override
315     public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) {
316         return getPropertySet(null, obj, identifier, arg);
317     }
318 
319     @Override
320     public JexlPropertySet getPropertySet(
321             final List<PropertyResolver> resolvers, final Object obj, final Object identifier, final Object arg
322     ) {
323         final Class<?> claz = obj.getClass();
324         final String property = AbstractExecutor.castString(identifier);
325         final Introspector is = base();
326         final List<PropertyResolver> actual = resolvers == null? strategy.apply(null, obj) : resolvers;
327         JexlPropertySet executor = null;
328         for (final PropertyResolver resolver : actual) {
329             if (resolver instanceof JexlResolver) {
330                 switch ((JexlResolver) resolver) {
331                     case PROPERTY:
332                         // first try for a setFoo() type of property (also setfoo() )
333                         executor = PropertySetExecutor.discover(is, claz, property, arg);
334                         break;
335                     case MAP:
336                         // let's see if we are a map...
337                         executor = MapSetExecutor.discover(is, claz, identifier, arg);
338                         break;
339                     case LIST:
340                         // let's see if we can convert the identifier to an int,
341                         // if obj is an array or a list, we can still do something
342                         final Integer index = AbstractExecutor.castInteger(identifier);
343                         if (index != null) {
344                             executor = ListSetExecutor.discover(is, claz, identifier, arg);
345                         }
346                         break;
347                     case DUCK:
348                         // if that didn't work, look for set(foo)
349                         executor = DuckSetExecutor.discover(is, claz, identifier, arg);
350                         if (executor == null && property != null && property != identifier) {
351                             executor = DuckSetExecutor.discover(is, claz, property, arg);
352                         }
353                         break;
354                     case FIELD:
355                         // a field may be?
356                         executor = FieldSetExecutor.discover(is, claz, property, arg);
357                         break;
358                     case CONTAINER:
359                     default:
360                         continue; // in case we add new ones in enum
361                 }
362             } else {
363                 executor = resolver.getPropertySet(this, obj, identifier, arg);
364             }
365             if (executor != null) {
366                 return executor;
367             }
368         }
369         return null;
370     }
371 
372     @Override
373     @SuppressWarnings("unchecked")
374     public Iterator<?> getIterator(final Object obj) {
375         if (!permissions.allow(obj.getClass())) {
376             return null;
377         }
378         if (obj instanceof Iterator<?>) {
379             return ((Iterator<?>) obj);
380         }
381         if (obj.getClass().isArray()) {
382             return new ArrayIterator(obj);
383         }
384         if (obj instanceof Map<?, ?>) {
385             return ((Map<?, ?>) obj).values().iterator();
386         }
387         if (obj instanceof Enumeration<?>) {
388             return new EnumerationIterator<>((Enumeration<Object>) obj);
389         }
390         if (obj instanceof Iterable<?>) {
391             return ((Iterable<?>) obj).iterator();
392         }
393         try {
394             // look for an iterator() method to support the JDK5 Iterable
395             // interface or any user tools/DTOs that want to work in
396             // foreach without implementing the Collection interface
397             final JexlMethod it = getMethod(obj, "iterator", (Object[]) null);
398             if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) {
399                 return (Iterator<Object>) it.invoke(obj, (Object[]) null);
400             }
401         } catch (final Exception xany) {
402             if (logger != null && logger.isDebugEnabled()) {
403                 logger.info("unable to solve iterator()", xany);
404             }
405         }
406         return null;
407     }
408 
409     @Override
410     public JexlMethod getConstructor(final Object ctorHandle, final Object... args) {
411         return ConstructorMethod.discover(base(), ctorHandle, args);
412     }
413 
414     /**
415      * The concrete uberspect Arithmetic class.
416      */
417     protected class ArithmeticUberspect implements JexlArithmetic.Uberspect {
418         /** The arithmetic instance being analyzed. */
419         private final JexlArithmetic arithmetic;
420         /** The set of overloaded operators. */
421         private final Set<JexlOperator> overloads;
422 
423         /**
424          * Creates an instance.
425          * @param theArithmetic the arithmetic instance
426          * @param theOverloads  the overloaded operators
427          */
428         ArithmeticUberspect(final JexlArithmetic theArithmetic, final Set<JexlOperator> theOverloads) {
429             this.arithmetic = theArithmetic;
430             this.overloads = theOverloads;
431         }
432 
433         @Override
434         public JexlMethod getOperator(final JexlOperator operator, final Object... args) {
435             return overloads.contains(operator) && args != null
436                    ? getMethod(arithmetic, operator.getMethodName(), args)
437                    : null;
438         }
439 
440         @Override
441         public boolean overloads(final JexlOperator operator) {
442             return overloads.contains(operator);
443         }
444     }
445 
446     @Override
447     public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) {
448         JexlArithmetic.Uberspect jau = null;
449         if (arithmetic != null) {
450             final Class<? extends JexlArithmetic> aclass = arithmetic.getClass();
451             Set<JexlOperator> ops = operatorMap.get(aclass);
452             if (ops == null) {
453                 ops = EnumSet.noneOf(JexlOperator.class);
454                 // deal only with derived classes
455                 if (!JexlArithmetic.class.equals(aclass)) {
456                     for (final JexlOperator op : JexlOperator.values()) {
457                         final Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName());
458                         if (methods != null) {
459                             for (final Method method : methods) {
460                                 final Class<?>[] parms = method.getParameterTypes();
461                                 if (parms.length != op.getArity()) {
462                                     continue;
463                                 }
464                                 // filter method that is an actual overload:
465                                 // - not inherited (not declared by base class)
466                                 // - nor overridden (not present in base class)
467                                 if (!JexlArithmetic.class.equals(method.getDeclaringClass())) {
468                                     try {
469                                         JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes());
470                                     } catch (final NoSuchMethodException xmethod) {
471                                         // method was not found in JexlArithmetic; this is an operator definition
472                                         ops.add(op);
473                                     }
474                                 }
475                             }
476                         }
477                     }
478                 }
479                 // register this arithmetic class in the operator map
480                 operatorMap.put(aclass, ops);
481             }
482             jau = new ArithmeticUberspect(arithmetic, ops);
483         }
484         return jau;
485     }
486 }