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 * 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"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}