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