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 java.io.BufferedReader;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.math.MathContext;
25 import java.net.URL;
26 import java.nio.charset.Charset;
27 import java.nio.file.Files;
28 import java.util.Objects;
29
30 import org.apache.commons.jexl3.introspection.JexlUberspect;
31
32 /**
33 * Creates and evaluates JexlExpression and JexlScript objects.
34 * Determines the behavior of expressions and scripts during their evaluation with respect to:
35 * <ul>
36 * <li>Introspection, see {@link JexlUberspect}</li>
37 * <li>Arithmetic and comparison, see {@link JexlArithmetic}</li>
38 * <li>Error reporting</li>
39 * <li>Logging</li>
40 * </ul>
41 *
42 * <p>Note that methods that evaluate expressions may throw <em>unchecked</em> exceptions;
43 * The {@link JexlException} are thrown in "non-silent" mode but since these are
44 * RuntimeException, user-code <em>should</em> catch them wherever most appropriate.</p>
45 *
46 * @since 2.0
47 */
48 public abstract class JexlEngine {
49
50 /** Default constructor */
51 public JexlEngine() {} // Keep Javadoc happy
52
53 /**
54 * The empty context class, public for instrospection.
55 */
56 public static final class EmptyContext implements JexlContext {
57 /**
58 * Default ctor.
59 */
60 EmptyContext() {}
61
62 @Override
63 public Object get(final String name) {
64 return null;
65 }
66
67 @Override
68 public boolean has(final String name) {
69 return false;
70 }
71
72 @Override
73 public void set(final String name, final Object value) {
74 throw new UnsupportedOperationException("Not supported in void context.");
75 }
76 }
77
78 /**
79 * The empty/static/non-mutable JexlNamespace class, public for instrospection.
80 */
81 public static final class EmptyNamespaceResolver implements JexlContext.NamespaceResolver {
82 /**
83 * Default ctor.
84 */
85 EmptyNamespaceResolver() {}
86
87 @Override
88 public Object resolveNamespace(final String name) {
89 return null;
90 }
91 }
92
93 /** The failure marker class. */
94 private static final class FailObject {
95 /**
96 * Default ctor.
97 */
98 FailObject() {}
99
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 }