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