blob: c9f83e773f90b0a8faf6465e3524619a36d01467 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.clearsilver.jsilver.template;
import com.google.clearsilver.jsilver.autoescape.AutoEscapeContext;
import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
import com.google.clearsilver.jsilver.autoescape.EscapeMode;
import com.google.clearsilver.jsilver.data.DataContext;
import com.google.clearsilver.jsilver.data.UniqueStack;
import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
import com.google.clearsilver.jsilver.exceptions.JSilverIOException;
import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
import com.google.clearsilver.jsilver.functions.FunctionExecutor;
import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
import com.google.clearsilver.jsilver.values.Value;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Default implementation of RenderingContext.
*/
public class DefaultRenderingContext implements RenderingContext, FunctionExecutor {
public static final Logger logger = Logger.getLogger(DefaultRenderingContext.class.getName());
private final DataContext dataContext;
private final ResourceLoader resourceLoader;
private final Appendable out;
private final FunctionExecutor globalFunctionExecutor;
private final AutoEscapeOptions autoEscapeOptions;
private final UniqueStack<String> includeStack;
private List<String> escaperStack = new ArrayList<String>(8); // seems like a reasonable initial
// capacity.
private String currentEscaper; // optimization to reduce List lookup.
private List<Template> executionStack = new ArrayList<Template>(8);
private Map<String, Macro> macros = new HashMap<String, Macro>();
private List<EscapeMode> autoEscapeStack = new ArrayList<EscapeMode>();
private EscapeMode autoEscapeMode;
private AutoEscapeContext autoEscapeContext;
private int line;
private int column;
private AutoEscapeContext.AutoEscapeState startingAutoEscapeState;
public DefaultRenderingContext(DataContext dataContext, ResourceLoader resourceLoader,
Appendable out, FunctionExecutor globalFunctionExecutor, AutoEscapeOptions autoEscapeOptions) {
this.dataContext = dataContext;
this.resourceLoader = resourceLoader;
this.out = out;
this.globalFunctionExecutor = globalFunctionExecutor;
this.autoEscapeOptions = autoEscapeOptions;
this.autoEscapeMode = EscapeMode.ESCAPE_NONE;
this.autoEscapeContext = null;
this.includeStack = new UniqueStack<String>();
}
/**
* Lookup a function by name, execute it and return the results.
*/
@Override
public Value executeFunction(String name, Value... args) {
return globalFunctionExecutor.executeFunction(name, args);
}
@Override
public void escape(String name, String input, Appendable output) throws IOException {
globalFunctionExecutor.escape(name, input, output);
}
@Override
public boolean isEscapingFunction(String name) {
return globalFunctionExecutor.isEscapingFunction(name);
}
@Override
public void pushEscapingFunction(String name) {
escaperStack.add(currentEscaper);
if (name == null || name.equals("")) {
currentEscaper = null;
} else {
currentEscaper = name;
}
}
@Override
public void popEscapingFunction() {
int len = escaperStack.size();
if (len == 0) {
throw new IllegalStateException("No more escaping functions to pop.");
}
currentEscaper = escaperStack.remove(len - 1);
}
@Override
public void writeEscaped(String text) {
// If runtime auto escaping is enabled, only apply it if
// we are not going to do any other default escaping on the variable.
boolean applyAutoEscape = isRuntimeAutoEscaping() && (currentEscaper == null);
if (applyAutoEscape) {
autoEscapeContext.setCurrentPosition(line, column);
pushEscapingFunction(autoEscapeContext.getEscapingFunctionForCurrentState());
}
try {
if (shouldLogEscapedVariables()) {
StringBuilder tmp = new StringBuilder();
globalFunctionExecutor.escape(currentEscaper, text, tmp);
if (!tmp.toString().equals(text)) {
logger.warning(new StringBuilder(getLoggingPrefix()).append(" Auto-escape changed [")
.append(text).append("] to [").append(tmp.toString()).append("]").toString());
}
out.append(tmp);
} else {
globalFunctionExecutor.escape(currentEscaper, text, out);
}
} catch (IOException e) {
throw new JSilverIOException(e);
} finally {
if (applyAutoEscape) {
autoEscapeContext.insertText();
popEscapingFunction();
}
}
}
private String getLoggingPrefix() {
return "[" + getCurrentResourceName() + ":" + line + ":" + column + "]";
}
private boolean shouldLogEscapedVariables() {
return (autoEscapeOptions != null && autoEscapeOptions.getLogEscapedVariables());
}
@Override
public void writeUnescaped(CharSequence text) {
if (isRuntimeAutoEscaping() && (currentEscaper == null)) {
autoEscapeContext.setCurrentPosition(line, column);
autoEscapeContext.parseData(text.toString());
}
try {
out.append(text);
} catch (IOException e) {
throw new JSilverIOException(e);
}
}
@Override
public void pushExecutionContext(Template template) {
executionStack.add(template);
}
@Override
public void popExecutionContext() {
executionStack.remove(executionStack.size() - 1);
}
@Override
public void setCurrentPosition(int line, int column) {
// TODO: Should these be saved in executionStack as part
// of pushExecutionContext?
this.line = line;
this.column = column;
}
@Override
public void registerMacro(String name, Macro macro) {
macros.put(name, macro);
}
@Override
public Macro findMacro(String name) {
Macro macro = macros.get(name);
if (macro == null) {
throw new JSilverInterpreterException("No such macro: " + name);
}
return macro;
}
@Override
public DataContext getDataContext() {
return dataContext;
}
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
@Override
public AutoEscapeOptions getAutoEscapeOptions() {
return autoEscapeOptions;
}
@Override
public EscapeMode getAutoEscapeMode() {
if (isRuntimeAutoEscaping() || (currentEscaper != null)) {
return EscapeMode.ESCAPE_NONE;
} else {
return autoEscapeMode;
}
}
@Override
public void pushAutoEscapeMode(EscapeMode mode) {
if (isRuntimeAutoEscaping()) {
throw new JSilverInterpreterException(
"cannot call pushAutoEscapeMode while runtime auto escaping is in progress");
}
autoEscapeStack.add(autoEscapeMode);
autoEscapeMode = mode;
}
@Override
public void popAutoEscapeMode() {
int len = autoEscapeStack.size();
if (len == 0) {
throw new IllegalStateException("No more auto escaping modes to pop.");
}
autoEscapeMode = autoEscapeStack.remove(autoEscapeStack.size() - 1);
}
@Override
public boolean isRuntimeAutoEscaping() {
return autoEscapeContext != null;
}
/**
* {@inheritDoc}
*
* @throws JSilverInterpreterException if startRuntimeAutoEscaping is called while runtime
* autoescaping is already in progress.
*/
@Override
public void startRuntimeAutoEscaping() {
if (isRuntimeAutoEscaping()) {
throw new JSilverInterpreterException("startRuntimeAutoEscaping() is not re-entrant at "
+ getCurrentResourceName());
}
if (!autoEscapeMode.equals(EscapeMode.ESCAPE_NONE)) {
// TODO: Get the resourceName as a parameter to this function
autoEscapeContext = new AutoEscapeContext(autoEscapeMode, getCurrentResourceName());
startingAutoEscapeState = autoEscapeContext.getCurrentState();
} else {
autoEscapeContext = null;
}
}
private String getCurrentResourceName() {
if (executionStack.size() == 0) {
return "";
} else {
return executionStack.get(executionStack.size() - 1).getDisplayName();
}
}
@Override
public void stopRuntimeAutoEscaping() {
if (autoEscapeContext != null) {
if (!startingAutoEscapeState.equals(autoEscapeContext.getCurrentState())) {
// We do not allow a macro call to change context of the rest of the template.
// Since the rest of the template has already been auto-escaped at parse time
// with the assumption that the macro call will not modify the context.
throw new JSilverAutoEscapingException("Macro starts in context " + startingAutoEscapeState
+ " but ends in different context " + autoEscapeContext.getCurrentState(),
autoEscapeContext.getResourceName());
}
}
autoEscapeContext = null;
}
@Override
public boolean pushIncludeStackEntry(String templateName) {
return includeStack.push(templateName);
}
@Override
public boolean popIncludeStackEntry(String templateName) {
return templateName.equals(includeStack.pop());
}
@Override
public Iterable<String> getIncludedTemplateNames() {
return includeStack;
}
}