1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.jexl3;
19
20 import java.nio.charset.Charset;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Map;
24 import java.util.function.IntFunction;
25 import java.util.function.Supplier;
26
27 import org.apache.commons.jexl3.internal.Engine;
28 import org.apache.commons.jexl3.internal.SoftCache;
29 import org.apache.commons.jexl3.introspection.JexlPermissions;
30 import org.apache.commons.jexl3.introspection.JexlSandbox;
31 import org.apache.commons.jexl3.introspection.JexlUberspect;
32 import org.apache.commons.jexl3.parser.JexlScriptParser;
33 import org.apache.commons.logging.Log;
34
35 /**
36 * Configures and builds a JexlEngine.
37 *
38 * <p>
39 * The builder allow fine-tuning an engine instance behavior according to various control needs.
40 * Check <em>{@link #JexlBuilder()}</em> for permission impacts starting with <em>JEXL 3.3</em>.
41 * </p><p>
42 * Broad configurations elements are controlled through the features ({@link JexlFeatures}) that can restrict JEXL
43 * syntax - for instance, only expressions with no-side effects - and permissions ({@link JexlPermissions}) that control
44 * the visible set of objects - for instance, avoiding access to any object in java.rmi.* -.
45 * </p><p>
46 * Fine error control and runtime-overridable behaviors are implemented through options ({@link JexlOptions}). Most
47 * common flags accessible from the builder are reflected in its options ({@link #options()}).
48 * </p><p>
49 * The {@code silent} flag tells the engine what to do with the error; when true, errors are logged as
50 * warning, when false, they throw {@link JexlException} exceptions.
51 * </p><p>
52 * The {@code strict} flag tells the engine when and if null as operand is considered an error. The {@code safe}
53 * flog determines if safe-navigation is used. Safe-navigation allows an evaluation shortcut and return null in expressions
54 * that attempts dereferencing null, typically a method call or accessing a property.
55 * </p><p>
56 * The {@code lexical} and {@code lexicalShade} flags can be used to enforce a lexical scope for
57 * variables and parameters. The {@code lexicalShade} can be used to further ensure no global variable can be
58 * used with the same name as a local one even after it goes out of scope. The corresponding feature flags should be
59 * preferred since they will detect violations at parsing time. (see {@link JexlFeatures})
60 * </p><p>
61 * The following rules apply on silent and strict flags:
62 * </p>
63 * <ul>
64 * <li>When "silent" & "not-strict":
65 * <p> 0 & null should be indicators of "default" values so that even in an case of error,
66 * something meaningful can still be inferred; may be convenient for configurations.
67 * </p>
68 * </li>
69 * <li>When "silent" & "strict":
70 * <p>One should probably consider using null as an error case - ie, every object
71 * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
72 * can be used to workaround exceptional cases.
73 * Use case could be configuration with no implicit values or defaults.
74 * </p>
75 * </li>
76 * <li>When "not-silent" & "not-strict":
77 * <p>The error control grain is roughly on par with JEXL 1.0</p>
78 * </li>
79 * <li>When "not-silent" & "strict":
80 * <p>The finest error control grain is obtained; it is the closest to Java code -
81 * still augmented by "script" capabilities regarding automated conversions and type matching.
82 * </p>
83 * </li>
84 * </ul>
85 */
86 public class JexlBuilder {
87 /**
88 * The set of default permissions used when creating a new builder.
89 * <p>Static but modifiable so these default permissions can be changed to a purposeful set.</p>
90 * <p>In JEXL 3.3, these are {@link JexlPermissions#RESTRICTED}.</p>
91 * <p>In JEXL 3.2, these were equivalent to {@link JexlPermissions#UNRESTRICTED}.</p>
92 */
93 private static JexlPermissions PERMISSIONS = JexlPermissions.RESTRICTED;
94
95 /** The default maximum expression length to hit the expression cache. */
96 protected static final int CACHE_THRESHOLD = 64;
97
98 /**
99 * 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"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 ("" , ||) 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 }