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 java.nio.charset.Charset;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Map;
024import java.util.function.IntFunction;
025
026import org.apache.commons.jexl3.internal.Engine;
027import org.apache.commons.jexl3.internal.SoftCache;
028import org.apache.commons.jexl3.introspection.JexlPermissions;
029import org.apache.commons.jexl3.introspection.JexlSandbox;
030import org.apache.commons.jexl3.introspection.JexlUberspect;
031import org.apache.commons.logging.Log;
032
033/**
034 * Configures and builds a JexlEngine.
035 *
036 * <p>
037 *     The builder allow fine-tuning an engine instance behavior according to various control needs.
038 *     Check <em>{@link #JexlBuilder()}</em> for permission impacts starting with <em>JEXL 3.3</em>.
039 * </p><p>
040 *     Broad configurations elements are controlled through the features ({@link JexlFeatures}) that can restrict JEXL
041 *  syntax - for instance, only expressions with no-side effects - and permissions ({@link JexlPermissions}) that control
042 *  the visible set of objects - for instance, avoiding access to any object in java.rmi.* -.
043 *  </p><p>
044 *     Fine error control and runtime-overridable behaviors are implemented through options ({@link JexlOptions}). Most
045 * common flags accessible from the builder are reflected in its options ({@link #options()}).
046 * </p><p>
047 *     The <code>silent</code> flag tells the engine what to do with the error; when true, errors are logged as
048 * warning, when false, they throw {@link JexlException} exceptions.
049 * </p><p>
050 *     The <code>strict</code> flag tells the engine when and if null as operand is considered an error. The <code>safe</code>
051 * flog determines if safe-navigation is used. Safe-navigation allows an  evaluation shortcut and return null in expressions
052 * that attempts dereferencing null, typically a method call or accessing a property.
053 * </p><p>
054 *     The <code>lexical</code> and <code>lexicalShade</code> flags can be used to enforce a lexical scope for
055 * variables and parameters. The <code>lexicalShade</code> can be used to further ensure no global variable can be
056 * used with the same name as a local one even after it goes out of scope. The corresponding feature flags should be
057 * preferred since they will detect violations at parsing time. (see {@link JexlFeatures})
058 * </p><p>
059 *     The following rules apply on silent and strict flags:
060 * </p>
061 * <ul>
062 * <li>When "silent" &amp; "not-strict":
063 * <p> 0 &amp; null should be indicators of "default" values so that even in an case of error,
064 * something meaningful can still be inferred; may be convenient for configurations.
065 * </p>
066 * </li>
067 * <li>When "silent" &amp; "strict":
068 * <p>One should probably consider using null as an error case - ie, every object
069 * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
070 * can be used to workaround exceptional cases.
071 * Use case could be configuration with no implicit values or defaults.
072 * </p>
073 * </li>
074 * <li>When "not-silent" &amp; "not-strict":
075 * <p>The error control grain is roughly on par with JEXL 1.0</p>
076 * </li>
077 * <li>When "not-silent" &amp; "strict":
078 * <p>The finest error control grain is obtained; it is the closest to Java code -
079 * still augmented by "script" capabilities regarding automated conversions and type matching.
080 * </p>
081 * </li>
082 * </ul>
083 */
084public class JexlBuilder {
085    /**
086     * The set of default permissions used when creating a new builder.
087     * <p>Static but modifiable so these default permissions can be changed to a purposeful set.</p>
088     * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p>
089     * <p>In JEXL 3.2, these were equivalent to {@link JexlPermissions#UNRESTRICTED}.</p>
090     */
091    private static JexlPermissions PERMISSIONS = JexlPermissions.RESTRICTED;
092
093    /** The default maximum expression length to hit the expression cache. */
094    protected static final int CACHE_THRESHOLD = 64;
095
096    /**
097     * Sets the default permissions.
098     * @param permissions the permissions
099     */
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 */
375public 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}