    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}