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