Home | History | Annotate | Download | only in template
      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.template;
     18 
     19 import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext;
     20 import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
     21 import com.google.clearsilver.jsilver.autoescape.EscapeMode;
     22 import com.google.clearsilver.jsilver.data.DataContext;
     23 import com.google.clearsilver.jsilver.data.UniqueStack;
     24 import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
     25 import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
     26 import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
     27 import com.google.clearsilver.jsilver.functions.FunctionExecutor;
     28 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
     29 import com.google.clearsilver.jsilver.values.Value;
     30 
     31 import java.io.IOException;
     32 import java.util.ArrayList;
     33 import java.util.HashMap;
     34 import java.util.List;
     35 import java.util.Map;
     36 import java.util.logging.Logger;
     37 
     38 /**
     39  * Default implementation of RenderingContext.
     40  */
     41 public class DefaultRenderingContext implements RenderingContext, FunctionExecutor {
     42 
     43   public static final Logger logger = Logger.getLogger(DefaultRenderingContext.class.getName());
     44   private final DataContext dataContext;
     45   private final ResourceLoader resourceLoader;
     46   private final Appendable out;
     47   private final FunctionExecutor globalFunctionExecutor;
     48   private final AutoEscapeOptions autoEscapeOptions;
     49   private final UniqueStack<String> includeStack;
     50 
     51   private List<String> escaperStack = new ArrayList<String>(8); // seems like a reasonable initial
     52                                                                 // capacity.
     53   private String currentEscaper; // optimization to reduce List lookup.
     54 
     55   private List<Template> executionStack = new ArrayList<Template>(8);
     56 
     57   private Map<String, Macro> macros = new HashMap<String, Macro>();
     58   private List<EscapeMode> autoEscapeStack = new ArrayList<EscapeMode>();
     59   private EscapeMode autoEscapeMode;
     60   private AutoEscapeContext autoEscapeContext;
     61   private int line;
     62   private int column;
     63   private AutoEscapeContext.AutoEscapeState startingAutoEscapeState;
     64 
     65   public DefaultRenderingContext(DataContext dataContext, ResourceLoader resourceLoader,
     66       Appendable out, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) {
     67     this.dataContext = dataContext;
     68     this.resourceLoader = resourceLoader;
     69     this.out = out;
     70     this.globalFunctionExecutor = globalFunctionExecutor;
     71     this.autoEscapeOptions = autoEscapeOptions;
     72     this.autoEscapeMode = EscapeMode.ESCAPE_NONE;
     73     this.autoEscapeContext = null;
     74     this.includeStack = new UniqueStack<String>();
     75   }
     76 
     77   /**
     78    * Lookup a function by name, execute it and return the results.
     79    */
     80   @Override
     81   public Value executeFunction(String name, Value... args) {
     82     return globalFunctionExecutor.executeFunction(name, args);
     83   }
     84 
     85   @Override
     86   public void escape(String name, String input, Appendable output) throws IOException {
     87     globalFunctionExecutor.escape(name, input, output);
     88   }
     89 
     90   @Override
     91   public boolean isEscapingFunction(String name) {
     92     return globalFunctionExecutor.isEscapingFunction(name);
     93   }
     94 
     95   @Override
     96   public void pushEscapingFunction(String name) {
     97     escaperStack.add(currentEscaper);
     98     if (name == null || name.equals("")) {
     99       currentEscaper = null;
    100     } else {
    101       currentEscaper = name;
    102     }
    103   }
    104 
    105   @Override
    106   public void popEscapingFunction() {
    107     int len = escaperStack.size();
    108     if (len == 0) {
    109       throw new IllegalStateException("No more escaping functions to pop.");
    110     }
    111     currentEscaper = escaperStack.remove(len - 1);
    112   }
    113 
    114   @Override
    115   public void writeEscaped(String text) {
    116     // If runtime auto escaping is enabled, only apply it if
    117     // we are not going to do any other default escaping on the variable.
    118     boolean applyAutoEscape = isRuntimeAutoEscaping() && (currentEscaper == null);
    119     if (applyAutoEscape) {
    120       autoEscapeContext.setCurrentPosition(line, column);
    121       pushEscapingFunction(autoEscapeContext.getEscapingFunctionForCurrentState());
    122     }
    123     try {
    124       if (shouldLogEscapedVariables()) {
    125         StringBuilder tmp = new StringBuilder();
    126         globalFunctionExecutor.escape(currentEscaper, text, tmp);
    127         if (!tmp.toString().equals(text)) {
    128           logger.warning(new StringBuilder(getLoggingPrefix()).append(" Auto-escape changed [")
    129               .append(text).append("] to [").append(tmp.toString()).append("]").toString());
    130         }
    131         out.append(tmp);
    132       } else {
    133         globalFunctionExecutor.escape(currentEscaper, text, out);
    134       }
    135     } catch (IOException e) {
    136       throw new JSilverIOException(e);
    137     } finally {
    138       if (applyAutoEscape) {
    139         autoEscapeContext.insertText();
    140         popEscapingFunction();
    141       }
    142     }
    143   }
    144 
    145   private String getLoggingPrefix() {
    146     return "[" + getCurrentResourceName() + ":" + line + ":" + column + "]";
    147   }
    148 
    149   private boolean shouldLogEscapedVariables() {
    150     return (autoEscapeOptions != null && autoEscapeOptions.getLogEscapedVariables());
    151   }
    152 
    153   @Override
    154   public void writeUnescaped(CharSequence text) {
    155     if (isRuntimeAutoEscaping() && (currentEscaper == null)) {
    156       autoEscapeContext.setCurrentPosition(line, column);
    157       autoEscapeContext.parseData(text.toString());
    158     }
    159     try {
    160       out.append(text);
    161     } catch (IOException e) {
    162       throw new JSilverIOException(e);
    163     }
    164   }
    165 
    166   @Override
    167   public void pushExecutionContext(Template template) {
    168     executionStack.add(template);
    169   }
    170 
    171   @Override
    172   public void popExecutionContext() {
    173     executionStack.remove(executionStack.size() - 1);
    174   }
    175 
    176   @Override
    177   public void setCurrentPosition(int line, int column) {
    178     // TODO: Should these be saved in executionStack as part
    179     // of pushExecutionContext?
    180     this.line = line;
    181     this.column = column;
    182   }
    183 
    184   @Override
    185   public void registerMacro(String name, Macro macro) {
    186     macros.put(name, macro);
    187   }
    188 
    189   @Override
    190   public Macro findMacro(String name) {
    191     Macro macro = macros.get(name);
    192     if (macro == null) {
    193       throw new JSilverInterpreterException("No such macro: " + name);
    194     }
    195     return macro;
    196   }
    197 
    198   @Override
    199   public DataContext getDataContext() {
    200     return dataContext;
    201   }
    202 
    203   @Override
    204   public ResourceLoader getResourceLoader() {
    205     return resourceLoader;
    206   }
    207 
    208   @Override
    209   public AutoEscapeOptions getAutoEscapeOptions() {
    210     return autoEscapeOptions;
    211   }
    212 
    213   @Override
    214   public EscapeMode getAutoEscapeMode() {
    215     if (isRuntimeAutoEscaping() || (currentEscaper != null)) {
    216       return EscapeMode.ESCAPE_NONE;
    217     } else {
    218       return autoEscapeMode;
    219     }
    220   }
    221 
    222   @Override
    223   public void pushAutoEscapeMode(EscapeMode mode) {
    224     if (isRuntimeAutoEscaping()) {
    225       throw new JSilverInterpreterException(
    226           "cannot call pushAutoEscapeMode while runtime auto escaping is in progress");
    227     }
    228     autoEscapeStack.add(autoEscapeMode);
    229     autoEscapeMode = mode;
    230   }
    231 
    232   @Override
    233   public void popAutoEscapeMode() {
    234     int len = autoEscapeStack.size();
    235     if (len == 0) {
    236       throw new IllegalStateException("No more auto escaping modes to pop.");
    237     }
    238     autoEscapeMode = autoEscapeStack.remove(autoEscapeStack.size() - 1);
    239   }
    240 
    241   @Override
    242   public boolean isRuntimeAutoEscaping() {
    243     return autoEscapeContext != null;
    244   }
    245 
    246   /**
    247    * {@inheritDoc}
    248    *
    249    * @throws JSilverInterpreterException if startRuntimeAutoEscaping is called while runtime
    250    *         autoescaping is already in progress.
    251    */
    252   @Override
    253   public void startRuntimeAutoEscaping() {
    254     if (isRuntimeAutoEscaping()) {
    255       throw new JSilverInterpreterException("startRuntimeAutoEscaping() is not re-entrant at "
    256           + getCurrentResourceName());
    257     }
    258     if (!autoEscapeMode.equals(EscapeMode.ESCAPE_NONE)) {
    259       // TODO: Get the resourceName as a parameter to this function
    260       autoEscapeContext = new AutoEscapeContext(autoEscapeMode, getCurrentResourceName());
    261       startingAutoEscapeState = autoEscapeContext.getCurrentState();
    262     } else {
    263       autoEscapeContext = null;
    264     }
    265   }
    266 
    267   private String getCurrentResourceName() {
    268     if (executionStack.size() == 0) {
    269       return "";
    270     } else {
    271       return executionStack.get(executionStack.size() - 1).getDisplayName();
    272     }
    273   }
    274 
    275   @Override
    276   public void stopRuntimeAutoEscaping() {
    277     if (autoEscapeContext != null) {
    278       if (!startingAutoEscapeState.equals(autoEscapeContext.getCurrentState())) {
    279         // We do not allow a macro call to change context of the rest of the template.
    280         // Since the rest of the template has already been auto-escaped at parse time
    281         // with the assumption that the macro call will not modify the context.
    282         throw new JSilverAutoEscapingException("Macro starts in context " + startingAutoEscapeState
    283             + " but ends in different context " + autoEscapeContext.getCurrentState(),
    284             autoEscapeContext.getResourceName());
    285       }
    286     }
    287     autoEscapeContext = null;
    288   }
    289 
    290   @Override
    291   public boolean pushIncludeStackEntry(String templateName) {
    292     return includeStack.push(templateName);
    293   }
    294 
    295   @Override
    296   public boolean popIncludeStackEntry(String templateName) {
    297     return templateName.equals(includeStack.pop());
    298   }
    299 
    300   @Override
    301   public Iterable<String> getIncludedTemplateNames() {
    302     return includeStack;
    303   }
    304 }
    305