001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.jexl3; 019 020import java.io.Reader; 021import java.io.StringReader; 022import java.io.Writer; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027/** 028 * A simple "JeXL Template" engine. 029 * 030 * <p>At the base is an evaluator similar to the Unified EL evaluator used in JSP/JSF based on JEXL. 031 * At the top is a template engine inspired by Velocity that uses JEXL (instead of OGNL/VTL) as the scripting 032 * language.</p> 033 * 034 * <p>The evaluator is intended to be used in configuration modules, XML based frameworks or JSP taglibs 035 * and facilitate the implementation of expression evaluation.</p> 036 * 037 * <p>The template engine is intended to output any form of text; html, XML, CSV...</p> 038 * 039 * @since 3.0 040 */ 041public abstract class JxltEngine { 042 043 /** Default constructor */ 044 public JxltEngine() {} // Keep Javadoc happy 045 046 /** 047 * The sole type of (runtime) exception the JxltEngine can throw. 048 */ 049 public static class Exception extends JexlException { 050 051 /** Serial version UID. */ 052 private static final long serialVersionUID = 201112030113L; 053 054 /** 055 * Creates an Exception. 056 * 057 * @param info the contextual information 058 * @param msg the exception message 059 * @param cause the exception cause 060 */ 061 public Exception(final JexlInfo info, final String msg, final Throwable cause) { 062 super(info, msg, cause); 063 } 064 } 065 066 /** 067 * A unified expression that can mix immediate, deferred and nested sub-expressions as well as string constants; 068 * <ul> 069 * <li>The "immediate" syntax is of the form <code>"...${jexl-expr}..."</code></li> 070 * <li>The "deferred" syntax is of the form <code>"...#{jexl-expr}..."</code></li> 071 * <li>The "nested" syntax is of the form <code>"...#{...${jexl-expr0}...}..."</code></li> 072 * <li>The "composite" syntax is of the form <code>"...${jexl-expr0}... #{jexl-expr1}..."</code></li> 073 * </ul> 074 * 075 * <p>Deferred and immediate expression carry different intentions:</p> 076 * 077 * <ul> 078 * <li>An immediate expression indicate that evaluation is intended to be performed close to 079 * the definition/parsing point.</li> 080 * <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li> 081 * </ul> 082 * 083 * <p>For instance: <code>"Hello ${name}, now is #{time}"</code> is a composite "deferred" expression since one 084 * of its subexpressions is deferred. Furthermore, this (composite) expression intent is 085 * to perform two evaluations; one close to its definition and another one in a later 086 * phase.</p> 087 * 088 * <p>The API reflects this feature in 2 methods, prepare and evaluate. The prepare method 089 * will evaluate the immediate subexpression and return an expression that contains only 090 * the deferred subexpressions (and constants), a prepared expression. Such a prepared expression 091 * is suitable for a later phase evaluation that may occur with a different JexlContext. 092 * Note that it is valid to call evaluate without prepare in which case the same JexlContext 093 * is used for the 2 evaluation phases.</p> 094 * 095 * <p>In the most common use-case where deferred expressions are to be kept around as properties of objects, 096 * one should createExpression and prepare an expression before storing it and evaluate it each time 097 * the property storing it is accessed.</p> 098 * 099 * <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}