1 /*
2 * Copyright (c) 2003-2006 The Visigoth Software Society. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The end-user documentation included with the redistribution, if
18 * any, must include the following acknowledgement:
19 * "This product includes software developed by the
20 * Visigoth Software Society (http://www.visigoths.org/)."
21 * Alternately, this acknowledgement may appear in the software itself,
22 * if and wherever such third-party acknowledgements normally appear.
23 *
24 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25 * project contributors may be used to endorse or promote products derived
26 * from this software without prior written permission. For written
27 * permission, please contact visigoths@visigoths.org.
28 *
29 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30 * nor may "FreeMarker" or "Visigoth" appear in their names
31 * without prior written permission of the Visigoth Software Society.
32 *
33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44 * SUCH DAMAGE.
45 * ====================================================================
46 *
47 * This software consists of voluntary contributions made by many
48 * individuals on behalf of the Visigoth Software Society. For more
49 * information on the Visigoth Software Society, please see
50 * http://www.visigoths.org/
51 */
52
53 package freemarker.template;
54
55 import java.io;
56 import java.util;
57
58 import freemarker.cache;
59 import freemarker.core;
60 import freemarker.template.utility;
61
62 /**
63 * Main entry point into the FreeMarker API, this class encapsulates the
64 * various configuration parameters with which FreeMarker is run, as well
65 * as serves as a central template loading and caching point. Note that
66 * this class uses a default strategy for loading
67 * and caching templates. You can plug in a replacement
68 * template loading mechanism by using the {@link #setTemplateLoader(TemplateLoader)}
69 * method.
70 *
71 * This object is <em>not synchronized</em>. Thus, the settings must not be changed
72 * after you have started to access the object from multiple threads. If you use multiple
73 * threads, set everything directly after you have instantiated the <code>Configuration</code>
74 * object, and don't change the settings anymore.
75 *
76 * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
77 * @author Attila Szegedi
78 * @version $Id: Configuration.java,v 1.122.2.5 2006/04/26 21:25:19 ddekany Exp $
79 */
80
81 public class Configuration extends Configurable implements Cloneable {
82 public static final String DEFAULT_ENCODING_KEY = "default_encoding";
83 public static final String LOCALIZED_LOOKUP_KEY = "localized_lookup";
84 public static final String STRICT_SYNTAX_KEY = "strict_syntax";
85 public static final String WHITESPACE_STRIPPING_KEY = "whitespace_stripping";
86 public static final String CACHE_STORAGE_KEY = "cache_storage";
87 public static final String TEMPLATE_UPDATE_DELAY_KEY = "template_update_delay";
88 public static final String AUTO_IMPORT_KEY = "auto_import";
89 public static final String AUTO_INCLUDE_KEY = "auto_include";
90 public static final String TAG_SYNTAX_KEY = "tag_syntax";
91 public static final int AUTO_DETECT_TAG_SYNTAX = 0;
92 public static final int ANGLE_BRACKET_TAG_SYNTAX = 1;
93 public static final int SQUARE_BRACKET_TAG_SYNTAX = 2;
94
95
96 private static Configuration defaultConfig = new Configuration();
97 private static String cachedVersion;
98 private boolean strictSyntax = true, localizedLookup = true, whitespaceStripping = true;
99 private int tagSyntax = ANGLE_BRACKET_TAG_SYNTAX;
100
101 private TemplateCache cache;
102 private HashMap variables = new HashMap();
103 private HashMap encodingMap = new HashMap();
104 private Map autoImportMap = new HashMap();
105 private ArrayList autoImports = new ArrayList(), autoIncludes = new ArrayList();
106 private String defaultEncoding = SecurityUtilities.getSystemProperty("file.encoding");
107
108
109 public Configuration() {
110 cache = new TemplateCache();
111 cache.setConfiguration(this);
112 cache.setDelay(5000);
113 loadBuiltInSharedVariables();
114 }
115
116 public Object clone() {
117 try {
118 Configuration copy = (Configuration)super.clone();
119 copy.variables = new HashMap(variables);
120 copy.encodingMap = new HashMap(encodingMap);
121 copy.createTemplateCache(cache.getTemplateLoader(), cache.getCacheStorage());
122 return copy;
123 } catch (CloneNotSupportedException e) {
124 throw new RuntimeException("Clone is not supported, but it should be: " + e.getMessage());
125 }
126 }
127
128 private void loadBuiltInSharedVariables() {
129 variables.put("capture_output", new CaptureOutput());
130 variables.put("compress", StandardCompress.INSTANCE);
131 variables.put("html_escape", new HtmlEscape());
132 variables.put("normalize_newlines", new NormalizeNewlines());
133 variables.put("xml_escape", new XmlEscape());
134 }
135
136 /**
137 * Loads a preset language-to-encoding map. It assumes the usual character
138 * encodings for most languages.
139 * The previous content of the encoding map will be lost.
140 * This default map currently contains the following mappings:
141 * <table>
142 * <tr><td>ar</td><td>ISO-8859-6</td></tr>
143 * <tr><td>be</td><td>ISO-8859-5</td></tr>
144 * <tr><td>bg</td><td>ISO-8859-5</td></tr>
145 * <tr><td>ca</td><td>ISO-8859-1</td></tr>
146 * <tr><td>cs</td><td>ISO-8859-2</td></tr>
147 * <tr><td>da</td><td>ISO-8859-1</td></tr>
148 * <tr><td>de</td><td>ISO-8859-1</td></tr>
149 * <tr><td>el</td><td>ISO-8859-7</td></tr>
150 * <tr><td>en</td><td>ISO-8859-1</td></tr>
151 * <tr><td>es</td><td>ISO-8859-1</td></tr>
152 * <tr><td>et</td><td>ISO-8859-1</td></tr>
153 * <tr><td>fi</td><td>ISO-8859-1</td></tr>
154 * <tr><td>fr</td><td>ISO-8859-1</td></tr>
155 * <tr><td>hr</td><td>ISO-8859-2</td></tr>
156 * <tr><td>hu</td><td>ISO-8859-2</td></tr>
157 * <tr><td>is</td><td>ISO-8859-1</td></tr>
158 * <tr><td>it</td><td>ISO-8859-1</td></tr>
159 * <tr><td>iw</td><td>ISO-8859-8</td></tr>
160 * <tr><td>ja</td><td>Shift_JIS</td></tr>
161 * <tr><td>ko</td><td>EUC-KR</td></tr>
162 * <tr><td>lt</td><td>ISO-8859-2</td></tr>
163 * <tr><td>lv</td><td>ISO-8859-2</td></tr>
164 * <tr><td>mk</td><td>ISO-8859-5</td></tr>
165 * <tr><td>nl</td><td>ISO-8859-1</td></tr>
166 * <tr><td>no</td><td>ISO-8859-1</td></tr>
167 * <tr><td>pl</td><td>ISO-8859-2</td></tr>
168 * <tr><td>pt</td><td>ISO-8859-1</td></tr>
169 * <tr><td>ro</td><td>ISO-8859-2</td></tr>
170 * <tr><td>ru</td><td>ISO-8859-5</td></tr>
171 * <tr><td>sh</td><td>ISO-8859-5</td></tr>
172 * <tr><td>sk</td><td>ISO-8859-2</td></tr>
173 * <tr><td>sl</td><td>ISO-8859-2</td></tr>
174 * <tr><td>sq</td><td>ISO-8859-2</td></tr>
175 * <tr><td>sr</td><td>ISO-8859-5</td></tr>
176 * <tr><td>sv</td><td>ISO-8859-1</td></tr>
177 * <tr><td>tr</td><td>ISO-8859-9</td></tr>
178 * <tr><td>uk</td><td>ISO-8859-5</td></tr>
179 * <tr><td>zh</td><td>GB2312</td></tr>
180 * <tr><td>zh_TW</td><td>Big5</td></tr>
181 * </table>
182 * @see #clearEncodingMap
183 * @see #setEncoding
184 */
185 public void loadBuiltInEncodingMap() {
186 encodingMap.clear();
187 encodingMap.put("ar", "ISO-8859-6");
188 encodingMap.put("be", "ISO-8859-5");
189 encodingMap.put("bg", "ISO-8859-5");
190 encodingMap.put("ca", "ISO-8859-1");
191 encodingMap.put("cs", "ISO-8859-2");
192 encodingMap.put("da", "ISO-8859-1");
193 encodingMap.put("de", "ISO-8859-1");
194 encodingMap.put("el", "ISO-8859-7");
195 encodingMap.put("en", "ISO-8859-1");
196 encodingMap.put("es", "ISO-8859-1");
197 encodingMap.put("et", "ISO-8859-1");
198 encodingMap.put("fi", "ISO-8859-1");
199 encodingMap.put("fr", "ISO-8859-1");
200 encodingMap.put("hr", "ISO-8859-2");
201 encodingMap.put("hu", "ISO-8859-2");
202 encodingMap.put("is", "ISO-8859-1");
203 encodingMap.put("it", "ISO-8859-1");
204 encodingMap.put("iw", "ISO-8859-8");
205 encodingMap.put("ja", "Shift_JIS");
206 encodingMap.put("ko", "EUC-KR");
207 encodingMap.put("lt", "ISO-8859-2");
208 encodingMap.put("lv", "ISO-8859-2");
209 encodingMap.put("mk", "ISO-8859-5");
210 encodingMap.put("nl", "ISO-8859-1");
211 encodingMap.put("no", "ISO-8859-1");
212 encodingMap.put("pl", "ISO-8859-2");
213 encodingMap.put("pt", "ISO-8859-1");
214 encodingMap.put("ro", "ISO-8859-2");
215 encodingMap.put("ru", "ISO-8859-5");
216 encodingMap.put("sh", "ISO-8859-5");
217 encodingMap.put("sk", "ISO-8859-2");
218 encodingMap.put("sl", "ISO-8859-2");
219 encodingMap.put("sq", "ISO-8859-2");
220 encodingMap.put("sr", "ISO-8859-5");
221 encodingMap.put("sv", "ISO-8859-1");
222 encodingMap.put("tr", "ISO-8859-9");
223 encodingMap.put("uk", "ISO-8859-5");
224 encodingMap.put("zh", "GB2312");
225 encodingMap.put("zh_TW", "Big5");
226 }
227
228 /**
229 * Clears language-to-encoding map.
230 * @see #loadBuiltInEncodingMap
231 * @see #setEncoding
232 */
233 public void clearEncodingMap() {
234 encodingMap.clear();
235 }
236
237 /**
238 * Returns the default (singleton) Configuration object. Note that you can
239 * create as many separate configurations as you wish; this global instance
240 * is provided for convenience, or when you have no reason to use a separate
241 * instance.
242 *
243 * @deprecated The usage of the static singleton (the "default")
244 * {@link Configuration} instance can easily cause erroneous, unpredictable
245 * behavior. This is because multiple independent software components may use
246 * FreeMarker internally inside the same application, so they will interfere
247 * because of the common {@link Configuration} instance. Each such component
248 * should use its own private {@link Configuration} object instead, that it
249 * typically creates with <code>new Configuration()</code> when the component
250 * is initialized.
251 */
252 static public Configuration getDefaultConfiguration() {
253 return defaultConfig;
254 }
255
256 /**
257 * Sets the Configuration object that will be retrieved from future calls
258 * to {@link #getDefaultConfiguration()}.
259 *
260 * @deprecated Using the "default" {@link Configuration} instance can
261 * easily lead to erroneous, unpredictable behaviour.
262 * See more {@link Configuration#getDefaultConfiguration() here...}.
263 */
264 static public void setDefaultConfiguration(Configuration config) {
265 defaultConfig = config;
266 }
267
268 /**
269 * Sets a template loader that is used to look up and load templates.
270 * By providing your own template loader, you can customize the way
271 * templates are loaded. Several convenience methods in this class already
272 * allow you to install commonly used loaders:
273 * {@link #setClassForTemplateLoading(Class, String)},
274 * {@link #setDirectoryForTemplateLoading(File)}, and
275 * {@link #setServletContextForTemplateLoading(Object, String)}. By default,
276 * a multi-loader is used that first tries to load a template from the file
277 * in the current directory, then from a resource on the classpath.
278 */
279 public synchronized void setTemplateLoader(TemplateLoader loader) {
280 createTemplateCache(loader, cache.getCacheStorage());
281 }
282
283 private void createTemplateCache(TemplateLoader loader, CacheStorage storage)
284 {
285 TemplateCache oldCache = cache;
286 cache = new TemplateCache(loader, storage);
287 cache.setDelay(oldCache.getDelay());
288 cache.setConfiguration(this);
289 cache.setLocalizedLookup(localizedLookup);
290 }
291 /**
292 * @return the template loader that is used to look up and load templates.
293 * @see #setTemplateLoader
294 */
295 public TemplateLoader getTemplateLoader()
296 {
297 return cache.getTemplateLoader();
298 }
299
300 public synchronized void setCacheStorage(CacheStorage storage) {
301 createTemplateCache(cache.getTemplateLoader(), storage);
302 }
303
304 /**
305 * Set the explicit directory from which to load templates.
306 */
307 public void setDirectoryForTemplateLoading(File dir) throws IOException {
308 TemplateLoader tl = getTemplateLoader();
309 if (tl instanceof FileTemplateLoader) {
310 String path = ((FileTemplateLoader) tl).baseDir.getCanonicalPath();
311 if (path.equals(dir.getCanonicalPath()))
312 return;
313 }
314 setTemplateLoader(new FileTemplateLoader(dir));
315 }
316
317 /**
318 * Sets the servlet context from which to load templates
319 * @param sctxt the ServletContext object. Note that the type is <code>Object</code>
320 * to prevent class loading errors when user who uses FreeMarker not for
321 * servlets has no javax.servlet in the CLASSPATH.
322 * @param path the path relative to the ServletContext.
323 * If this path is absolute, it is taken to be relative
324 * to the server's URL, i.e. http://myserver.com/
325 * and if the path is relative, it is taken to be
326 * relative to the web app context, i.e.
327 * http://myserver.context.com/mywebappcontext/
328 */
329 public void setServletContextForTemplateLoading(Object sctxt, String path) {
330 try {
331 if (path == null) {
332 setTemplateLoader( (TemplateLoader)
333 ClassUtil.forName("freemarker.cache.WebappTemplateLoader")
334 .getConstructor(new Class[]{ClassUtil.forName("javax.servlet.ServletContext")})
335 .newInstance(new Object[]{sctxt}) );
336 }
337 else {
338 setTemplateLoader( (TemplateLoader)
339 ClassUtil.forName("freemarker.cache.WebappTemplateLoader")
340 .getConstructor(new Class[]{ClassUtil.forName("javax.servlet.ServletContext"), String.class})
341 .newInstance(new Object[]{sctxt, path}) );
342 }
343 } catch (Exception exc) {
344 throw new RuntimeException("Internal FreeMarker error: " + exc);
345 }
346 }
347
348 /**
349 * Sets a class relative to which we do the
350 * Class.getResource() call to load templates.
351 */
352 public void setClassForTemplateLoading(Class clazz, String pathPrefix) {
353 setTemplateLoader(new ClassTemplateLoader(clazz, pathPrefix));
354 }
355
356 /**
357 * Set the time in seconds that must elapse before checking whether there is a newer
358 * version of a template file.
359 * This method is thread-safe and can be called while the engine works.
360 */
361 public void setTemplateUpdateDelay(int delay) {
362 cache.setDelay(1000L * delay);
363 }
364
365 /**
366 * Sets whether directives such as if, else, etcetera
367 * must be written as #if, #else, etcetera.
368 * Any tag not starting with <# or </# is considered as plain text
369 * and will go to the output as is. Tag starting with <# or </# must
370 * be valid FTL tag, or else the template is invalid (i.e. <#noSuchDirective>
371 * is an error).
372 */
373
374 public void setStrictSyntaxMode(boolean b) {
375 strictSyntax = b;
376 }
377
378 /**
379 * Tells whether directives such as if, else, etcetera
380 * must be written as #if, #else, etcetera.
381 *
382 * @see #setStrictSyntaxMode
383 */
384 public boolean getStrictSyntaxMode() {
385 return strictSyntax;
386 }
387
388 /**
389 * Sets whether the FTL parser will try to remove
390 * superfluous white-space around certain FTL tags.
391 */
392 public void setWhitespaceStripping(boolean b) {
393 whitespaceStripping = b;
394 }
395
396 /**
397 * Gets whether the FTL parser will try to remove
398 * superfluous white-space around certain FTL tags.
399 *
400 * @see #setWhitespaceStripping
401 */
402 public boolean getWhitespaceStripping() {
403 return whitespaceStripping;
404 }
405
406 /**
407 * Determines the syntax of the template files (angle bracket VS square bracket)
408 * that has no <markup>ftl</markup> directive in it. The <code>tagSyntax</code>
409 * parameter must be one of:
410 * <ul>
411 * <li>{@link Configuration#AUTO_DETECT_TAG_SYNTAX}:
412 * use the syntax of the first FreeMarker tag (can be anything, like <tt>list</tt>,
413 * <tt>include</tt>, user defined, ...etc)
414 * <li>{@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}:
415 * use the angle bracket syntax (the normal syntax)
416 * <li>{@link Configuration#SQUARE_BRACKET_TAG_SYNTAX}:
417 * use the square bracket syntax
418 * </ul>
419 *
420 * <p>In FreeMarker 2.3.x {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX} is the
421 * default for better backward compatibility. Starting from 2.4.x {@link
422 * Configuration#AUTO_DETECT_TAG_SYNTAX} is the default, so it is recommended to use
423 * that even for 2.3.x.
424 *
425 * <p>This setting is ignored for the templates that have <tt>ftl</tt> directive in
426 * it. For those templates the syntax used for the <tt>ftl</tt> directive determines
427 * the syntax.
428 */
429 public void setTagSyntax(int tagSyntax) {
430 if (tagSyntax != AUTO_DETECT_TAG_SYNTAX
431 && tagSyntax != SQUARE_BRACKET_TAG_SYNTAX
432 && tagSyntax != ANGLE_BRACKET_TAG_SYNTAX)
433 {
434 throw new IllegalArgumentException("This can only be set to one of three settings: Configuration.AUTO_DETECT_TAG_SYNTAX, Configuration.ANGLE_BRACKET_SYNTAX, or Configuration.SQAUARE_BRACKET_SYNTAX");
435 }
436 this.tagSyntax = tagSyntax;
437 }
438
439 /**
440 * @return whether the alternative square bracket
441 * syntax is set as the default
442 */
443 public int getTagSyntax() {
444 return tagSyntax;
445 }
446
447 /**
448 * Equivalent to <tt>getTemplate(name, thisCfg.getLocale(), thisCfg.getEncoding(thisCfg.getLocale()), true)</tt>.
449 */
450 public Template getTemplate(String name) throws IOException {
451 Locale loc = getLocale();
452 return getTemplate(name, loc, getEncoding(loc), true);
453 }
454
455 /**
456 * Equivalent to <tt>getTemplate(name, locale, thisCfg.getEncoding(locale), true)</tt>.
457 */
458 public Template getTemplate(String name, Locale locale) throws IOException {
459 return getTemplate(name, locale, getEncoding(locale), true);
460 }
461
462 /**
463 * Equivalent to <tt>getTemplate(name, thisCfg.getLocale(), encoding, true)</tt>.
464 */
465 public Template getTemplate(String name, String encoding) throws IOException {
466 return getTemplate(name, getLocale(), encoding, true);
467 }
468
469 /**
470 * Equivalent to <tt>getTemplate(name, locale, encoding, true)</tt>.
471 */
472 public Template getTemplate(String name, Locale locale, String encoding) throws IOException {
473 return getTemplate(name, locale, encoding, true);
474 }
475
476 /**
477 * Retrieves a template specified by a name and locale, interpreted using
478 * the specified character encoding, either parsed or unparsed. For the
479 * exact semantics of parameters, see
480 * {@link TemplateCache#getTemplate(String, Locale, String, boolean)}.
481 * @return the requested template.
482 * @throws FileNotFoundException if the template could not be found.
483 * @throws IOException if there was a problem loading the template.
484 * @throws ParseException (extends <code>IOException</code>) if the template is syntactically bad.
485 */
486 public Template getTemplate(String name, Locale locale, String encoding, boolean parse) throws IOException {
487 Template result = cache.getTemplate(name, locale, encoding, parse);
488 if (result == null) {
489 throw new FileNotFoundException("Template " + name + " not found.");
490 }
491 return result;
492 }
493
494 /**
495 * Sets the default encoding for converting bytes to characters when
496 * reading template files in a locale for which no explicit encoding
497 * was specified. Defaults to default system encoding.
498 */
499 public void setDefaultEncoding(String encoding) {
500 defaultEncoding = encoding;
501 }
502
503 /**
504 * Gets the default encoding for converting bytes to characters when
505 * reading template files in a locale for which no explicit encoding
506 * was specified. Defaults to default system encoding.
507 */
508 public String getDefaultEncoding() {
509 return defaultEncoding;
510 }
511
512 /**
513 * Gets the preferred character encoding for the given locale, or the
514 * default encoding if no encoding is set explicitly for the specified
515 * locale. You can associate encodings with locales using
516 * {@link #setEncoding(Locale, String)} or {@link #loadBuiltInEncodingMap()}.
517 * @param loc the locale
518 * @return the preferred character encoding for the locale.
519 */
520 public String getEncoding(Locale loc) {
521 // Try for a full name match (may include country and variant)
522 String charset = (String) encodingMap.get(loc.toString());
523 if (charset == null) {
524 if (loc.getVariant().length() > 0) {
525 Locale l = new Locale(loc.getLanguage(), loc.getCountry());
526 charset = (String) encodingMap.get(l.toString());
527 if (charset != null) {
528 encodingMap.put(loc.toString(), charset);
529 }
530 }
531 charset = (String) encodingMap.get(loc.getLanguage());
532 if (charset != null) {
533 encodingMap.put(loc.toString(), charset);
534 }
535 }
536 return charset != null ? charset : defaultEncoding;
537 }
538
539 /**
540 * Sets the character set encoding to use for templates of
541 * a given locale. If there is no explicit encoding set for some
542 * locale, then the default encoding will be used, what you can
543 * set with {@link #setDefaultEncoding}.
544 *
545 * @see #clearEncodingMap
546 * @see #loadBuiltInEncodingMap
547 */
548 public void setEncoding(Locale locale, String encoding) {
549 encodingMap.put(locale.toString(), encoding);
550 }
551
552 /**
553 * Adds a shared variable to the configuration.
554 * Shared variables are variables that are visible
555 * as top-level variables for all templates which use this
556 * configuration, if the data model does not contain a
557 * variable with the same name.
558 *
559 * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared variables,
560 * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
561 *
562 * @param name the name used to access the data object from your template.
563 * If a shared variable with this name already exists, it will replace
564 * that.
565 * @see #setSharedVariable(String,Object)
566 * @see #setAllSharedVariables
567 */
568 public void setSharedVariable(String name, TemplateModel tm) {
569 variables.put(name, tm);
570 }
571
572 /**
573 * Returns the set containing the names of all defined shared variables.
574 * The method returns a new Set object on each call that is completely
575 * disconnected from the Configuration. That is, modifying the set will have
576 * no effect on the Configuration object.
577 */
578 public Set getSharedVariableNames() {
579 return new HashSet(variables.keySet());
580 }
581
582 /**
583 * Adds shared variable to the configuration.
584 * It uses {@link Configurable#getObjectWrapper()} to wrap the
585 * <code>obj</code>.
586 * @see #setSharedVariable(String,TemplateModel)
587 * @see #setAllSharedVariables
588 */
589 public void setSharedVariable(String name, Object obj) throws TemplateModelException {
590 setSharedVariable(name, getObjectWrapper().wrap(obj));
591 }
592
593 /**
594 * Adds all object in the hash as shared variable to the configuration.
595 *
596 * <p>Never use <tt>TemplateModel</tt> implementation that is not thread-safe for shared variables,
597 * if the configuration is used by multiple threads! It is the typical situation for Servlet based Web sites.
598 *
599 * @param hash a hash model whose objects will be copied to the
600 * configuration with same names as they are given in the hash.
601 * If a shared variable with these names already exist, it will be replaced
602 * with those from the map.
603 *
604 * @see #setSharedVariable(String,Object)
605 * @see #setSharedVariable(String,TemplateModel)
606 */
607 public void setAllSharedVariables(TemplateHashModelEx hash) throws TemplateModelException {
608 TemplateModelIterator keys = hash.keys().iterator();
609 TemplateModelIterator values = hash.values().iterator();
610 while(keys.hasNext())
611 {
612 setSharedVariable(((TemplateScalarModel)keys.next()).getAsString(), values.next());
613 }
614 }
615
616 /**
617 * Gets a shared variable. Shared variables are variables that are
618 * available to all templates. When a template is processed, and an identifier
619 * is undefined in the data model, a shared variable object with the same identifier
620 * is then looked up in the configuration. There are several predefined variables
621 * that are always available through this method, see the FreeMarker manual
622 * for a comprehensive list of them.
623 *
624 * @see #setSharedVariable(String,Object)
625 * @see #setSharedVariable(String,TemplateModel)
626 * @see #setAllSharedVariables
627 */
628 public TemplateModel getSharedVariable(String name) {
629 return (TemplateModel) variables.get(name);
630 }
631
632 /**
633 * Removes all shared variables, except the predefined ones (compress, html_escape, etc.).
634 */
635 public void clearSharedVariables() {
636 variables.clear();
637 loadBuiltInSharedVariables();
638 }
639
640 /**
641 * Removes all entries from the template cache, thus forcing reloading of templates
642 * on subsequent <code>getTemplate</code> calls.
643 * This method is thread-safe and can be called while the engine works.
644 */
645 public void clearTemplateCache() {
646 cache.clear();
647 }
648
649 /**
650 * Returns if localized template lookup is enabled or not.
651 * This method is thread-safe and can be called while the engine works.
652 */
653 public boolean getLocalizedLookup() {
654 return cache.getLocalizedLookup();
655 }
656
657 /**
658 * Enables/disables localized template lookup. Enabled by default.
659 * This method is thread-safe and can be called while the engine works.
660 */
661 public void setLocalizedLookup(boolean localizedLookup) {
662 this.localizedLookup = localizedLookup;
663 cache.setLocalizedLookup(localizedLookup);
664 }
665
666 /**
667 * Sets a setting by name and string value.
668 *
669 * In additional to the settings understood by
670 * {@link Configurable#setSetting the super method}, it understands these:
671 * <ul>
672 * <li><code>"auto_import"</code>: Sets the list of auto-imports. Example of valid value:
673 * <br><code>/lib/form.ftl as f, /lib/widget as w, "/lib/evil name.ftl" as odd</code>
674 * See: {@link #setAutoImports}
675 * <li><code>"auto_include"</code>: Sets the list of auto-includes. Example of valid value:
676 * <br><code>/include/common.ftl, "/include/evil name.ftl"</code>
677 * See: {@link #setAutoIncludes}
678 * <li><code>"default_encoding"</code>: The name of the charset, such as <code>"UTF-8"</code>.
679 * See: {@link #setDefaultEncoding}
680 * <li><code>"localized_lookup"</code>:
681 * <code>"true"</code>, <code>"false"</code>, <code>"yes"</code>, <code>"no"</code>,
682 * <code>"t"</code>, <code>"f"</code>, <code>"y"</code>, <code>"n"</code>.
683 * Case insensitive.
684 * See: {@link #setLocalizedLookup}
685 * <li><code>"strict_syntax"</code>: <code>"true"</code>, <code>"false"</code>, etc.
686 * See: {@link #setStrictSyntaxMode}
687 * <li><code>"whitespace_stripping"</code>: <code>"true"</code>, <code>"false"</code>, etc.
688 * See: {@link #setWhitespaceStripping}
689 * <li><code>"cache_storage"</code>: If the value contains dot, then it is
690 * interpreted as class name, and the object will be created with
691 * its parameterless constructor. If the value does not contain dot,
692 * then a {@link freemarker.cache.MruCacheStorage} will be used with the
693 * maximum strong and soft sizes specified with the setting value. Examples
694 * of valid setting values:
695 * <table border=1 cellpadding=4>
696 * <tr><th>Setting value<th>max. strong size<th>max. soft size
697 * <tr><td><code>"strong:50, soft:500"</code><td>50<td>500
698 * <tr><td><code>"strong:100, soft"</code><td>100<td><code>Integer.MAX_VALUE</code>
699 * <tr><td><code>"strong:100"</code><td>100<td>0
700 * <tr><td><code>"soft:100"</code><td>0<td>100
701 * <tr><td><code>"strong"</code><td><code>Integer.MAX_VALUE</code><td>0
702 * <tr><td><code>"soft"</code><td>0<td><code>Integer.MAX_VALUE</code>
703 * </table>
704 * The value is not case sensitive. The order of <tt>soft</tt> and <tt>strong</tt>
705 * entries is not significant.
706 * See also: {@link #setCacheStorage}
707 * <li><code>"template_update_delay"</code>: Valid positive integer, the
708 * update delay measured in seconds.
709 * See: {@link #setTemplateUpdateDelay}
710 * <li><code>"tag_syntax"</code>: Must be one of:
711 * <code>"auto_detect"</code>, <code>"angle_bracket"</code>,
712 * <code>"square_bracket"</code>.
713 * </ul>
714 *
715 * @param key the name of the setting.
716 * @param value the string that describes the new value of the setting.
717 *
718 * @throws UnknownSettingException if the key is wrong.
719 * @throws TemplateException if the new value of the setting can't be set
720 * for any other reasons.
721 */
722 public void setSetting(String key, String value) throws TemplateException {
723 if ("TemplateUpdateInterval".equalsIgnoreCase(key)) {
724 key = TEMPLATE_UPDATE_DELAY_KEY;
725 } else if ("DefaultEncoding".equalsIgnoreCase(key)) {
726 key = DEFAULT_ENCODING_KEY;
727 }
728 try {
729 if (DEFAULT_ENCODING_KEY.equals(key)) {
730 setDefaultEncoding(value);
731 } else if (LOCALIZED_LOOKUP_KEY.equals(key)) {
732 setLocalizedLookup(StringUtil.getYesNo(value));
733 } else if (STRICT_SYNTAX_KEY.equals(key)) {
734 setStrictSyntaxMode(StringUtil.getYesNo(value));
735 } else if (WHITESPACE_STRIPPING_KEY.equals(key)) {
736 setWhitespaceStripping(StringUtil.getYesNo(value));
737 } else if (CACHE_STORAGE_KEY.equals(key)) {
738 if (value.indexOf('.') == -1) {
739 int strongSize = 0;
740 int softSize = 0;
741 Map map = StringUtil.parseNameValuePairList(
742 value, String.valueOf(Integer.MAX_VALUE));
743 Iterator it = map.entrySet().iterator();
744 while (it.hasNext()) {
745 Map.Entry ent = (Map.Entry) it.next();
746 String pname = (String) ent.getKey();
747 int pvalue;
748 try {
749 pvalue = Integer.parseInt((String) ent.getValue());
750 } catch (NumberFormatException e) {
751 throw invalidSettingValueException(key, value);
752 }
753 if ("soft".equalsIgnoreCase(pname)) {
754 softSize = pvalue;
755 } else if ("strong".equalsIgnoreCase(pname)) {
756 strongSize = pvalue;
757 } else {
758 throw invalidSettingValueException(key, value);
759 }
760 }
761 if (softSize == 0 && strongSize == 0) {
762 throw invalidSettingValueException(key, value);
763 }
764 setCacheStorage(new MruCacheStorage(strongSize, softSize));
765 } else {
766 setCacheStorage((CacheStorage) ClassUtil.forName(value)
767 .newInstance());
768 }
769 } else if (TEMPLATE_UPDATE_DELAY_KEY.equals(key)) {
770 setTemplateUpdateDelay(Integer.parseInt(value));
771 } else if (AUTO_INCLUDE_KEY.equals(key)) {
772 setAutoIncludes(new SettingStringParser(value).parseAsList());
773 } else if (AUTO_IMPORT_KEY.equals(key)) {
774 setAutoImports(new SettingStringParser(value).parseAsImportList());
775 } else if (TAG_SYNTAX_KEY.equals(key)) {
776 if ("auto_detect".equals(value)) {
777 setTagSyntax(AUTO_DETECT_TAG_SYNTAX);
778 } else if ("angle_bracket".equals(value)) {
779 setTagSyntax(ANGLE_BRACKET_TAG_SYNTAX);
780 } else if ("square_bracket".equals(value)) {
781 setTagSyntax(SQUARE_BRACKET_TAG_SYNTAX);
782 } else {
783 throw invalidSettingValueException(key, value);
784 }
785 } else {
786 super.setSetting(key, value);
787 }
788 } catch(Exception e) {
789 throw new TemplateException(
790 "Failed to set setting " + key + " to value " + value,
791 e, getEnvironment());
792 }
793 }
794
795
796 /**
797 * Add an auto-imported template.
798 * The importing will happen at the top of any template that
799 * is vended by this Configuration object.
800 * @param namespace the name of the namespace into which the template is imported
801 * @param template the name of the template
802 */
803 public synchronized void addAutoImport(String namespace, String template) {
804 autoImports.remove(namespace);
805 autoImports.add(namespace);
806 autoImportMap.put(namespace, template);
807 }
808
809 /**
810 * Remove an auto-imported template
811 * @param namespace the name of the namespace into which the template was imported
812 */
813
814 public synchronized void removeAutoImport(String namespace) {
815 autoImports.remove(namespace);
816 autoImportMap.remove(namespace);
817 }
818
819 /**
820 * set a map of namespace names to templates for auto-importing
821 * a set of templates. Note that all previous auto-imports are removed.
822 */
823
824 public synchronized void setAutoImports(Map map) {
825 autoImports = new ArrayList(map.keySet());
826 if (map instanceof HashMap) {
827 autoImportMap = (Map) ((HashMap) map).clone();
828 }
829 else if (map instanceof SortedMap) {
830 autoImportMap = new TreeMap(map);
831 }
832 else {
833 autoImportMap = new HashMap(map);
834 }
835 }
836
837 void doAutoImports(Environment env) throws TemplateException, IOException {
838 for (int i=0; i<autoImports.size(); i++) {
839 String namespace = (String) autoImports.get(i);
840 String templateName = (String) autoImportMap.get(namespace);
841 env.importLib(templateName, namespace);
842 }
843 }
844
845 /**
846 * add a template to be automatically included at the top of any template that
847 * is vended by this Configuration object.
848 * @param templateName the lookup name of the template.
849 */
850
851 public synchronized void addAutoInclude(String templateName) {
852 autoIncludes.remove(templateName);
853 autoIncludes.add(templateName);
854 }
855
856 /**
857 * set the list of automatically included templates.
858 * Note that all previous auto-includes are removed.
859 */
860 public synchronized void setAutoIncludes(List templateNames) {
861 autoIncludes.clear();
862 Iterator it = templateNames.iterator();
863 while (it.hasNext()) {
864 Object o = it.next();
865 if (!(o instanceof String)) {
866 throw new IllegalArgumentException("List items must be String-s.");
867 }
868 autoIncludes.add(o);
869 }
870 }
871
872 /**
873 * remove a template from the auto-include list.
874 * @param templateName the lookup name of the template in question.
875 */
876
877 public synchronized void removeAutoInclude(String templateName) {
878 autoIncludes.remove(templateName);
879 }
880
881 /**
882 * Returns FreeMarker version number string.
883 * Examples of possible return values:
884 * <code>"2.2.5"</code>, <code>"2.3pre13"</code>,
885 * <code>"2.3pre13mod"</code>, <code>"2.3rc1"</code>, <code>"2.3"</code>,
886 * <code>"3.0"</code>.
887 *
888 * <p>Notes on FreeMarker version numbering rules:
889 * <ul>
890 * <li>"pre" and "rc" (lowercase!) means "preview" and "release
891 * candidate" respectively. It is must be followed with a
892 * number (as "1" for the first release candidate).
893 * <li>The "mod" after the version number indicates that it's an
894 * unreleased modified version of the released version.
895 * After releases, the nighly builds are such releases. E.g.
896 * the nightly build after releasing "2.2.1" but before releasing
897 * "2.2.2" is "2.2.1mod".
898 * <li>The 2nd version number must be present, and maybe 0,
899 * as in "3.0".
900 * <li>The 3rd version number is never 0. E.g. the version
901 * number string for the first release of the 2.2 series
902 * is "2.2", and NOT "2.2.0".
903 * <li>When only the 3rd version number increases
904 * (2.2 -> 2.2.1, 2.2.1 -> 2.2.2 etc.), 100% backward compatiblity
905 * with the previous version MUST be kept.
906 * This means that <tt>freemarker.jar</tt> can be replaced in an
907 * application without risk (as far as the application doesn't depend
908 * on the presence of a FreeMarker bug).
909 * Note that backward compatibility restrictions do not apply for
910 * preview releases.
911 * </ul>
912 */
913 public static String getVersionNumber() {
914 if (cachedVersion != null) {
915 return cachedVersion;
916 }
917 try {
918 Properties vp = new Properties();
919 InputStream ins = Configuration.class.getClassLoader()
920 .getResourceAsStream("freemarker/version.properties");
921 if (ins == null) {
922 throw new RuntimeException("Version file is missing.");
923 } else {
924 try {
925 vp.load(ins);
926 } finally {
927 ins.close();
928 }
929 String v = vp.getProperty("version");
930 if (v == null) {
931 throw new RuntimeException("Version file is corrupt: version key is missing.");
932 }
933 cachedVersion = v;
934 }
935 return cachedVersion;
936 } catch (IOException e) {
937 throw new RuntimeException("Failed to load version file: " + e);
938 }
939 }
940
941 void doAutoIncludes(Environment env) throws TemplateException, IOException {
942 for (int i = 0; i < autoIncludes.size(); i++) {
943 String templateName = (String) autoIncludes.get(i);
944 Template template = getTemplate(templateName, env.getLocale());
945 env.include(template);
946 }
947 }
948
949 }