blob: aaf1a1351abbbdf52184e656a3b7e0d41eca3dc0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.gitblit.wicket.freemarker;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.IMarkupCacheKeyProvider;
import org.apache.wicket.markup.IMarkupResourceStreamProvider;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.util.string.Strings;
import com.gitblit.utils.StringUtils;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* This class allows FreeMarker to be used as a Wicket preprocessor or as a
* snippet injector for something like a CMS. There are some cases where Wicket
* is not flexible enough to generate content, especially when you need to generate
* hybrid HTML/JS content outside the scope of Wicket.
*
* @author James Moger
*
*/
@SuppressWarnings("unchecked")
public class FreemarkerPanel extends Panel
implements
IMarkupResourceStreamProvider,
IMarkupCacheKeyProvider
{
private static final long serialVersionUID = 1L;
private final String template;
private boolean parseGeneratedMarkup;
private boolean escapeHtml;
private boolean throwFreemarkerExceptions;
private transient String stackTraceAsString;
private transient String evaluatedTemplate;
/**
* Construct.
*
* @param id
* Component id
* @param template
* The Freemarker template
* @param values
* values map that can be substituted by Freemarker.
*/
public FreemarkerPanel(final String id, String template, final Map<String, Object> values)
{
this(id, template, Model.ofMap(values));
}
/**
* Construct.
*
* @param id
* Component id
* @param templateResource
* The Freemarker template as a string resource
* @param model
* Model with variables that can be substituted by Freemarker.
*/
public FreemarkerPanel(final String id, final String template, final IModel< ? extends Map<String, Object>> model)
{
super(id, model);
this.template = template;
}
/**
* Gets the Freemarker template.
*
* @return the Freemarker template
*/
private Template getTemplate()
{
if (StringUtils.isEmpty(template))
{
throw new IllegalArgumentException("Template not specified!");
}
try {
return Freemarker.getTemplate(template);
} catch (IOException e) {
onException(e);
}
return null;
}
/**
* @see org.apache.wicket.markup.html.panel.Panel#onComponentTagBody(org.apache.wicket.markup.
* MarkupStream, org.apache.wicket.markup.ComponentTag)
*/
@Override
protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag)
{
if (!Strings.isEmpty(stackTraceAsString))
{
// TODO: only display the Freemarker error/stacktrace in development
// mode?
replaceComponentTagBody(markupStream, openTag, Strings
.toMultilineMarkup(stackTraceAsString));
}
else if (!parseGeneratedMarkup)
{
// check that no components have been added in case the generated
// markup should not be
// parsed
if (size() > 0)
{
throw new WicketRuntimeException(
"Components cannot be added if the generated markup should not be parsed.");
}
if (evaluatedTemplate == null)
{
// initialize evaluatedTemplate
getMarkupResourceStream(null, null);
}
replaceComponentTagBody(markupStream, openTag, evaluatedTemplate);
}
else
{
super.onComponentTagBody(markupStream, openTag);
}
}
/**
* Either print or rethrow the throwable.
*
* @param exception
* the cause
* @param markupStream
* the markup stream
* @param openTag
* the open tag
*/
private void onException(final Exception exception)
{
if (!throwFreemarkerExceptions)
{
// print the exception on the panel
stackTraceAsString = Strings.toString(exception);
}
else
{
// rethrow the exception
throw new WicketRuntimeException(exception);
}
}
/**
* Gets whether to escape HTML characters.
*
* @return whether to escape HTML characters. The default value is false.
*/
public void setEscapeHtml(boolean value)
{
this.escapeHtml = value;
}
/**
* Evaluates the template and returns the result.
*
* @param templateReader
* used to read the template
* @return the result of evaluating the velocity template
*/
private String evaluateFreemarkerTemplate(Template template)
{
if (evaluatedTemplate == null)
{
// Get model as a map
final Map<String, Object> map = (Map<String, Object>)getDefaultModelObject();
// create a writer for capturing the Velocity output
StringWriter writer = new StringWriter();
// string to be used as the template name for log messages in case
// of error
try
{
// execute the Freemarker script and capture the output in writer
Freemarker.evaluate(template, map, writer);
// replace the tag's body the Freemarker output
evaluatedTemplate = writer.toString();
if (escapeHtml)
{
// encode the result in order to get valid html output that
// does not break the rest of the page
evaluatedTemplate = Strings.escapeMarkup(evaluatedTemplate).toString();
}
return evaluatedTemplate;
}
catch (IOException e)
{
onException(e);
}
catch (TemplateException e)
{
onException(e);
}
return null;
}
return evaluatedTemplate;
}
/**
* Gets whether to parse the resulting Wicket markup.
*
* @return whether to parse the resulting Wicket markup. The default is false.
*/
public void setParseGeneratedMarkup(boolean value)
{
this.parseGeneratedMarkup = value;
}
/**
* Whether any Freemarker exception should be trapped and displayed on the panel (false) or thrown
* up to be handled by the exception mechanism of Wicket (true). The default is false, which
* traps and displays any exception without having consequences for the other components on the
* page.
* <p>
* Trapping these exceptions without disturbing the other components is especially useful in CMS
* like applications, where 'normal' users are allowed to do basic scripting. On errors, you
* want them to be able to have them correct them while the rest of the application keeps on
* working.
* </p>
*
* @return Whether any Freemarker exceptions should be thrown or trapped. The default is false.
*/
public void setThrowFreemarkerExceptions(boolean value)
{
this.throwFreemarkerExceptions = value;
}
/**
* @see org.apache.wicket.markup.IMarkupResourceStreamProvider#getMarkupResourceStream(org.apache
* .wicket.MarkupContainer, java.lang.Class)
*/
@Override
public final IResourceStream getMarkupResourceStream(MarkupContainer container,
Class< ? > containerClass)
{
Template template = getTemplate();
if (template == null)
{
throw new WicketRuntimeException("could not find Freemarker template for panel: " + this);
}
// evaluate the template and return a new StringResourceStream
StringBuffer sb = new StringBuffer();
sb.append("<wicket:panel>");
sb.append(evaluateFreemarkerTemplate(template));
sb.append("</wicket:panel>");
return new StringResourceStream(sb.toString());
}
/**
* @see org.apache.wicket.markup.IMarkupCacheKeyProvider#getCacheKey(org.apache.wicket.
* MarkupContainer, java.lang.Class)
*/
@Override
public final String getCacheKey(MarkupContainer container, Class< ? > containerClass)
{
// don't cache the evaluated template
return null;
}
/**
* @see org.apache.wicket.Component#onDetach()
*/
@Override
protected void onDetach()
{
super.onDetach();
stackTraceAsString = null;
evaluatedTemplate = null;
}
}