Home | History | Annotate | Download | only in jsilver
      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    */
    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    */
    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
    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    */
    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
    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
    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
    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
    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
    314   public void render(Template template, Data data, Appendable output) throws IOException,
    315       JSilverException {
    316     render(template, data, output, defaultResourceLoader);
    317   }
    318 
    319   @Override
    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
    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
    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    */
    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    */
    383   public Appendable createAppendableBuffer() {
    384     return outputBufferProvider.get();
    385   }
    386 
    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    */
    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    */
    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    */
    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    */
    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
    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
    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    */
    442   public ResourceLoader getResourceLoader() {
    443     return defaultResourceLoader;
    444   }
    445 
    446   /**
    447    * Force all cached templates to be cleared.
    448    */
    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    */
    457   public TemplateLoader getTemplateLoader() {
    458     return templateLoader;
    459   }
    460 
    461   /**
    462    * Returns a copy of the JSilverOptions used by this JSilver instance.
    463    */
    464   public JSilverOptions getOptions() {
    465     return options.clone();
    466   }
    467 }
    468