001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.jexl3;
019
020import org.apache.commons.jexl3.internal.Engine;
021import org.apache.commons.jexl3.introspection.JexlPermissions;
022import org.apache.commons.jexl3.introspection.JexlSandbox;
023import org.apache.commons.jexl3.introspection.JexlUberspect;
024import org.apache.commons.logging.Log;
025
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Map;
029import java.nio.charset.Charset;
030
031/**
032 * Configures and builds a JexlEngine.
033 *
034 * <p>
035 *     The builder allow fine-tuning an engine instance behavior according to various control needs.
036 *     Check <em>{@link #JexlBuilder()}</em> for permission impacts starting with <em>JEXL 3.3</em>.
037 * </p><p>
038 *     Broad configurations elements are controlled through the features ({@link JexlFeatures}) that can restrict JEXL
039 *  syntax - for instance, only expressions with no-side effects - and permissions ({@link JexlPermissions}) that control
040 *  the visible set of objects - for instance, avoiding access to any object in java.rmi.* -.
041 *  </p><p>
042 *     Fine error control and runtime-overridable behaviors are implemented through options ({@link JexlOptions}). Most
043 * common flags accessible from the builder are reflected in its options ({@link #options()}).
044 * </p><p>
045 *     The <code>silent</code> flag tells the engine what to do with the error; when true, errors are logged as
046 * warning, when false, they throw {@link JexlException} exceptions.
047 * </p><p>
048 *     The <code>strict</code> flag tells the engine when and if null as operand is considered an error. The <code>safe</code>
049 * flog determines if safe-navigation is used. Safe-navigation allows an  evaluation shortcut and return null in expressions
050 * that attempts dereferencing null, typically a method call or accessing a property.
051 * </p><p>
052 *     The <code>lexical</code> and <code>lexicalShade</code> flags can be used to enforce a lexical scope for
053 * variables and parameters. The <code>lexicalShade</code> can be used to further ensure no global variable can be
054 * used with the same name as a local one even after it goes out of scope. The corresponding feature flags should be
055 * preferred since they will detect violations at parsing time. (see {@link JexlFeatures})
056 * </p><p>
057 *     The following rules apply on silent and strict flags:
058 * </p>
059 * <ul>
060 * <li>When "silent" &amp; "not-strict":
061 * <p> 0 &amp; null should be indicators of "default" values so that even in an case of error,
062 * something meaningful can still be inferred; may be convenient for configurations.
063 * </p>
064 * </li>
065 * <li>When "silent" &amp; "strict":
066 * <p>One should probably consider using null as an error case - ie, every object
067 * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
068 * can be used to workaround exceptional cases.
069 * Use case could be configuration with no implicit values or defaults.
070 * </p>
071 * </li>
072 * <li>When "not-silent" &amp; "not-strict":
073 * <p>The error control grain is roughly on par with JEXL 1.0</p>
074 * </li>
075 * <li>When "not-silent" &amp; "strict":
076 * <p>The finest error control grain is obtained; it is the closest to Java code -
077 * still augmented by "script" capabilities regarding automated conversions and type matching.
078 * </p>
079 * </li>
080 * </ul>
081 */
082public class JexlBuilder {
083    /**
084     * The set of default permissions used when creating a new builder.
085     * <p>Static but modifiable so these default permissions can be changed to a purposeful set.</p>
086     * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p>
087     * <p>In JEXL 3.2, these were equivalent to {@link JexlPermissions#UNRESTRICTED}.</p>
088     */
089    private static JexlPermissions PERMISSIONS = JexlPermissions.RESTRICTED;
090
091    /**
092     * Sets the default permissions.
093     * @param permissions the permissions
094     */
095    public static void setDefaultPermissions(final JexlPermissions permissions) {
096        PERMISSIONS = permissions == null? JexlPermissions.RESTRICTED : permissions;
097    }
098
099    /** 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}