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