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 }