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