View Javadoc
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    *      https://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.Reader;
21  import java.io.StringReader;
22  import java.io.Writer;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  /**
28   * A simple "JeXL Template" engine.
29   *
30   * <p>At the base is an evaluator similar to the Unified EL evaluator used in JSP/JSF based on JEXL.
31   * At the top is a template engine inspired by Velocity that uses JEXL (instead of OGNL/VTL) as the scripting
32   * language.</p>
33   *
34   * <p>The evaluator is intended to be used in configuration modules, XML based frameworks or JSP taglibs
35   * and facilitate the implementation of expression evaluation.</p>
36   *
37   * <p>The template engine is intended to output any form of text; html, XML, CSV...</p>
38   *
39   * @since 3.0
40   */
41  public abstract class JxltEngine {
42  
43      /** Default constructor */
44      public JxltEngine() {} // Keep Javadoc happy
45  
46      /**
47       * The sole type of (runtime) exception the JxltEngine can throw.
48       */
49      public static class Exception extends JexlException {
50  
51          /** Serial version UID. */
52          private static final long serialVersionUID = 201112030113L;
53  
54          /**
55           * Creates an Exception.
56           *
57           * @param info the contextual information
58           * @param msg the exception message
59           * @param cause the exception cause
60           */
61          public Exception(final JexlInfo info, final String msg, final Throwable cause) {
62              super(info, msg, cause);
63          }
64      }
65  
66      /**
67       * A unified expression that can mix immediate, deferred and nested sub-expressions as well as string constants;
68       * <ul>
69       *   <li>The "immediate" syntax is of the form <code>"...${jexl-expr}..."</code></li>
70       *   <li>The "deferred" syntax is of the form <code>"...#{jexl-expr}..."</code></li>
71       *   <li>The "nested" syntax is of the form <code>"...#{...${jexl-expr0}...}..."</code></li>
72       *   <li>The "composite" syntax is of the form <code>"...${jexl-expr0}... #{jexl-expr1}..."</code></li>
73       * </ul>
74       *
75       * <p>Deferred and immediate expression carry different intentions:</p>
76       *
77       * <ul>
78       *   <li>An immediate expression indicate that evaluation is intended to be performed close to
79       *       the definition/parsing point.</li>
80       *   <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li>
81       * </ul>
82       *
83       * <p>For instance: <code>"Hello ${name}, now is #{time}"</code> is a composite "deferred" expression since one
84       * of its subexpressions is deferred. Furthermore, this (composite) expression intent is
85       * to perform two evaluations; one close to its definition and another one in a later
86       * phase.</p>
87       *
88       * <p>The API reflects this feature in 2 methods, prepare and evaluate. The prepare method
89       * will evaluate the immediate subexpression and return an expression that contains only
90       * the deferred subexpressions (and constants), a prepared expression. Such a prepared expression
91       * is suitable for a later phase evaluation that may occur with a different JexlContext.
92       * Note that it is valid to call evaluate without prepare in which case the same JexlContext
93       * is used for the 2 evaluation phases.</p>
94       *
95       * <p>In the most common use-case where deferred expressions are to be kept around as properties of objects,
96       * one should createExpression and prepare an expression before storing it and evaluate it each time
97       * the property storing it is accessed.</p>
98       *
99       * <p>Note that nested expression use the JEXL syntax as in:</p>
100      *
101      * <blockquote><code>"#{${bar}+'.charAt(2)'}"</code></blockquote>
102      *
103      * <p>The most common mistake leading to an invalid expression being the following:</p>
104      *
105      * <blockquote><code>"#{${bar}charAt(2)}"</code></blockquote>
106      *
107      * <p>Also note that methods that createExpression evaluate expressions may throw <em>unchecked</em> exceptions;
108      * The {@link JxltEngine.Exception} are thrown when the engine instance is in "non-silent" mode
109      * but since these are RuntimeException, user-code <em>should</em> catch them where appropriate.</p>
110      *
111      * @since 2.0
112      */
113     public interface Expression {
114 
115         /**
116          * Generates this expression's string representation.
117          *
118          * @return the string representation
119          */
120         String asString();
121 
122         /**
123          * Adds this expression's string representation to a StringBuilder.
124          *
125          * @param strb the builder to fill
126          * @return the builder argument
127          */
128         StringBuilder asString(StringBuilder strb);
129 
130         /**
131          * Evaluates this expression.
132          *
133          * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warning.</p>
134          *
135          * @param context the variable context
136          * @return the result of this expression evaluation or null if an error occurs and the {@link JexlEngine} is
137          * running in silent mode
138          * @throws Exception if an error occurs and the {@link JexlEngine}
139          * is not silent
140          */
141         Object evaluate(JexlContext context);
142 
143         /**
144          * Gets this expression's source expression.
145          * <p>
146          * If this expression was prepared, this allows to retrieve the
147          * original expression that lead to it.</p>
148          * <p>Other expressions return themselves.</p>
149          *
150          * @return the source expression
151          */
152         Expression getSource();
153 
154         /**
155          * Gets the list of variables accessed by this expression.
156          * <p>This method will visit all nodes of the sub-expressions and extract all variables whether they
157          * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p>
158          *
159          * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
160          * or the empty set if no variables are used
161          */
162         Set<List<String>> getVariables();
163 
164         /**
165          * Checks whether this expression is deferred.
166          *
167          * @return true if deferred, false otherwise
168          */
169         boolean isDeferred();
170 
171         /**
172          * Checks whether this expression is immediate.
173          *
174          * @return true if immediate, false otherwise
175          */
176         boolean isImmediate();
177 
178         /**
179          * Evaluates the immediate sub-expressions.
180          *
181          * <p>When the expression is dependant upon immediate and deferred sub-expressions,
182          * evaluates the immediate sub-expressions with the context passed as parameter
183          * and returns this expression deferred form.</p>
184          *
185          * <p>In effect, this binds the result of the immediate sub-expressions evaluation in the
186          * context, allowing to differ evaluation of the remaining (deferred) expression within another context.
187          * This only has an effect to nested and composite expressions that contain differed and
188          * immediate sub-expressions.</p>
189          *
190          * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warning.* </p>
191          *
192          * @param context the context to use for immediate expression evaluations
193          * @return an {@link Expression} or null if an error occurs and the {@link JexlEngine} is running
194          * in silent mode
195          * @throws Exception if an error occurs and the {@link JexlEngine} is not in silent mode
196          */
197         Expression prepare(JexlContext context);
198 
199         /**
200          * Formats this expression, adding its source string representation in
201          * comments if available: 'expression /*= source *\/'' .
202          *
203          * @return the formatted expression string
204          */
205         @Override
206         String toString();
207     }
208 
209     /**
210      * A template is a JEXL script that evaluates by writing its content through a Writer.
211      * <p>
212      * The source text is parsed considering each line beginning with '$$' (as default pattern) as JEXL script code
213      * and all others as Unified JEXL expressions; those expressions will be invoked from the script during
214      * evaluation and their output gathered through a writer.
215      * It is thus possible to use looping or conditional construct "around" expressions generating output.
216      * </p>
217      * For instance:
218      * <blockquote><pre>{@code
219      * $$ for (var x : [1, 3, 5, 42, 169]) {
220      * $$   if (x == 42) {
221      * Life, the universe, and everything
222      * $$   } else if (x > 42) {
223      * The value $(x} is over forty-two
224      * $$   } else {
225      * The value ${x} is under forty-two
226      * $$   }
227      * $$ }
228      * }</pre></blockquote>
229      *
230      * <p>Will evaluate as:</p>
231      *
232      * <blockquote><pre>
233      * The value 1 is under forty-two
234      * The value 3 is under forty-two
235      * The value 5 is under forty-two
236      * Life, the universe, and everything
237      * The value 169 is over forty-two
238      * </pre></blockquote>
239      *
240      * <p>During evaluation, the template context exposes its writer as '$jexl' which is safe to use in this case.
241      * This allows writing directly through the writer without adding new-lines as in:</p>
242      *
243      * <blockquote><pre>
244      * $$ for(var cell : cells) { $jexl.print(cell); $jexl.print(';') }
245      * </pre></blockquote>
246      *
247      * <p>A template is expanded as one JEXL script and a list of template expressions; each template expression is
248      * being replaced in the script by a call to jexl:print(expr) (the expr is in fact the expr number in the template).
249      * This integration uses a specialized JexlContext (TemplateContext) that serves as a namespace (for jexl:)
250      * and stores the template expression array and the writer (java.io.Writer) that the 'jexl:print(...)'
251      * delegates the output generation to.</p>
252      *
253      * @since 3.0
254      */
255     public interface Template {
256 
257         /**
258          * Recreate the template source from its inner components.
259          *
260          * @return the template source rewritten
261          */
262         String asString();
263 
264         /**
265          * Evaluates this template.
266          *
267          * @param context the context to use during evaluation
268          * @param writer the writer to use for output
269          */
270         void evaluate(JexlContext context, Writer writer);
271 
272         /**
273          * Evaluates this template.
274          *
275          * @param context the context to use during evaluation
276          * @param writer the writer to use for output
277          * @param args the arguments
278          */
279         void evaluate(JexlContext context, Writer writer, Object... args);
280 
281         /**
282          * Gets the list of parameters expected by this template.
283          *
284          * @return the parameter names array
285          */
286         String[] getParameters();
287 
288         /**
289          * Gets this script pragmas.
290          *
291          * @return the (non-null, possibly empty) pragmas map
292          * @since 3.1
293          */
294         Map<String, Object> getPragmas();
295 
296         /**
297          * Gets the list of variables accessed by this template.
298          * <p>This method will visit all nodes of the sub-expressions and extract all variables whether they
299          * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p>
300          *
301          * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string)
302          * or the empty set if no variables are used
303          */
304         Set<List<String>> getVariables();
305 
306         /**
307          * Prepares this template by expanding any contained deferred TemplateExpression.
308          *
309          * @param context the context to prepare against
310          * @return the prepared version of the template
311          */
312         Template prepare(JexlContext context);
313 
314         /**
315          * Prepares this template by expanding any contained deferred TemplateExpression with optional arguments.
316          * <p>This binds arguments to template parameters for immediate expressions when the template also
317          * uses deferred/nested expressions.</p>
318          *
319          * @param context the context to prepare against
320          * @param args the arguments to bind (optional)
321          * @return the prepared version of the template
322          * @since 3.6.2
323          */
324         default Template prepare(JexlContext context, Object... args) {
325             throw new UnsupportedOperationException(
326                     "This template implementation does not support prepare with arguments. "
327                     + "Override this method to provide support.");
328         }
329     }
330 
331     /**
332      * Clears the cache.
333      */
334     public abstract void clearCache();
335 
336     /**
337      * Creates a {@link Expression} from an expression string.
338      * Uses and fills up the expression cache if any.
339      *
340      * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
341      *
342      * @param info the {@link JexlInfo} source information
343      * @param expression the {@link Template} string expression
344      * @return the {@link Expression}, null if silent and an error occurred
345      * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
346      */
347     public abstract Expression createExpression(JexlInfo info, String expression);
348 
349     /**
350      * Creates a {@link Expression} from an expression string.
351      * Uses and fills up the expression cache if any.
352      *
353      * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
354      *
355      * @param expression the {@link Template} string expression
356      * @return the {@link Expression}, null if silent and an error occurred
357      * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
358      */
359     public Expression createExpression(final String expression) {
360         return createExpression(null, expression);
361     }
362 
363     /**
364      * Creates a new template.
365      *
366      * @param info the source info
367      * @param source the source
368      * @return the template
369      */
370     public Template createTemplate(final JexlInfo info, final String source) {
371         return createTemplate(info, "$$", new StringReader(source), (String[]) null);
372     }
373 
374     /**
375      * Creates a new template.
376      *
377      * @param info the jexl info (file, line, column)
378      * @param prefix the directive prefix
379      * @param source the source
380      * @param parms the parameter names
381      * @return the template
382      */
383     public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms);
384 
385     /**
386      * Creates a new template.
387      *
388      * @param info the source info
389      * @param parms the parameter names
390      * @param source the source
391      * @return the template
392      */
393     public Template createTemplate(final JexlInfo info, final String source, final String... parms) {
394         return createTemplate(info, "$$", new StringReader(source), parms);
395     }
396 
397     /**
398      * Creates a new template.
399      *
400      * @param source the source
401      * @return the template
402      */
403     public Template createTemplate(final String source) {
404         return createTemplate(null, source);
405     }
406 
407     /**
408      * Creates a new template.
409      *
410      * @param prefix the directive prefix
411      * @param source the source
412      * @param parms the parameter names
413      * @return the template
414      */
415     public Template createTemplate(final String prefix, final Reader source, final String... parms) {
416         return createTemplate(null, prefix, source, parms);
417     }
418 
419     /**
420      * Creates a new template.
421      *
422      * @param source the source
423      * @param parms the parameter names
424      * @return the template
425      */
426     public Template createTemplate(final String source, final String... parms) {
427         return createTemplate(null, source, parms);
428     }
429 
430     /**
431      * Gets the {@link JexlEngine} underlying this template engine.
432      *
433      * @return the JexlEngine
434      */
435     public abstract JexlEngine getEngine();
436 }