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 }