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 /** 044 * The sole type of (runtime) exception the JxltEngine can throw. 045 */ 046 public static class Exception extends JexlException { 047 048 /** Serial version UID. */ 049 private static final long serialVersionUID = 201112030113L; 050 051 /** 052 * Creates an Exception. 053 * 054 * @param info the contextual information 055 * @param msg the exception message 056 * @param cause the exception cause 057 */ 058 public Exception(final JexlInfo info, final String msg, final Throwable cause) { 059 super(info, msg, cause); 060 } 061 } 062 063 /** 064 * A unified expression that can mix immediate, deferred and nested sub-expressions as well as string constants; 065 * <ul> 066 * <li>The "immediate" syntax is of the form <code>"...${jexl-expr}..."</code></li> 067 * <li>The "deferred" syntax is of the form <code>"...#{jexl-expr}..."</code></li> 068 * <li>The "nested" syntax is of the form <code>"...#{...${jexl-expr0}...}..."</code></li> 069 * <li>The "composite" syntax is of the form <code>"...${jexl-expr0}... #{jexl-expr1}..."</code></li> 070 * </ul> 071 * 072 * <p>Deferred and immediate expression carry different intentions:</p> 073 * 074 * <ul> 075 * <li>An immediate expression indicate that evaluation is intended to be performed close to 076 * the definition/parsing point.</li> 077 * <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li> 078 * </ul> 079 * 080 * <p>For instance: <code>"Hello ${name}, now is #{time}"</code> is a composite "deferred" expression since one 081 * of its subexpressions is deferred. Furthermore, this (composite) expression intent is 082 * to perform two evaluations; one close to its definition and another one in a later 083 * phase.</p> 084 * 085 * <p>The API reflects this feature in 2 methods, prepare and evaluate. The prepare method 086 * will evaluate the immediate subexpression and return an expression that contains only 087 * the deferred subexpressions (and constants), a prepared expression. Such a prepared expression 088 * is suitable for a later phase evaluation that may occur with a different JexlContext. 089 * Note that it is valid to call evaluate without prepare in which case the same JexlContext 090 * is used for the 2 evaluation phases.</p> 091 * 092 * <p>In the most common use-case where deferred expressions are to be kept around as properties of objects, 093 * one should createExpression and prepare an expression before storing it and evaluate it each time 094 * the property storing it is accessed.</p> 095 * 096 * <p>Note that nested expression use the JEXL syntax as in:</p> 097 * 098 * <blockquote><code>"#{${bar}+'.charAt(2)'}"</code></blockquote> 099 * 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 * A template is a JEXL script that evaluates by writing its content through a Writer. 208 * <p> 209 * The source text is parsed considering each line beginning with '$$' (as default pattern) as JEXL script code 210 * and all others as Unified JEXL expressions; those expressions will be invoked from the script during 211 * evaluation and their output gathered through a writer. 212 * It is thus possible to use looping or conditional construct "around" expressions generating output. 213 * </p> 214 * For instance: 215 * <blockquote><pre> 216 * $$ for(var x : [1, 3, 5, 42, 169]) { 217 * $$ if (x == 42) { 218 * Life, the universe, and everything 219 * $$ } else if (x > 42) { 220 * The value $(x} is over forty-two 221 * $$ } else { 222 * The value ${x} is under forty-two 223 * $$ } 224 * $$ } 225 * </pre></blockquote> 226 * 227 * <p>Will evaluate as:</p> 228 * 229 * <blockquote><pre> 230 * The value 1 is under forty-two 231 * The value 3 is under forty-two 232 * The value 5 is under forty-two 233 * Life, the universe, and everything 234 * The value 169 is over forty-two 235 * </pre></blockquote> 236 * 237 * <p>During evaluation, the template context exposes its writer as '$jexl' which is safe to use in this case. 238 * This allows writing directly through the writer without adding new-lines as in:</p> 239 * 240 * <blockquote><pre> 241 * $$ for(var cell : cells) { $jexl.print(cell); $jexl.print(';') } 242 * </pre></blockquote> 243 * 244 * <p>A template is expanded as one JEXL script and a list of template expressions; each template expression is 245 * being replaced in the script by a call to jexl:print(expr) (the expr is in fact the expr number in the template). 246 * This integration uses a specialized JexlContext (TemplateContext) that serves as a namespace (for jexl:) 247 * and stores the template expression array and the writer (java.io.Writer) that the 'jexl:print(...)' 248 * delegates the output generation to.</p> 249 * 250 * @since 3.0 251 */ 252 public interface Template { 253 254 /** 255 * Recreate the template source from its inner components. 256 * 257 * @return the template source rewritten 258 */ 259 String asString(); 260 261 /** 262 * Evaluates this template. 263 * 264 * @param context the context to use during evaluation 265 * @param writer the writer to use for output 266 */ 267 void evaluate(JexlContext context, Writer writer); 268 269 /** 270 * Evaluates this template. 271 * 272 * @param context the context to use during evaluation 273 * @param writer the writer to use for output 274 * @param args the arguments 275 */ 276 void evaluate(JexlContext context, Writer writer, Object... args); 277 278 /** 279 * Gets the list of parameters expected by this template. 280 * 281 * @return the parameter names array 282 */ 283 String[] getParameters(); 284 285 /** 286 * Gets this script pragmas. 287 * 288 * @return the (non null, may be empty) pragmas map 289 * @since 3.1 290 */ 291 Map<String, Object> getPragmas(); 292 293 /** 294 * Gets the list of variables accessed by this template. 295 * <p>This method will visit all nodes of the sub-expressions and extract all variables whether they 296 * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p> 297 * 298 * @return the set of variables, each as a list of strings (ant-ish variables use more than 1 string) 299 * or the empty set if no variables are used 300 */ 301 Set<List<String>> getVariables(); 302 303 /** 304 * Prepares this template by expanding any contained deferred TemplateExpression. 305 * 306 * @param context the context to prepare against 307 * @return the prepared version of the template 308 */ 309 Template prepare(JexlContext context); 310 } 311 312 /** 313 * Clears the cache. 314 */ 315 public abstract void clearCache(); 316 317 /** 318 * Creates a {@link Expression} from an expression string. 319 * Uses and fills up the expression cache if any. 320 * 321 * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p> 322 * 323 * @param info the {@link JexlInfo} source information 324 * @param expression the {@link Template} string expression 325 * @return the {@link Expression}, null if silent and an error occurred 326 * @throws Exception if an error occurs and the {@link JexlEngine} is not silent 327 */ 328 public abstract Expression createExpression(JexlInfo info, String expression); 329 330 /** 331 * Creates a {@link Expression} from an expression string. 332 * Uses and fills up the expression cache if any. 333 * 334 * <p>If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.</p> 335 * 336 * @param expression the {@link Template} string expression 337 * @return the {@link Expression}, null if silent and an error occurred 338 * @throws Exception if an error occurs and the {@link JexlEngine} is not silent 339 */ 340 public Expression createExpression(final String expression) { 341 return createExpression(null, expression); 342 } 343 344 /** 345 * Creates a new template. 346 * 347 * @param info the source info 348 * @param source the source 349 * @return the template 350 */ 351 public Template createTemplate(final JexlInfo info, final String source) { 352 return createTemplate(info, "$$", new StringReader(source), (String[]) null); 353 } 354 355 /** 356 * Creates a new template. 357 * 358 * @param info the jexl info (file, line, column) 359 * @param prefix the directive prefix 360 * @param source the source 361 * @param parms the parameter names 362 * @return the template 363 */ 364 public abstract Template createTemplate(JexlInfo info, String prefix, Reader source, String... parms); 365 366 /** 367 * Creates a new template. 368 * 369 * @param info the source info 370 * @param parms the parameter names 371 * @param source the source 372 * @return the template 373 */ 374 public Template createTemplate(final JexlInfo info, final String source, final String... parms) { 375 return createTemplate(info, "$$", new StringReader(source), parms); 376 } 377 378 /** 379 * Creates a new template. 380 * 381 * @param source the source 382 * @return the template 383 */ 384 public Template createTemplate(final String source) { 385 return createTemplate(null, source); 386 } 387 388 /** 389 * Creates a new template. 390 * 391 * @param prefix the directive prefix 392 * @param source the source 393 * @param parms the parameter names 394 * @return the template 395 */ 396 public Template createTemplate(final String prefix, final Reader source, final String... parms) { 397 return createTemplate(null, prefix, source, parms); 398 } 399 400 /** 401 * Creates a new template. 402 * 403 * @param source the source 404 * @param parms the parameter names 405 * @return the template 406 */ 407 public Template createTemplate(final String source, final String... parms) { 408 return createTemplate(null, source, parms); 409 } 410 411 /** 412 * Gets the {@link JexlEngine} underlying this template engine. 413 * 414 * @return the JexlEngine 415 */ 416 public abstract JexlEngine getEngine(); 417}