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  
18  package org.apache.commons.jexl3;
19  
20  import java.nio.charset.Charset;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Map;
24  import java.util.function.IntFunction;
25  
26  import org.apache.commons.jexl3.internal.Engine;
27  import org.apache.commons.jexl3.internal.SoftCache;
28  import org.apache.commons.jexl3.introspection.JexlPermissions;
29  import org.apache.commons.jexl3.introspection.JexlSandbox;
30  import org.apache.commons.jexl3.introspection.JexlUberspect;
31  import org.apache.commons.logging.Log;
32  
33  /**
34   * Configures and builds a JexlEngine.
35   *
36   * <p>
37   *     The builder allow fine-tuning an engine instance behavior according to various control needs.
38   *     Check <em>{@link #JexlBuilder()}</em> for permission impacts starting with <em>JEXL 3.3</em>.
39   * </p><p>
40   *     Broad configurations elements are controlled through the features ({@link JexlFeatures}) that can restrict JEXL
41   *  syntax - for instance, only expressions with no-side effects - and permissions ({@link JexlPermissions}) that control
42   *  the visible set of objects - for instance, avoiding access to any object in java.rmi.* -.
43   *  </p><p>
44   *     Fine error control and runtime-overridable behaviors are implemented through options ({@link JexlOptions}). Most
45   * common flags accessible from the builder are reflected in its options ({@link #options()}).
46   * </p><p>
47   *     The <code>silent</code> flag tells the engine what to do with the error; when true, errors are logged as
48   * warning, when false, they throw {@link JexlException} exceptions.
49   * </p><p>
50   *     The <code>strict</code> flag tells the engine when and if null as operand is considered an error. The <code>safe</code>
51   * flog determines if safe-navigation is used. Safe-navigation allows an  evaluation shortcut and return null in expressions
52   * that attempts dereferencing null, typically a method call or accessing a property.
53   * </p><p>
54   *     The <code>lexical</code> and <code>lexicalShade</code> flags can be used to enforce a lexical scope for
55   * variables and parameters. The <code>lexicalShade</code> can be used to further ensure no global variable can be
56   * used with the same name as a local one even after it goes out of scope. The corresponding feature flags should be
57   * preferred since they will detect violations at parsing time. (see {@link JexlFeatures})
58   * </p><p>
59   *     The following rules apply on silent and strict flags:
60   * </p>
61   * <ul>
62   * <li>When "silent" &amp; "not-strict":
63   * <p> 0 &amp; null should be indicators of "default" values so that even in an case of error,
64   * something meaningful can still be inferred; may be convenient for configurations.
65   * </p>
66   * </li>
67   * <li>When "silent" &amp; "strict":
68   * <p>One should probably consider using null as an error case - ie, every object
69   * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
70   * can be used to workaround exceptional cases.
71   * Use case could be configuration with no implicit values or defaults.
72   * </p>
73   * </li>
74   * <li>When "not-silent" &amp; "not-strict":
75   * <p>The error control grain is roughly on par with JEXL 1.0</p>
76   * </li>
77   * <li>When "not-silent" &amp; "strict":
78   * <p>The finest error control grain is obtained; it is the closest to Java code -
79   * still augmented by "script" capabilities regarding automated conversions and type matching.
80   * </p>
81   * </li>
82   * </ul>
83   */
84  public class JexlBuilder {
85      /**
86       * The set of default permissions used when creating a new builder.
87       * <p>Static but modifiable so these default permissions can be changed to a purposeful set.</p>
88       * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p>
89       * <p>In JEXL 3.2, these were equivalent to {@link JexlPermissions#UNRESTRICTED}.</p>
90       */
91      private static JexlPermissions PERMISSIONS = JexlPermissions.RESTRICTED;
92  
93      /** The default maximum expression length to hit the expression cache. */
94      protected static final int CACHE_THRESHOLD = 64;
95  
96      /**
97       * Sets the default permissions.
98       * @param permissions the permissions
99       */
100     public static void setDefaultPermissions(final JexlPermissions permissions) {
101         PERMISSIONS = permissions == null ? JexlPermissions.RESTRICTED : permissions;
102     }
103 
104     /** The JexlUberspect instance. */
105     private JexlUberspect uberspect;
106 
107     /** The {@link JexlUberspect} resolver strategy. */
108     private JexlUberspect.ResolverStrategy strategy;
109 
110     /** The set of permissions. */
111     private JexlPermissions permissions;
112 
113     /** The sandbox. */
114     private JexlSandbox sandbox;
115 
116     /** The Log to which all JexlEngine messages will be logged. */
117     private Log logger;
118 
119     /** Whether error messages will carry debugging information. */
120     private Boolean debug;
121 
122     /** Whether interrupt throws JexlException.Cancel. */
123     private Boolean cancellable;
124 
125     /** The options. */
126     private final JexlOptions options = new JexlOptions();
127 
128     /** Whether getVariables considers all potential equivalent syntactic forms. */
129     private int collectMode = 1;
130 
131     /** The {@link JexlArithmetic} instance. */
132     private JexlArithmetic arithmetic;
133 
134     /** The cache size. */
135     private int cache = -1;
136 
137     /** The cache class factory. */
138     private IntFunction<JexlCache<?,?>> cacheFactory = SoftCache::new;
139 
140     /** The stack overflow limit. */
141     private int stackOverflow = Integer.MAX_VALUE;
142 
143     /** The maximum expression length to hit the expression cache. */
144     private int cacheThreshold = CACHE_THRESHOLD;
145 
146     /** The charset. */
147     private Charset charset = Charset.defaultCharset();
148 
149     /** The class loader. */
150     private ClassLoader loader;
151 
152     /** The features. */
153     private JexlFeatures features;
154 
155     /**
156      * Default constructor.
157      * <p>
158      * As of JEXL 3.3, to reduce the security risks inherent to JEXL&quot;s purpose, the builder will use a set of
159      * restricted permissions as a default to create the {@link JexlEngine} instance. This will greatly reduce which classes
160      * and methods are visible to JEXL and usable in scripts using default implicit behaviors.
161      * </p><p>
162      * However, without mitigation, this change will likely break some scripts at runtime, especially those exposing
163      * your own class instances through arguments, contexts or namespaces.
164      * The new default set of allowed packages and denied classes is described by {@link JexlPermissions#RESTRICTED}.
165      * </p><p>
166      * The recommended mitigation if your usage of JEXL is impacted is to first thoroughly review what should be
167      * allowed and exposed to script authors and implement those through a set of {@link JexlPermissions};
168      * those are easily created using {@link JexlPermissions#parse(String...)}.
169      * </p><p>
170      * In the urgent case of a strict 3.2 compatibility, the simplest and fastest mitigation is to use the 'unrestricted'
171      * set of permissions. The builder must be explicit about it either by setting the default permissions with a
172      * statement like <code>JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED);</code> or with a more precise
173      * one like <code>new JexlBuilder().permissions({@link JexlPermissions#UNRESTRICTED})</code>.
174      * </p><p>
175      * Note that an explicit call to {@link #uberspect(JexlUberspect)} will supersede any permissions related behavior
176      * by using the {@link JexlUberspect} provided as argument used as-is in the created {@link JexlEngine}.
177      * </p>
178      * @since 3.3
179      */
180     public JexlBuilder() {
181         this.permissions = PERMISSIONS;
182     }
183 
184     /** @return whether antish resolution is enabled */
185     public boolean antish() {
186         return options.isAntish();
187     }
188 
189     /**
190          * Sets whether the engine will resolve antish variable names.
191          *
192          * @param flag true means antish resolution is enabled, false disables it
193          * @return this builder
194          */
195         public JexlBuilder antish(final boolean flag) {
196             options.setAntish(flag);
197             return this;
198         }
199 
200     /** @return the arithmetic */
201     public JexlArithmetic arithmetic() {
202         return this.arithmetic;
203     }
204 
205     /**
206      * Sets the JexlArithmetic instance the engine will use.
207      *
208      * @param a the arithmetic
209      * @return this builder
210      */
211     public JexlBuilder arithmetic(final JexlArithmetic a) {
212         this.arithmetic = a;
213         options.setStrictArithmetic(a.isStrict());
214         options.setMathContext(a.getMathContext());
215         options.setMathScale(a.getMathScale());
216         return this;
217     }
218 
219     /**
220      * @return the cache size
221      */
222     public int cache() {
223       return cache;
224     }
225 
226     /**
227      * Sets the expression cache size the engine will use.
228      * <p>The cache will contain at most <code>size</code> expressions of at most <code>cacheThreshold</code> length.
229      * Note that all JEXL caches are held through SoftReferences and may be garbage-collected.</p>
230      *
231      * @param size if not strictly positive, no cache is used.
232      * @return this builder
233      */
234     public JexlBuilder cache(final int size) {
235         this.cache = size;
236         return this;
237     }
238 
239     /**
240      * @return the cache factory
241      */
242     public IntFunction<JexlCache<?, ?>> cacheFactory() {
243       return this.cacheFactory;
244     }
245 
246     /**
247      * Sets the expression cache size the engine will use.
248      *
249      * @param factory the function to produce a cache.
250      * @return this builder
251      */
252     public JexlBuilder cacheFactory(final IntFunction<JexlCache<?, ?>> factory) {
253       this.cacheFactory = factory;
254       return this;
255     }
256 
257     /**
258      * @return the cache threshold
259      */
260     public int cacheThreshold() {
261         return cacheThreshold;
262     }
263 
264     /**
265      * Sets the maximum length for an expression to be cached.
266      * <p>Expression whose length is greater than this expression cache length threshold will
267      * bypass the cache.</p>
268      * <p>It is expected that a "long" script will be parsed once and its reference kept
269      * around in user-space structures; the jexl expression cache has no added-value in this case.</p>
270      *
271      * @param length if not strictly positive, the value is silently replaced by the default value (64).
272      * @return this builder
273      */
274     public JexlBuilder cacheThreshold(final int length) {
275         this.cacheThreshold = length > 0? length : CACHE_THRESHOLD;
276         return this;
277     }
278 
279     /**
280      * @return the cancellable information flag
281      * @since 3.1
282      */
283     public Boolean cancellable() {
284         return this.cancellable;
285     }
286 
287     /**
288      * Sets the engine behavior upon interruption: throw an JexlException.Cancel or terminates the current evaluation
289      * and return null.
290      *
291      * @param flag true implies the engine throws the exception, false makes the engine return null.
292      * @return this builder
293      * @since 3.1
294      */
295     public JexlBuilder cancellable(final boolean flag) {
296         this.cancellable = flag;
297         options.setCancellable(flag);
298         return this;
299     }
300 
301     /** @return the charset */
302     public Charset charset() {
303         return charset;
304     }
305 
306     /**
307      * Sets the charset to use.
308      *
309      * @param arg the charset
310      * @return this builder
311      * @since 3.1
312      */
313     public JexlBuilder charset(final Charset arg) {
314         this.charset = arg;
315         return this;
316     }
317 
318     /**
319      * @return true if variable collection follows strict syntactic rule
320      * @since 3.2
321      */
322     public boolean collectAll() {
323         return this.collectMode != 0;
324     }
325 
326     /**
327      * Sets whether the engine variable collectors considers all potential forms of variable syntaxes.
328      *
329      * @param flag true means var collections considers constant array accesses equivalent to dotted references
330      * @return this builder
331      * @since 3.2
332      */
333     public JexlBuilder collectAll(final boolean flag) {
334         return collectMode(flag? 1 : 0);
335     }
336 
337     /**
338      * @return 0 if variable collection follows strict syntactic rule
339      * @since 3.2
340      */
341     public int collectMode() {
342         return this.collectMode;
343     }
344 
345     /**
346      * Experimental collector mode setter.
347      *
348      * @param mode 0 or 1 as equivalents to false and true, other values are experimental
349      * @return this builder
350      * @since 3.2
351      */
352     public JexlBuilder collectMode(final int mode) {
353         this.collectMode = mode;
354         return this;
355     }
356 
357     /**
358      * @return a {@link JexlEngine} instance
359      */
360     public JexlEngine create() {
361         return new Engine(this);
362     }
363 
364     /** @return the debugging information flag */
365     public Boolean debug() {
366         return this.debug;
367     }
368 
369    /**
370  * Sets whether the engine will report debugging information when error occurs.
371  *
372  * @param flag true implies debug is on, false implies debug is off.
373  * @return this builder
374  */
375 public JexlBuilder debug(final boolean flag) {
376     this.debug = flag;
377     return this;
378 }
379 
380     /** @return the features */
381     public JexlFeatures features() {
382         return this.features;
383     }
384 
385     /**
386      * Sets the features the engine will use as a base by default.
387      * <p>Note that the script flag will be ignored; the engine will be able to parse expressions and scripts.
388      * <p>Note also that these will apply to template expressions and scripts.
389      * <p>As a last remark, if lexical or lexicalShade are set as features, this
390      * method will also set the corresponding options.
391      * @param f the features
392      * @return this builder
393      */
394     public JexlBuilder features(final JexlFeatures f) {
395         this.features = f;
396         if (features != null) {
397             if (features.isLexical()) {
398                 options.setLexical(true);
399             }
400             if (features.isLexicalShade()) {
401                 options.setLexicalShade(true);
402             }
403         }
404         return this;
405     }
406 
407     /**
408      * Gets the optional set of imported packages.
409      * @return the set of imports, may be empty, not null
410      */
411     public Collection<String> imports() {
412         return options.getImports();
413     }
414 
415     /**
416      * Sets the optional set of imports.
417      * @param imports the imported packages
418      * @return this builder
419      */
420     public JexlBuilder imports(final Collection<String> imports) {
421         options.setImports(imports);
422         return this;
423     }
424 
425     /**
426      * Sets the optional set of imports.
427      * @param imports the imported packages
428      * @return this builder
429      */
430     public JexlBuilder imports(final String... imports) {
431         return imports(Arrays.asList(imports));
432     }
433 
434     /** @return whether lexical scope is enabled */
435     public boolean lexical() {
436         return options.isLexical();
437     }
438 
439     /**
440      * Sets whether the engine is in lexical mode.
441      *
442      * @param flag true means lexical function scope is in effect, false implies non-lexical scoping
443      * @return this builder
444      * @since 3.2
445      */
446     public JexlBuilder lexical(final boolean flag) {
447         options.setLexical(flag);
448         return this;
449     }
450 
451     /** @return whether lexical shading is enabled */
452     public boolean lexicalShade() {
453         return options.isLexicalShade();
454     }
455 
456     /**
457      * Sets whether the engine is in lexical shading mode.
458      *
459      * @param flag true means lexical shading is in effect, false implies no lexical shading
460      * @return this builder
461      * @since 3.2
462      */
463     public JexlBuilder lexicalShade(final boolean flag) {
464         options.setLexicalShade(flag);
465         return this;
466     }
467 
468     /** @return the class loader */
469     public ClassLoader loader() {
470         return loader;
471     }
472 
473     /**
474      * Sets the charset to use.
475      *
476      * @param arg the charset
477      * @return this builder
478      * @deprecated since 3.1 use {@link #charset(Charset)} instead
479      */
480     @Deprecated
481     public JexlBuilder loader(final Charset arg) {
482         return charset(arg);
483     }
484 
485     /**
486      * Sets the class loader to use.
487      *
488      * @param l the class loader
489      * @return this builder
490      */
491     public JexlBuilder loader(final ClassLoader l) {
492         this.loader = l;
493         return this;
494     }
495 
496     /** @return the logger */
497     public Log logger() {
498         return this.logger;
499     }
500 
501     /**
502      * Sets the o.a.c.Log instance to use.
503      *
504      * @param log the logger
505      * @return this builder
506      */
507     public JexlBuilder logger(final Log log) {
508         this.logger = log;
509         return this;
510     }
511 
512     /**
513      * @return the map of namespaces.
514      */
515     public Map<String, Object> namespaces() {
516         return options.getNamespaces();
517     }
518 
519     /**
520      * Sets the default namespaces map the engine will use.
521      * <p>
522      * Each entry key is used as a prefix, each entry value used as a bean implementing
523      * methods; an expression like 'nsx:method(123)' will thus be solved by looking at
524      * a registered bean named 'nsx' that implements method 'method' in that map.
525      * If all methods are static, you may use the bean class instead of an instance as value.
526      * </p>
527      * <p>
528      * If the entry value is a class that has one constructor taking a JexlContext as argument, an instance
529      * of the namespace will be created at evaluation time. It might be a good idea to derive a JexlContext
530      * to carry the information used by the namespace to avoid variable space pollution and strongly type
531      * the constructor with this specialized JexlContext.
532      * </p>
533      * <p>
534      * The key or prefix allows to retrieve the bean that plays the role of the namespace.
535      * If the prefix is null, the namespace is the top-level namespace allowing to define
536      * top-level user-defined namespaces ( ie: myfunc(...) )
537      * </p>
538      * <p>Note that the JexlContext is also used to try to solve top-level namespaces. This allows ObjectContext
539      * derived instances to call methods on the wrapped object.</p>
540      *
541      * @param ns the map of namespaces
542      * @return this builder
543      */
544     public JexlBuilder namespaces(final Map<String, Object> ns) {
545         options.setNamespaces(ns);
546         return this;
547     }
548 
549     /** @return the current set of options */
550     public JexlOptions options() {
551         return options;
552     }
553 
554     /** @return the permissions */
555     public JexlPermissions permissions() {
556         return this.permissions;
557     }
558 
559     /**
560      * Sets the JexlPermissions instance the engine will use.
561      *
562      * @param p the permissions
563      * @return this builder
564      */
565     public JexlBuilder permissions(final JexlPermissions p) {
566         this.permissions = p;
567         return this;
568     }
569 
570     /** @return true if safe, false otherwise */
571     public Boolean safe() {
572         return options.isSafe();
573     }
574 
575     /**
576      * Sets whether the engine considers dereferencing null in navigation expressions
577      * as null or triggers an error.
578      * <p><code>x.y()</code> if x is null throws an exception when not safe,
579      * return null and warns if it is.</p>
580      * <p>It is recommended to use <em>safe(false)</em> as an explicit default.</p>
581      *
582      * @param flag true means safe navigation, false throws exception when dereferencing null
583      * @return this builder
584      */
585     public JexlBuilder safe(final boolean flag) {
586         options.setSafe(flag);
587         return this;
588     }
589 
590     /** @return the sandbox */
591     public JexlSandbox sandbox() {
592         return this.sandbox;
593     }
594 
595     /**
596      * Sets the sandbox the engine will use.
597      *
598      * @param box the sandbox
599      * @return this builder
600      */
601     public JexlBuilder sandbox(final JexlSandbox box) {
602         this.sandbox = box;
603         return this;
604     }
605 
606     /** @return the silent error handling flag */
607     public Boolean silent() {
608         return options.isSilent();
609     }
610 
611     /**
612      * Sets whether the engine will throw JexlException during evaluation when an error is triggered.
613      * <p>When <em>not</em> silent, the engine throws an exception when the evaluation triggers an exception or an
614      * error.</p>
615      * <p>It is recommended to use <em>silent(true)</em> as an explicit default.</p>
616      * @param flag true means no JexlException will occur, false allows them
617      * @return this builder
618      */
619     public JexlBuilder silent(final boolean flag) {
620         options.setSilent(flag);
621         return this;
622     }
623 
624     /**
625      * @return the cache size
626      */
627     public int stackOverflow() {
628         return stackOverflow;
629     }
630 
631     /**
632      * Sets the number of script/expression evaluations that can be stacked.
633      * @param size if not strictly positive, limit is reached when Java StackOverflow is thrown.
634      * @return this builder
635      */
636     public JexlBuilder stackOverflow(final int size) {
637         this.stackOverflow = size;
638         return this;
639     }
640 
641     /** @return the JexlUberspect strategy */
642     public JexlUberspect.ResolverStrategy strategy() {
643         return this.strategy;
644     }
645 
646     /**
647      * Sets the JexlUberspect strategy the engine will use.
648      * <p>This is ignored if the uberspect has been set.
649      *
650      * @param rs the strategy
651      * @return this builder
652      */
653     public JexlBuilder strategy(final JexlUberspect.ResolverStrategy rs) {
654         this.strategy = rs;
655         return this;
656     }
657 
658     /** @return true if strict, false otherwise */
659     public Boolean strict() {
660         return options.isStrict();
661     }
662 
663     /**
664      * Sets whether the engine considers unknown variables, methods, functions and constructors as errors or
665      * evaluates them as null.
666      * <p>When <em>not</em> strict, operators or functions using null operands return null on evaluation. When
667      * strict, those raise exceptions.</p>
668      * <p>It is recommended to use <em>strict(true)</em> as an explicit default.</p>
669      *
670      * @param flag true means strict error reporting, false allows them to be evaluated as null
671      * @return this builder
672      */
673     public JexlBuilder strict(final boolean flag) {
674         options.setStrict(flag);
675         return this;
676     }
677 
678     /** @return the uberspect */
679     public JexlUberspect uberspect() {
680         return this.uberspect;
681     }
682 
683     /**
684      * Sets the JexlUberspect instance the engine will use.
685      *
686      * @param u the uberspect
687      * @return this builder
688      */
689     public JexlBuilder uberspect(final JexlUberspect u) {
690         this.uberspect = u;
691         return this;
692     }
693 }