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