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