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}