/*
|
* 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)
|
*/
|
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)
|
*/
|
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;
|
}
|
}
|