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