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    *      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.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          * Retrieves 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, may be 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     /**
316      * Clears the cache.
317      */
318     public abstract void clearCache();
319 
320     /**
321      * Creates a {@link Expression} from an expression string.
322      * Uses and fills up the expression cache if any.
323      *
324      * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
325      *
326      * @param info the {@link JexlInfo} source information
327      * @param expression the {@link Template} string expression
328      * @return the {@link Expression}, null if silent and an error occurred
329      * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
330      */
331     public abstract Expression createExpression(JexlInfo info, String expression);
332 
333     /**
334      * Creates a {@link Expression} from an expression string.
335      * Uses and fills up the expression cache if any.
336      *
337      * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p>
338      *
339      * @param expression the {@link Template} string expression
340      * @return the {@link Expression}, null if silent and an error occurred
341      * @throws Exception if an error occurs and the {@link JexlEngine} is not silent
342      */
343     public Expression createExpression(final String expression) {
344         return createExpression(null, expression);
345     }
346 
347     /**
348      * Creates a new template.
349      *
350      * @param info the source info
351      * @param source the source
352      * @return the template
353      */
354     public Template createTemplate(final JexlInfo info, final String source) {
355         return createTemplate(info, "$$", new StringReader(source), (String[]) null);
356     }
357 
358     /**
359      * Creates a new template.
360      *
361      * @param info the jexl info (file, line, column)
362      * @param prefix the directive prefix
363      * @param source the source
364      * @param parms the parameter names
365      * @return the template
366      */
367     public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms);
368 
369     /**
370      * Creates a new template.
371      *
372      * @param info the source info
373      * @param parms the parameter names
374      * @param source the source
375      * @return the template
376      */
377     public Template createTemplate(final JexlInfo info, final String source, final String... parms) {
378         return createTemplate(info, "$$", new StringReader(source), parms);
379     }
380 
381     /**
382      * Creates a new template.
383      *
384      * @param source the source
385      * @return the template
386      */
387     public Template createTemplate(final String source) {
388         return createTemplate(null, source);
389     }
390 
391     /**
392      * Creates a new template.
393      *
394      * @param prefix the directive prefix
395      * @param source the source
396      * @param parms the parameter names
397      * @return the template
398      */
399     public Template createTemplate(final String prefix, final Reader source, final String... parms) {
400         return createTemplate(null, prefix, source, parms);
401     }
402 
403     /**
404      * Creates a new template.
405      *
406      * @param source the source
407      * @param parms the parameter names
408      * @return the template
409      */
410     public Template createTemplate(final String source, final String... parms) {
411         return createTemplate(null, source, parms);
412     }
413 
414     /**
415      * Gets the {@link JexlEngine} underlying this template engine.
416      *
417      * @return the JexlEngine
418      */
419     public abstract JexlEngine getEngine();
420 }