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.JexlSandbox; 022import org.apache.commons.jexl3.introspection.JexlUberspect; 023import org.apache.commons.logging.Log; 024 025import java.util.Map; 026import java.nio.charset.Charset; 027 028/** 029 * Configure and builds a JexlEngine. 030 * 031 * <p>The <code>setSilent</code> and <code>setStrict</code> methods allow to fine-tune an engine instance behavior 032 * according to various error control needs. The strict flag tells the engine when and if null as operand is 033 * considered an error, the silent flag tells the engine what to do with the error 034 * (log as warning or throw exception).</p> 035 * 036 * <ul> 037 * <li>When "silent" & "not-strict": 038 * <p> 0 & null should be indicators of "default" values so that even in an case of error, 039 * something meaningful can still be inferred; may be convenient for configurations. 040 * </p> 041 * </li> 042 * <li>When "silent" & "strict": 043 * <p>One should probably consider using null as an error case - ie, every object 044 * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form 045 * can be used to workaround exceptional cases. 046 * Use case could be configuration with no implicit values or defaults. 047 * </p> 048 * </li> 049 * <li>When "not-silent" & "not-strict": 050 * <p>The error control grain is roughly on par with JEXL 1.0</p> 051 * </li> 052 * <li>When "not-silent" & "strict": 053 * <p>The finest error control grain is obtained; it is the closest to Java code - 054 * still augmented by "script" capabilities regarding automated conversions and type matching. 055 * </p> 056 * </li> 057 * </ul> 058 */ 059public class JexlBuilder { 060 061 /** The default maximum expression length to hit the expression cache. */ 062 protected static final int CACHE_THRESHOLD = 64; 063 064 /** The JexlUberspect instance. */ 065 private JexlUberspect uberspect = null; 066 067 /** The strategy strategy. */ 068 private JexlUberspect.ResolverStrategy strategy = null; 069 070 /** The sandbox. */ 071 private JexlSandbox sandbox = null; 072 073 /** The Log to which all JexlEngine messages will be logged. */ 074 private Log logger = null; 075 076 /** Whether error messages will carry debugging information. */ 077 private Boolean debug = null; 078 079 /** Whether interrupt throws JexlException.Cancel. */ 080 private Boolean cancellable = null; 081 082 /** The options. */ 083 private final JexlOptions options = new JexlOptions(); 084 085 /** Whether getVariables considers all potential equivalent syntactic forms. */ 086 private int collectMode = 1; 087 088 /** The {@link JexlArithmetic} instance. */ 089 private JexlArithmetic arithmetic = null; 090 091 /** The cache size. */ 092 private int cache = -1; 093 094 /** The stack overflow limit. */ 095 private int stackOverflow = Integer.MAX_VALUE; 096 097 /** The maximum expression length to hit the expression cache. */ 098 private int cacheThreshold = CACHE_THRESHOLD; 099 100 /** The charset. */ 101 private Charset charset = Charset.defaultCharset(); 102 103 /** The class loader. */ 104 private ClassLoader loader = null; 105 106 /** The features. */ 107 private JexlFeatures features = null; 108 109 /** 110 * Sets the JexlUberspect instance the engine will use. 111 * 112 * @param u the uberspect 113 * @return this builder 114 */ 115 public JexlBuilder uberspect(final JexlUberspect u) { 116 this.uberspect = u; 117 return this; 118 } 119 120 /** @return the uberspect */ 121 public JexlUberspect uberspect() { 122 return this.uberspect; 123 } 124 125 /** 126 * Sets the JexlUberspect strategy strategy the engine will use. 127 * <p>This is ignored if the uberspect has been set. 128 * 129 * @param rs the strategy 130 * @return this builder 131 */ 132 public JexlBuilder strategy(final JexlUberspect.ResolverStrategy rs) { 133 this.strategy = rs; 134 return this; 135 } 136 137 /** @return the strategy strategy */ 138 public JexlUberspect.ResolverStrategy strategy() { 139 return this.strategy; 140 } 141 142 /** @return the current set of options */ 143 public JexlOptions options() { 144 return options; 145 } 146 147 /** 148 * Sets the JexlArithmetic instance the engine will use. 149 * 150 * @param a the arithmetic 151 * @return this builder 152 */ 153 public JexlBuilder arithmetic(final JexlArithmetic a) { 154 this.arithmetic = a; 155 options.setStrictArithmetic(a.isStrict()); 156 options.setMathContext(a.getMathContext()); 157 options.setMathScale(a.getMathScale()); 158 return this; 159 } 160 161 /** @return the arithmetic */ 162 public JexlArithmetic arithmetic() { 163 return this.arithmetic; 164 } 165 166 /** 167 * Sets the sandbox the engine will use. 168 * 169 * @param box the sandbox 170 * @return this builder 171 */ 172 public JexlBuilder sandbox(final JexlSandbox box) { 173 this.sandbox = box; 174 return this; 175 } 176 177 /** @return the sandbox */ 178 public JexlSandbox sandbox() { 179 return this.sandbox; 180 } 181 182 /** 183 * Sets the features the engine will use as a base by default. 184 * <p>Note that the script flag will be ignored; the engine will be able to parse expressions and scripts. 185 * <p>Note also that these will apply to template expressions and scripts. 186 * <p>As a last remark, if lexical or lexicalShade are set as features, this 187 * method will also set the corresponding options. 188 * @param f the features 189 * @return this builder 190 */ 191 public JexlBuilder features(final JexlFeatures f) { 192 this.features = f; 193 if (features != null) { 194 if (features.isLexical()) { 195 options.setLexical(true); 196 } 197 if (features.isLexicalShade()) { 198 options.setLexicalShade(true); 199 } 200 } 201 return this; 202 } 203 204 /** @return the features */ 205 public JexlFeatures features() { 206 return this.features; 207 } 208 209 /** 210 * Sets the o.a.c.Log instance to use. 211 * 212 * @param log the logger 213 * @return this builder 214 */ 215 public JexlBuilder logger(final Log log) { 216 this.logger = log; 217 return this; 218 } 219 220 /** @return the logger */ 221 public Log logger() { 222 return this.logger; 223 } 224 225 /** 226 * Sets the class loader to use. 227 * 228 * @param l the class loader 229 * @return this builder 230 */ 231 public JexlBuilder loader(final ClassLoader l) { 232 this.loader = l; 233 return this; 234 } 235 236 /** @return the class loader */ 237 public ClassLoader loader() { 238 return loader; 239 } 240 241 /** 242 * Sets the charset to use. 243 * 244 * @param arg the charset 245 * @return this builder 246 * @deprecated since 3.1 use {@link #charset(Charset)} instead 247 */ 248 @Deprecated 249 public JexlBuilder loader(final Charset arg) { 250 return charset(arg); 251 } 252 253 /** 254 * Sets the charset to use. 255 * 256 * @param arg the charset 257 * @return this builder 258 * @since 3.1 259 */ 260 public JexlBuilder charset(final Charset arg) { 261 this.charset = arg; 262 return this; 263 } 264 265 /** @return the charset */ 266 public Charset charset() { 267 return charset; 268 } 269 270 /** 271 * Sets whether the engine will resolve antish variable names. 272 * 273 * @param flag true means antish resolution is enabled, false disables it 274 * @return this builder 275 */ 276 public JexlBuilder antish(final boolean flag) { 277 options.setAntish(flag); 278 return this; 279 } 280 281 /** @return whether antish resolution is enabled */ 282 public boolean antish() { 283 return options.isAntish(); 284 } 285 286 /** 287 * Sets whether the engine is in lexical mode. 288 * 289 * @param flag true means lexical function scope is in effect, false implies non-lexical scoping 290 * @return this builder 291 * @since 3.2 292 */ 293 public JexlBuilder lexical(final boolean flag) { 294 options.setLexical(flag); 295 return this; 296 } 297 298 /** @return whether lexical scope is enabled */ 299 public boolean lexical() { 300 return options.isLexical(); 301 } 302 303 /** 304 * Sets whether the engine is in lexical shading mode. 305 * 306 * @param flag true means lexical shading is in effect, false implies no lexical shading 307 * @return this builder 308 * @since 3.2 309 */ 310 public JexlBuilder lexicalShade(final boolean flag) { 311 options.setLexicalShade(flag); 312 return this; 313 } 314 315 /** @return whether lexical shading is enabled */ 316 public boolean lexicalShade() { 317 return options.isLexicalShade(); 318 } 319 320 /** 321 * Sets whether the engine will throw JexlException during evaluation when an error is triggered. 322 * 323 * @param flag true means no JexlException will occur, false allows them 324 * @return this builder 325 */ 326 public JexlBuilder silent(final boolean flag) { 327 options.setSilent(flag); 328 return this; 329 } 330 331 /** @return the silent error handling flag */ 332 public Boolean silent() { 333 return options.isSilent(); 334 } 335 336 /** 337 * Sets whether the engine considers unknown variables, methods, functions and constructors as errors or 338 * evaluates them as null. 339 * 340 * @param flag true means strict error reporting, false allows them to be evaluated as null 341 * @return this builder 342 */ 343 public JexlBuilder strict(final boolean flag) { 344 options.setStrict(flag); 345 return this; 346 } 347 348 /** @return true if strict, false otherwise */ 349 public Boolean strict() { 350 return options.isStrict(); 351 } 352 353 /** 354 * Sets whether the engine considers dereferencing null in navigation expressions 355 * as errors or evaluates them as null. 356 * <p><code>x.y()</code> if x is null throws an exception when not safe, 357 * return null and warns if it is.<p> 358 * 359 * @param flag true means safe navigation, false throws exception when dereferencing null 360 * @return this builder 361 */ 362 public JexlBuilder safe(final boolean flag) { 363 options.setSafe(flag); 364 return this; 365 } 366 367 /** @return true if safe, false otherwise */ 368 public Boolean safe() { 369 return options.isSafe(); 370 } 371 372 /** 373 * Sets whether the engine will report debugging information when error occurs. 374 * 375 * @param flag true implies debug is on, false implies debug is off. 376 * @return this builder 377 */ 378 public JexlBuilder debug(final boolean flag) { 379 this.debug = flag; 380 return this; 381 } 382 383 /** @return the debugging information flag */ 384 public Boolean debug() { 385 return this.debug; 386 } 387 388 /** 389 * Sets the engine behavior upon interruption: throw an JexlException.Cancel or terminates the current evaluation 390 * and return null. 391 * 392 * @param flag true implies the engine throws the exception, false makes the engine return null. 393 * @return this builder 394 * @since 3.1 395 */ 396 public JexlBuilder cancellable(final boolean flag) { 397 this.cancellable = flag; 398 options.setCancellable(flag); 399 return this; 400 } 401 402 /** 403 * @return the cancellable information flag 404 * @since 3.1 405 */ 406 public Boolean cancellable() { 407 return this.cancellable; 408 } 409 410 /** 411 * Sets whether the engine variable collectors considers all potential forms of variable syntaxes. 412 * 413 * @param flag true means var collections considers constant array accesses equivalent to dotted references 414 * @return this builder 415 * @since 3.2 416 */ 417 public JexlBuilder collectAll(final boolean flag) { 418 return collectMode(flag? 1 : 0); 419 } 420 421 /** 422 * Experimental collector mode setter. 423 * 424 * @param mode 0 or 1 as equivalents to false and true, other values are experimental 425 * @return this builder 426 * @since 3.2 427 */ 428 public JexlBuilder collectMode(final int mode) { 429 this.collectMode = mode; 430 return this; 431 } 432 433 /** 434 * @return true if variable collection follows strict syntactic rule 435 * @since 3.2 436 */ 437 public boolean collectAll() { 438 return this.collectMode != 0; 439 } 440 441 /** 442 * @return 0 if variable collection follows strict syntactic rule 443 * @since 3.2 444 */ 445 public int collectMode() { 446 return this.collectMode; 447 } 448 449 /** 450 * Sets the default namespaces map the engine will use. 451 * <p> 452 * Each entry key is used as a prefix, each entry value used as a bean implementing 453 * methods; an expression like 'nsx:method(123)' will thus be solved by looking at 454 * a registered bean named 'nsx' that implements method 'method' in that map. 455 * If all methods are static, you may use the bean class instead of an instance as value. 456 * </p> 457 * <p> 458 * If the entry value is a class that has one constructor taking a JexlContext as argument, an instance 459 * of the namespace will be created at evaluation time. It might be a good idea to derive a JexlContext 460 * to carry the information used by the namespace to avoid variable space pollution and strongly type 461 * the constructor with this specialized JexlContext. 462 * </p> 463 * <p> 464 * The key or prefix allows to retrieve the bean that plays the role of the namespace. 465 * If the prefix is null, the namespace is the top-level namespace allowing to define 466 * top-level user defined namespaces ( ie: myfunc(...) ) 467 * </p> 468 * <p>Note that the JexlContext is also used to try to solve top-level namespaces. This allows ObjectContext 469 * derived instances to call methods on the wrapped object.</p> 470 * 471 * @param ns the map of namespaces 472 * @return this builder 473 */ 474 public JexlBuilder namespaces(final Map<String, Object> ns) { 475 options.setNamespaces(ns); 476 return this; 477 } 478 479 /** 480 * @return the map of namespaces. 481 */ 482 public Map<String, Object> namespaces() { 483 return options.getNamespaces(); 484 } 485 486 /** 487 * Sets the expression cache size the engine will use. 488 * <p>The cache will contain at most <code>size</code> expressions of at most <code>cacheThreshold</code> length. 489 * Note that all JEXL caches are held through SoftReferences and may be garbage-collected.</p> 490 * 491 * @param size if not strictly positive, no cache is used. 492 * @return this builder 493 */ 494 public JexlBuilder cache(final int size) { 495 this.cache = size; 496 return this; 497 } 498 499 /** 500 * @return the cache size 501 */ 502 public int cache() { 503 return cache; 504 } 505 506 /** 507 * Sets the maximum length for an expression to be cached. 508 * <p>Expression whose length is greater than this expression cache length threshold will 509 * bypass the cache.</p> 510 * <p>It is expected that a "long" script will be parsed once and its reference kept 511 * around in user-space structures; the jexl expression cache has no added-value in this case.</p> 512 * 513 * @param length if not strictly positive, the value is silently replaced by the default value (64). 514 * @return this builder 515 */ 516 public JexlBuilder cacheThreshold(final int length) { 517 this.cacheThreshold = length > 0? length : CACHE_THRESHOLD; 518 return this; 519 } 520 521 /** 522 * @return the cache threshold 523 */ 524 public int cacheThreshold() { 525 return cacheThreshold; 526 } 527 528 /** 529 * Sets the number of script/expression evaluations that can be stacked. 530 * @param size if not strictly positive, limit is reached when java StackOverflow is thrown. 531 * @return this builder 532 */ 533 public JexlBuilder stackOverflow(final int size) { 534 this.stackOverflow = size; 535 return this; 536 } 537 538 /** 539 * @return the cache size 540 */ 541 public int stackOverflow() { 542 return stackOverflow; 543 } 544 545 /** 546 * @return a {@link JexlEngine} instance 547 */ 548 public JexlEngine create() { 549 return new Engine(this); 550 } 551}