• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.clearsilver.jsilver;
18 
19 import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
20 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
21 import com.google.clearsilver.jsilver.compiler.TemplateCompiler;
22 import com.google.clearsilver.jsilver.data.Data;
23 import com.google.clearsilver.jsilver.data.DataFactory;
24 import com.google.clearsilver.jsilver.data.HDFDataFactory;
25 import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
26 import com.google.clearsilver.jsilver.exceptions.JSilverException;
27 import com.google.clearsilver.jsilver.functions.Function;
28 import com.google.clearsilver.jsilver.functions.FunctionRegistry;
29 import com.google.clearsilver.jsilver.functions.TextFilter;
30 import com.google.clearsilver.jsilver.functions.bundles.ClearSilverCompatibleFunctions;
31 import com.google.clearsilver.jsilver.functions.bundles.CoreOperators;
32 import com.google.clearsilver.jsilver.interpreter.InterpretedTemplateLoader;
33 import com.google.clearsilver.jsilver.interpreter.LoadingTemplateFactory;
34 import com.google.clearsilver.jsilver.interpreter.OptimizerProvider;
35 import com.google.clearsilver.jsilver.interpreter.OptimizingTemplateFactory;
36 import com.google.clearsilver.jsilver.interpreter.TemplateFactory;
37 import com.google.clearsilver.jsilver.output.InstanceOutputBufferProvider;
38 import com.google.clearsilver.jsilver.output.OutputBufferProvider;
39 import com.google.clearsilver.jsilver.output.ThreadLocalOutputBufferProvider;
40 import com.google.clearsilver.jsilver.precompiler.PrecompiledTemplateLoader;
41 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
42 import com.google.clearsilver.jsilver.syntax.DataCommandConsolidator;
43 import com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer;
44 import com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper;
45 import com.google.clearsilver.jsilver.syntax.node.Switch;
46 import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
47 import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper;
48 import com.google.clearsilver.jsilver.template.Template;
49 import com.google.clearsilver.jsilver.template.TemplateLoader;
50 
51 import java.io.IOException;
52 import java.util.LinkedList;
53 import java.util.List;
54 
55 /**
56  * JSilver templating system.
57  *
58  * <p>
59  * This is a pure Java version of ClearSilver.
60  * </p>
61  *
62  * <h2>Example Usage</h2>
63  *
64  * <pre>
65  * // Load resources (e.g. templates) from directory.
66  * JSilver jSilver = new JSilver(new FileResourceLoader("/path/to/templates"));
67  *
68  * // Set up some data.
69  * Data data = new Data();
70  * data.setValue("name.first", "Mr");
71  * data.setValue("name.last", "Man");
72  *
73  * // Render template to System.out. Writer output = ...;
74  * jSilver.render("say-hello", data, output);
75  * </pre>
76  *
77  * For example usage, see java/com/google/clearsilver/jsilver/examples.
78  *
79  * Additional options can be passed to the constructor using JSilverOptions.
80  *
81  * @see <a href="http://go/jsilver">JSilver Docs</a>
82  * @see <a href="http://clearsilver.net">ClearSilver Docs</a>
83  * @see JSilverOptions
84  * @see Data
85  * @see ResourceLoader
86  */
87 public final class JSilver implements TemplateRenderer, DataLoader {
88 
89   private final JSilverOptions options;
90 
91   private final TemplateLoader templateLoader;
92 
93   /**
94    * If caching enabled, the cached wrapper (otherwise null). Kept here so we can call clearCache()
95    * later.
96    */
97 
98   private final FunctionRegistry globalFunctions = new ClearSilverCompatibleFunctions();
99 
100   private final ResourceLoader defaultResourceLoader;
101 
102   private final DataFactory dataFactory;
103 
104   // Object used to return Appendable output buffers when needed.
105   private final OutputBufferProvider outputBufferProvider;
106   public static final String VAR_ESCAPE_MODE_KEY = "Config.VarEscapeMode";
107   public static final String AUTO_ESCAPE_KEY = "Config.AutoEscape";
108 
109   /**
110    * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
111    *        directory, classpath, memory, etc.
112    * @param options Additional options.
113    * @see JSilverOptions
114    */
JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options)115   public JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options) {
116     // To ensure that options cannot be changed externally, we clone them and
117     // use the frozen clone.
118     options = options.clone();
119 
120     this.defaultResourceLoader = defaultResourceLoader;
121     this.dataFactory =
122         new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy());
123     this.options = options;
124 
125     // Setup the output buffer provider either with a threadlocal pool
126     // or creating a new instance each time it is asked for.
127     int bufferSize = options.getInitialBufferSize();
128     if (options.getUseOutputBufferPool()) {
129       // Use a ThreadLocal to reuse StringBuilder objects.
130       outputBufferProvider = new ThreadLocalOutputBufferProvider(bufferSize);
131     } else {
132       // Create a new StringBuilder each time.
133       outputBufferProvider = new InstanceOutputBufferProvider(bufferSize);
134     }
135 
136     // Loads the template from the resource loader, manipulating the AST as
137     // required for correctness.
138     TemplateFactory templateFactory = new LoadingTemplateFactory();
139 
140     // Applies optimizations to improve performance.
141     // These steps are entirely optional, and are not required for correctness.
142     templateFactory = setupOptimizerFactory(templateFactory);
143 
144     TemplateLoader templateLoader;
145     List<DelegatingTemplateLoader> delegatingTemplateLoaders =
146         new LinkedList<DelegatingTemplateLoader>();
147     AutoEscapeOptions autoEscapeOptions = new AutoEscapeOptions();
148     autoEscapeOptions.setPropagateEscapeStatus(options.getPropagateEscapeStatus());
149     autoEscapeOptions.setLogEscapedVariables(options.getLogEscapedVariables());
150     if (options.getCompileTemplates()) {
151       // Compiled templates.
152       TemplateCompiler compiler =
153           new TemplateCompiler(templateFactory, globalFunctions, autoEscapeOptions);
154       delegatingTemplateLoaders.add(compiler);
155       templateLoader = compiler;
156     } else {
157       // Walk parse tree every time.
158       InterpretedTemplateLoader interpreter =
159           new InterpretedTemplateLoader(templateFactory, globalFunctions, autoEscapeOptions);
160       delegatingTemplateLoaders.add(interpreter);
161       templateLoader = interpreter;
162     }
163 
164     // Do we want to load precompiled Template class objects?
165     if (options.getPrecompiledTemplateMap() != null) {
166       // Load precompiled template classes.
167       PrecompiledTemplateLoader ptl =
168           new PrecompiledTemplateLoader(templateLoader, options.getPrecompiledTemplateMap(),
169               globalFunctions, autoEscapeOptions);
170       delegatingTemplateLoaders.add(ptl);
171       templateLoader = ptl;
172     }
173 
174     for (DelegatingTemplateLoader loader : delegatingTemplateLoaders) {
175       loader.setTemplateLoaderDelegate(templateLoader);
176     }
177     this.templateLoader = templateLoader;
178   }
179 
180   /**
181    * Applies optimizations to improve performance. These steps are entirely optional, and are not
182    * required for correctness.
183    */
setupOptimizerFactory(TemplateFactory templateFactory)184   private TemplateFactory setupOptimizerFactory(TemplateFactory templateFactory) {
185     // DataCommandConsolidator saves state so we need to create a new one
186     // every time we run it.
187     OptimizerProvider dataCommandConsolidatorProvider = new OptimizerProvider() {
188       public Switch getOptimizer() {
189         return new DataCommandConsolidator();
190       }
191     };
192 
193     // SyntaxTreeOptimizer has no state so we can use the same object
194     // concurrently, but it is cheap to make so lets be consistent.
195     OptimizerProvider syntaxTreeOptimizerProvider = new OptimizerProvider() {
196       public Switch getOptimizer() {
197         return new SyntaxTreeOptimizer();
198       }
199     };
200 
201     OptimizerProvider stripStructuralWhitespaceProvider = null;
202     if (options.getStripStructuralWhiteSpace()) {
203       // StructuralWhitespaceStripper has state so create a new one each time.
204       stripStructuralWhitespaceProvider = new OptimizerProvider() {
205         public Switch getOptimizer() {
206           return new StructuralWhitespaceStripper();
207         }
208       };
209     }
210 
211     return new OptimizingTemplateFactory(templateFactory, dataCommandConsolidatorProvider,
212         syntaxTreeOptimizerProvider, stripStructuralWhitespaceProvider);
213   }
214 
215   /**
216    * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
217    *        directory, classpath, memory, etc.
218    * @param cacheTemplates Whether to cache templates. Cached templates are much faster but do not
219    *        check the filesystem for updates. Use true in prod, false in dev.
220    * @deprecated Use {@link #JSilver(ResourceLoader, JSilverOptions)}.
221    */
222   @Deprecated
JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates)223   public JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates) {
224     this(defaultResourceLoader, new JSilverOptions().setCacheTemplates(cacheTemplates));
225   }
226 
227   /**
228    * Creates a JSilver instance with default options.
229    *
230    * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
231    *        directory, classpath, memory, etc.
232    * @see JSilverOptions
233    */
JSilver(ResourceLoader defaultResourceLoader)234   public JSilver(ResourceLoader defaultResourceLoader) {
235     this(defaultResourceLoader, new JSilverOptions());
236   }
237 
238   /**
239    * Renders a given template and provided data, writing to an arbitrary output.
240    *
241    * @param templateName Name of template to load (e.g. "things/blah.cs").
242    * @param data Data to be used in template.
243    * @param output Where template should be rendered to. This can be a Writer, PrintStream,
244    *        System.out/err), StringBuffer/StringBuilder or anything that implements Appendable
245    * @param resourceLoader How to find the template data to render and any included files it depends
246    *        on.
247    */
248   @Override
render(String templateName, Data data, Appendable output, ResourceLoader resourceLoader)249   public void render(String templateName, Data data, Appendable output,
250       ResourceLoader resourceLoader) throws IOException, JSilverException {
251     EscapeMode escapeMode = getEscapeMode(data);
252     render(templateLoader.load(templateName, resourceLoader, escapeMode), data, output,
253         resourceLoader);
254   }
255 
256   /**
257    * Renders a given template and provided data, writing to an arbitrary output.
258    *
259    * @param templateName Name of template to load (e.g. "things/blah.cs").
260    * @param data Data to be used in template.
261    * @param output Where template should be rendered to. This can be a Writer, PrintStream,
262    *        System.out/err), StringBuffer/StringBuilder or anything that implements
263    */
264   @Override
render(String templateName, Data data, Appendable output)265   public void render(String templateName, Data data, Appendable output) throws IOException,
266       JSilverException {
267     render(templateName, data, output, defaultResourceLoader);
268   }
269 
270   /**
271    * Same as {@link TemplateRenderer#render(String, Data, Appendable)}, except returns rendered
272    * template as a String.
273    */
274   @Override
render(String templateName, Data data)275   public String render(String templateName, Data data) throws IOException, JSilverException {
276     Appendable output = createAppendableBuffer();
277     try {
278       render(templateName, data, output);
279       return output.toString();
280     } finally {
281       releaseAppendableBuffer(output);
282     }
283   }
284 
285   /**
286    * Renders a given template and provided data, writing to an arbitrary output.
287    *
288    * @param template Template to load.
289    * @param data Data to be used in template.
290    * @param output Where template should be rendered to. This can be a Writer, PrintStream,
291    *        System.out/err), StringBuffer/StringBuilder or anything that implements
292    *        java.io.Appendable.
293    */
294   @Override
render(Template template, Data data, Appendable output, ResourceLoader resourceLoader)295   public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader)
296       throws IOException, JSilverException {
297     if (options.getStripHtmlWhiteSpace() && !(output instanceof HtmlWhiteSpaceStripper)) {
298       // Strip out whitespace from rendered HTML content.
299       output = new HtmlWhiteSpaceStripper(output);
300     }
301     template.render(data, output, resourceLoader);
302   }
303 
304   /**
305    * Renders a given template and provided data, writing to an arbitrary output.
306    *
307    * @param template Template to load.
308    * @param data Data to be used in template.
309    * @param output Where template should be rendered to. This can be a Writer, PrintStream,
310    *        System.out/err), StringBuffer/StringBuilder or anything that implements
311    *        java.io.Appendable.
312    */
313   @Override
render(Template template, Data data, Appendable output)314   public void render(Template template, Data data, Appendable output) throws IOException,
315       JSilverException {
316     render(template, data, output, defaultResourceLoader);
317   }
318 
319   @Override
render(Template template, Data data)320   public String render(Template template, Data data) throws IOException, JSilverException {
321     Appendable output = createAppendableBuffer();
322     try {
323       render(template, data, output);
324       return output.toString();
325     } finally {
326       releaseAppendableBuffer(output);
327     }
328   }
329 
330   /**
331    * Renders a given template from the content passed in. That is, the first parameter is the actual
332    * template content rather than the filename to load.
333    *
334    * @param content Content of template (e.g. "Hello &lt;cs var:name ?&gt;").
335    * @param data Data to be used in template.
336    * @param output Where template should be rendered to. This can be a Writer, PrintStream,
337    *        System.out/err), StringBuffer/StringBuilder or anything that implements
338    *        java.io.Appendable
339    */
340   @Override
renderFromContent(String content, Data data, Appendable output)341   public void renderFromContent(String content, Data data, Appendable output) throws IOException,
342       JSilverException {
343     EscapeMode escapeMode = getEscapeMode(data);
344     render(templateLoader.createTemp("[renderFromContent]", content, escapeMode), data, output);
345   }
346 
347   /**
348    * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template
349    * as a String.
350    */
351   @Override
renderFromContent(String content, Data data)352   public String renderFromContent(String content, Data data) throws IOException, JSilverException {
353     Appendable output = createAppendableBuffer();
354     try {
355       renderFromContent(content, data, output);
356       return output.toString();
357     } finally {
358       releaseAppendableBuffer(output);
359     }
360   }
361 
362   /**
363    * Determine the escaping to apply based on Config variables in HDF. If there is no escaping
364    * specified in the HDF, check whether JSilverOptions has any escaping configured.
365    *
366    * @param data HDF Data to check
367    * @return EscapeMode
368    */
getEscapeMode(Data data)369   public EscapeMode getEscapeMode(Data data) {
370     EscapeMode escapeMode =
371         EscapeMode.computeEscapeMode(data.getValue(VAR_ESCAPE_MODE_KEY), data
372             .getBooleanValue(AUTO_ESCAPE_KEY));
373     if (escapeMode.equals(EscapeMode.ESCAPE_NONE)) {
374       escapeMode = options.getEscapeMode();
375     }
376 
377     return escapeMode;
378   }
379 
380   /**
381    * Override this to change the type of Appendable buffer used in {@link #render(String, Data)}.
382    */
createAppendableBuffer()383   public Appendable createAppendableBuffer() {
384     return outputBufferProvider.get();
385   }
386 
releaseAppendableBuffer(Appendable buffer)387   public void releaseAppendableBuffer(Appendable buffer) {
388     outputBufferProvider.release(buffer);
389   }
390 
391   /**
392    * Registers a global Function that can be used from any template.
393    */
registerGlobalFunction(String name, Function function)394   public void registerGlobalFunction(String name, Function function) {
395     globalFunctions.registerFunction(name, function);
396   }
397 
398   /**
399    * Registers a global TextFilter as function that can be used from any template.
400    */
registerGlobalFunction(String name, TextFilter textFilter)401   public void registerGlobalFunction(String name, TextFilter textFilter) {
402     globalFunctions.registerFunction(name, textFilter);
403   }
404 
405   /**
406    * Registers a global escaper. This also makes it available as a Function named with "_escape"
407    * suffix (e.g. "html_escape").
408    */
registerGlobalEscaper(String name, TextFilter escaper)409   public void registerGlobalEscaper(String name, TextFilter escaper) {
410     globalFunctions.registerFunction(name + "_escape", escaper, true);
411     globalFunctions.registerEscapeMode(name, escaper);
412   }
413 
414   /**
415    * Create new Data instance, ready to be populated.
416    */
createData()417   public Data createData() {
418     return dataFactory.createData();
419   }
420 
421   /**
422    * Loads data in Hierarchical Data Format (HDF) into an existing Data object.
423    */
424   @Override
loadData(String dataFileName, Data output)425   public void loadData(String dataFileName, Data output) throws JSilverBadSyntaxException,
426       IOException {
427     dataFactory.loadData(dataFileName, defaultResourceLoader, output);
428   }
429 
430   /**
431    * Loads data in Hierarchical Data Format (HDF) into a new Data object.
432    */
433   @Override
loadData(String dataFileName)434   public Data loadData(String dataFileName) throws IOException {
435     return dataFactory.loadData(dataFileName, defaultResourceLoader);
436   }
437 
438   /**
439    * Gets underlying ResourceLoader so you can access arbitrary files using the same mechanism as
440    * JSilver.
441    */
getResourceLoader()442   public ResourceLoader getResourceLoader() {
443     return defaultResourceLoader;
444   }
445 
446   /**
447    * Force all cached templates to be cleared.
448    */
clearCache()449   public void clearCache() {
450 
451   }
452 
453   /**
454    * Returns the TemplateLoader used by this JSilver template renderer. Needed for HDF/CS
455    * compatbility.
456    */
getTemplateLoader()457   public TemplateLoader getTemplateLoader() {
458     return templateLoader;
459   }
460 
461   /**
462    * Returns a copy of the JSilverOptions used by this JSilver instance.
463    */
getOptions()464   public JSilverOptions getOptions() {
465     return options.clone();
466   }
467 }
468