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 org.apache.commons.jexl3.introspection.JexlUberspect; 21 22 import java.io.BufferedReader; 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.IOException; 26 import java.io.InputStreamReader; 27 import java.math.MathContext; 28 import java.net.URL; 29 import java.nio.charset.Charset; 30 31 /** 32 * Creates and evaluates JexlExpression and JexlScript objects. 33 * Determines the behavior of expressions and scripts during their evaluation with respect to: 34 * <ul> 35 * <li>Introspection, see {@link JexlUberspect}</li> 36 * <li>Arithmetic and comparison, see {@link JexlArithmetic}</li> 37 * <li>Error reporting</li> 38 * <li>Logging</li> 39 * </ul> 40 * 41 * <p>Note that methods that evaluate expressions may throw <em>unchecked</em> exceptions; 42 * The {@link JexlException} are thrown in "non-silent" mode but since these are 43 * RuntimeException, user-code <em>should</em> catch them wherever most appropriate.</p> 44 * 45 * @since 2.0 46 */ 47 public abstract class JexlEngine { 48 49 /** A marker singleton for invocation failures in tryInvoke. */ 50 public static final Object TRY_FAILED = new FailObject(); 51 52 /** The failure marker class. */ 53 private static final class FailObject { 54 /** 55 * Default ctor. 56 */ 57 FailObject() {} 58 59 @Override 60 public String toString() { 61 return "tryExecute failed"; 62 } 63 } 64 65 /** 66 * The thread local context. 67 */ 68 protected static final java.lang.ThreadLocal<JexlContext.ThreadLocal> CONTEXT = 69 new java.lang.ThreadLocal<>(); 70 71 /** 72 * Accesses the current thread local context. 73 * 74 * @return the context or null 75 */ 76 public static JexlContext.ThreadLocal getThreadContext() { 77 return CONTEXT.get(); 78 } 79 80 /** 81 * The thread local engine. 82 */ 83 protected static final java.lang.ThreadLocal<JexlEngine> ENGINE = 84 new java.lang.ThreadLocal<>(); 85 86 /** 87 * Accesses the current thread local engine. 88 * <p>Advanced: you should only use this to retrieve the engine within a method/ctor called through the evaluation 89 * of a script/expression.</p> 90 * @return the engine or null 91 */ 92 public static JexlEngine getThreadEngine() { 93 return ENGINE.get(); 94 } 95 96 /** 97 * Sets the current thread local context. 98 * <p>This should only be used carefully, for instance when re-evaluating a "stored" script that requires a 99 * given Namespace resolver. Remember to synchronize access if context is shared between threads. 100 * 101 * @param tls the thread local context to set 102 */ 103 public static void setThreadContext(final JexlContext.ThreadLocal tls) { 104 CONTEXT.set(tls); 105 } 106 107 /** 108 * Script evaluation options. 109 * <p>The JexlContext used for evaluation can implement this interface to alter behavior.</p> 110 * @deprecated 3.2 111 */ 112 @Deprecated 113 public interface Options { 114 115 /** 116 * The charset used for parsing. 117 * 118 * @return the charset 119 */ 120 Charset getCharset(); 121 /** 122 * Sets whether the engine will throw a {@link JexlException} when an error is encountered during evaluation. 123 * 124 * @return true if silent, false otherwise 125 */ 126 Boolean isSilent(); 127 128 /** 129 * Checks whether the engine considers unknown variables, methods, functions and constructors as errors or 130 * evaluates them as null. 131 * 132 * @return true if strict, false otherwise 133 */ 134 Boolean isStrict(); 135 136 /** 137 * Checks whether the arithmetic triggers errors during evaluation when null is used as an operand. 138 * 139 * @return true if strict, false otherwise 140 */ 141 Boolean isStrictArithmetic(); 142 143 /** 144 * Whether evaluation will throw JexlException.Cancel (true) or return null (false) when interrupted. 145 * @return true when cancellable, false otherwise 146 * @since 3.1 147 */ 148 Boolean isCancellable(); 149 150 /** 151 * The MathContext instance used for +,-,/,*,% operations on big decimals. 152 * 153 * @return the math context 154 */ 155 MathContext getArithmeticMathContext(); 156 157 /** 158 * The BigDecimal scale used for comparison and coercion operations. 159 * 160 * @return the scale 161 */ 162 int getArithmeticMathScale(); 163 } 164 165 /** Default features. */ 166 public static final JexlFeatures DEFAULT_FEATURES = new JexlFeatures(); 167 168 /** 169 * An empty/static/non-mutable JexlContext singleton used instead of null context. 170 */ 171 public static final JexlContext EMPTY_CONTEXT = new EmptyContext(); 172 173 /** 174 * The empty context class, public for instrospection. 175 */ 176 public static final class EmptyContext implements JexlContext { 177 /** 178 * Default ctor. 179 */ 180 EmptyContext() {} 181 182 @Override 183 public Object get(final String name) { 184 return null; 185 } 186 187 @Override 188 public boolean has(final String name) { 189 return false; 190 } 191 192 @Override 193 public void set(final String name, final Object value) { 194 throw new UnsupportedOperationException("Not supported in void context."); 195 } 196 } 197 198 /** 199 * An empty/static/non-mutable JexlNamespace singleton used instead of null namespace. 200 */ 201 public static final JexlContext.NamespaceResolver EMPTY_NS = new EmptyNamespaceResolver(); 202 203 /** 204 * The empty/static/non-mutable JexlNamespace class, public for instrospection. 205 */ 206 public static final class EmptyNamespaceResolver implements JexlContext.NamespaceResolver { 207 /** 208 * Default ctor. 209 */ 210 EmptyNamespaceResolver() {} 211 212 @Override 213 public Object resolveNamespace(final String name) { 214 return null; 215 } 216 } 217 218 /** The default Jxlt cache size. */ 219 private static final int JXLT_CACHE_SIZE = 256; 220 221 /** 222 * Gets the charset used for parsing. 223 * 224 * @return the charset 225 */ 226 public abstract Charset getCharset(); 227 228 /** 229 * Gets this engine underlying {@link JexlUberspect}. 230 * 231 * @return the uberspect 232 */ 233 public abstract JexlUberspect getUberspect(); 234 235 /** 236 * Gets this engine underlying {@link JexlArithmetic}. 237 * 238 * @return the arithmetic 239 */ 240 public abstract JexlArithmetic getArithmetic(); 241 242 /** 243 * Checks whether this engine is in debug mode. 244 * 245 * @return true if debug is on, false otherwise 246 */ 247 public abstract boolean isDebug(); 248 249 /** 250 * Checks whether this engine throws JexlException during evaluation. 251 * 252 * @return true if silent, false (default) otherwise 253 */ 254 public abstract boolean isSilent(); 255 256 /** 257 * Checks whether this engine considers unknown variables, methods, functions and constructors as errors. 258 * 259 * @return true if strict, false otherwise 260 */ 261 public abstract boolean isStrict(); 262 263 /** 264 * Checks whether this engine will throw JexlException.Cancel (true) or return null (false) when interrupted 265 * during an execution. 266 * 267 * @return true if cancellable, false otherwise 268 */ 269 public abstract boolean isCancellable(); 270 271 /** 272 * Sets the class loader used to discover classes in 'new' expressions. 273 * <p>This method is <em>not</em> thread safe; it may be called after JexlEngine 274 * initialization and allow scripts to use new classes definitions.</p> 275 * 276 * @param loader the class loader to use 277 */ 278 public abstract void setClassLoader(ClassLoader loader); 279 280 /** 281 * Creates a new {@link JxltEngine} instance using this engine. 282 * 283 * @return a JEXL Template engine 284 */ 285 public JxltEngine createJxltEngine() { 286 return createJxltEngine(true); 287 } 288 289 /** 290 * Creates a new {@link JxltEngine} instance using this engine. 291 * 292 * @param noScript whether the JxltEngine only allows Jexl expressions or scripts 293 * @return a JEXL Template engine 294 */ 295 public JxltEngine createJxltEngine(final boolean noScript) { 296 return createJxltEngine(noScript, JXLT_CACHE_SIZE, '$', '#'); 297 } 298 299 /** 300 * Creates a new instance of {@link JxltEngine} using this engine. 301 * 302 * @param noScript whether the JxltEngine only allows JEXL expressions or scripts 303 * @param cacheSize the number of expressions in this cache, default is 256 304 * @param immediate the immediate template expression character, default is '$' 305 * @param deferred the deferred template expression character, default is '#' 306 * @return a JEXL Template engine 307 */ 308 public abstract JxltEngine createJxltEngine(boolean noScript, int cacheSize, char immediate, char deferred); 309 310 /** 311 * Clears the expression cache. 312 */ 313 public abstract void clearCache(); 314 315 /** 316 * Creates an JexlExpression from a String containing valid JEXL syntax. 317 * This method parses the expression which must contain either a reference or an expression. 318 * 319 * @param info An info structure to carry debugging information if needed 320 * @param expression A String containing valid JEXL syntax 321 * @return An {@link JexlExpression} which can be evaluated using a {@link JexlContext} 322 * @throws JexlException if there is a problem parsing the script 323 */ 324 public abstract JexlExpression createExpression(JexlInfo info, String expression); 325 326 /** 327 * Creates a JexlExpression from a String containing valid JEXL syntax. 328 * This method parses the expression which must contain either a reference or an expression. 329 * 330 * @param expression A String containing valid JEXL syntax 331 * @return An {@link JexlExpression} which can be evaluated using a {@link JexlContext} 332 * @throws JexlException if there is a problem parsing the script 333 */ 334 public final JexlExpression createExpression(final String expression) { 335 return createExpression(null, expression); 336 } 337 /** 338 * Creates a JexlScript from a String containing valid JEXL syntax. 339 * This method parses the script and validates the syntax. 340 * 341 * @param features A set of features that will be enforced during parsing 342 * @param info An info structure to carry debugging information if needed 343 * @param source A string containing valid JEXL syntax 344 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 345 * values should be used during evaluation 346 * @return A {@link JexlScript} which can be executed using a {@link JexlContext} 347 * @throws JexlException if there is a problem parsing the script 348 */ 349 public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String... names); 350 351 /** 352 * Creates a JexlScript from a String containing valid JEXL syntax. 353 * This method parses the script and validates the syntax. 354 * 355 * @param info An info structure to carry debugging information if needed 356 * @param source A string containing valid JEXL syntax 357 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 358 * values should be used during evaluation 359 * @return A {@link JexlScript} which can be executed using a {@link JexlContext} 360 * @throws JexlException if there is a problem parsing the script 361 */ 362 public final JexlScript createScript(final JexlInfo info, final String source, final String... names) { 363 return createScript(null, info, source, names); 364 } 365 366 /** 367 * Creates a Script from a String containing valid JEXL syntax. 368 * This method parses the script and validates the syntax. 369 * 370 * @param scriptText A String containing valid JEXL syntax 371 * @return A {@link JexlScript} which can be executed using a {@link JexlContext} 372 * @throws JexlException if there is a problem parsing the script. 373 */ 374 public final JexlScript createScript(final String scriptText) { 375 return createScript(null, null, scriptText, (String[]) null); 376 } 377 378 /** 379 * Creates a Script from a String containing valid JEXL syntax. 380 * This method parses the script and validates the syntax. 381 * 382 * @param source A String containing valid JEXL syntax 383 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 384 * values should be used during evaluation 385 * @return A {@link JexlScript} which can be executed using a {@link JexlContext} 386 * @throws JexlException if there is a problem parsing the script 387 */ 388 public final JexlScript createScript(final String source, final String... names) { 389 return createScript(null, null, source, names); 390 } 391 392 /** 393 * Creates a Script from a {@link File} containing valid JEXL syntax. 394 * This method parses the script and validates the syntax. 395 * 396 * @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file. 397 * @return A {@link JexlScript} which can be executed with a {@link JexlContext}. 398 * @throws JexlException if there is a problem reading or parsing the script. 399 */ 400 public final JexlScript createScript(final File scriptFile) { 401 return createScript(null, null, readSource(scriptFile), (String[]) null); 402 } 403 404 /** 405 * Creates a Script from a {@link File} containing valid JEXL syntax. 406 * This method parses the script and validates the syntax. 407 * 408 * @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file. 409 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 410 * values should be used during evaluation. 411 * @return A {@link JexlScript} which can be executed with a {@link JexlContext}. 412 * @throws JexlException if there is a problem reading or parsing the script. 413 */ 414 public final JexlScript createScript(final File scriptFile, final String... names) { 415 return createScript(null, null, readSource(scriptFile), names); 416 } 417 418 /** 419 * Creates a Script from a {@link File} containing valid JEXL syntax. 420 * This method parses the script and validates the syntax. 421 * 422 * @param info An info structure to carry debugging information if needed 423 * @param scriptFile A {@link File} containing valid JEXL syntax. Must not be null. Must be a readable file. 424 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 425 * values should be used during evaluation. 426 * @return A {@link JexlScript} which can be executed with a {@link JexlContext}. 427 * @throws JexlException if there is a problem reading or parsing the script. 428 */ 429 public final JexlScript createScript(final JexlInfo info, final File scriptFile, final String... names) { 430 return createScript(null, info, readSource(scriptFile), names); 431 } 432 433 /** 434 * Creates a Script from a {@link URL} containing valid JEXL syntax. 435 * This method parses the script and validates the syntax. 436 * 437 * @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null. 438 * @return A {@link JexlScript} which can be executed with a {@link JexlContext}. 439 * @throws JexlException if there is a problem reading or parsing the script. 440 */ 441 public final JexlScript createScript(final URL scriptUrl) { 442 return createScript(null, readSource(scriptUrl), (String[]) null); 443 } 444 445 /** 446 * Creates a Script from a {@link URL} containing valid JEXL syntax. 447 * This method parses the script and validates the syntax. 448 * 449 * @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null. 450 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 451 * values should be used during evaluation. 452 * @return A {@link JexlScript} which can be executed with a {@link JexlContext}. 453 * @throws JexlException if there is a problem reading or parsing the script. 454 */ 455 public final JexlScript createScript(final URL scriptUrl, final String... names) { 456 return createScript(null, null, readSource(scriptUrl), names); 457 } 458 459 /** 460 * Creates a Script from a {@link URL} containing valid JEXL syntax. 461 * This method parses the script and validates the syntax. 462 * 463 * @param info An info structure to carry debugging information if needed 464 * @param scriptUrl A {@link URL} containing valid JEXL syntax. Must not be null. 465 * @param names The script parameter names used during parsing; a corresponding array of arguments containing 466 * values should be used during evaluation. 467 * @return A {@link JexlScript} which can be executed with a {@link JexlContext}. 468 * @throws JexlException if there is a problem reading or parsing the script. 469 */ 470 public final JexlScript createScript(final JexlInfo info, final URL scriptUrl, final String... names) { 471 return createScript(null, info, readSource(scriptUrl), names); 472 } 473 474 /** 475 * Accesses properties of a bean using an expression. 476 * <p> 477 * jexl.get(myobject, "foo.bar"); should equate to 478 * myobject.getFoo().getBar(); (or myobject.getFoo().get("bar")) 479 * </p> 480 * <p> 481 * If the JEXL engine is silent, errors will be logged through its logger as warning. 482 * </p> 483 * 484 * @param bean the bean to get properties from 485 * @param expr the property expression 486 * @return the value of the property 487 * @throws JexlException if there is an error parsing the expression or during evaluation 488 */ 489 public abstract Object getProperty(Object bean, String expr); 490 491 /** 492 * Accesses properties of a bean using an expression. 493 * <p> 494 * If the JEXL engine is silent, errors will be logged through its logger as warning. 495 * </p> 496 * 497 * @param context the evaluation context 498 * @param bean the bean to get properties from 499 * @param expr the property expression 500 * @return the value of the property 501 * @throws JexlException if there is an error parsing the expression or during evaluation 502 */ 503 public abstract Object getProperty(JexlContext context, Object bean, String expr); 504 505 /** 506 * Assign properties of a bean using an expression. 507 * <p> 508 * jexl.set(myobject, "foo.bar", 10); should equate to 509 * myobject.getFoo().setBar(10); (or myobject.getFoo().put("bar", 10) ) 510 * </p> 511 * <p> 512 * If the JEXL engine is silent, errors will be logged through its logger as warning. 513 * </p> 514 * 515 * @param bean the bean to set properties in 516 * @param expr the property expression 517 * @param value the value of the property 518 * @throws JexlException if there is an error parsing the expression or during evaluation 519 */ 520 public abstract void setProperty(Object bean, String expr, Object value); 521 522 /** 523 * Assign properties of a bean using an expression. <p> If the JEXL engine is silent, errors will be logged through 524 * its logger as warning. </p> 525 * 526 * @param context the evaluation context 527 * @param bean the bean to set properties in 528 * @param expr the property expression 529 * @param value the value of the property 530 * @throws JexlException if there is an error parsing the expression or during evaluation 531 */ 532 public abstract void setProperty(JexlContext context, Object bean, String expr, Object value); 533 534 /** 535 * Invokes an object's method by name and arguments. 536 * 537 * @param obj the method's invoker object 538 * @param meth the method's name 539 * @param args the method's arguments 540 * @return the method returned value or null if it failed and engine is silent 541 * @throws JexlException if method could not be found or failed and engine is not silent 542 */ 543 public abstract Object invokeMethod(Object obj, String meth, Object... args); 544 545 /** 546 * Creates a new instance of an object using the most appropriate constructor based on the arguments. 547 * 548 * @param <T> the type of object 549 * @param clazz the class to instantiate 550 * @param args the constructor arguments 551 * @return the created object instance or null on failure when silent 552 */ 553 public abstract <T> T newInstance(Class<? extends T> clazz, Object... args); 554 555 /** 556 * Creates a new instance of an object using the most appropriate constructor based on the arguments. 557 * 558 * @param clazz the name of the class to instantiate resolved through this engine's class loader 559 * @param args the constructor arguments 560 * @return the created object instance or null on failure when silent 561 */ 562 public abstract Object newInstance(String clazz, Object... args); 563 564 /** 565 * Creates a JexlInfo instance. 566 * 567 * @param fn url/file/template/script user given name 568 * @param l line number 569 * @param c column number 570 * @return a JexlInfo instance 571 */ 572 public JexlInfo createInfo(final String fn, final int l, final int c) { 573 return new JexlInfo(fn, l, c); 574 } 575 576 /** 577 * Create an information structure for dynamic set/get/invoke/new. 578 * <p>This gathers the class, method and line number of the first calling method 579 * outside of o.a.c.jexl3.</p> 580 * 581 * @return a JexlInfo instance 582 */ 583 public JexlInfo createInfo() { 584 return new JexlInfo(); 585 } 586 587 /** 588 * Creates a string from a reader. 589 * 590 * @param reader to be read. 591 * @return the contents of the reader as a String. 592 * @throws IOException on any error reading the reader. 593 */ 594 protected static String toString(final BufferedReader reader) throws IOException { 595 final StringBuilder buffer = new StringBuilder(); 596 String line; 597 while ((line = reader.readLine()) != null) { 598 buffer.append(line).append('\n'); 599 } 600 return buffer.toString(); 601 } 602 603 /** 604 * Reads a JEXL source from a File. 605 * 606 * @param file the script file 607 * @return the source 608 */ 609 protected String readSource(final File file) { 610 if (file == null) { 611 throw new NullPointerException("source file is null"); 612 } 613 try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), 614 getCharset()))) { 615 return toString(reader); 616 } catch (final IOException xio) { 617 throw new JexlException(createInfo(file.toString(), 1, 1), "could not read source File", xio); 618 } 619 } 620 621 /** 622 * Reads a JEXL source from an URL. 623 * 624 * @param url the script url 625 * @return the source 626 */ 627 protected String readSource(final URL url) { 628 if (url == null) { 629 throw new NullPointerException("source URL is null"); 630 } 631 try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), getCharset()))) { 632 return toString(reader); 633 } catch (final IOException xio) { 634 throw new JexlException(createInfo(url.toString(), 1, 1), "could not read source URL", xio); 635 } 636 } 637 }