org.apache.wicket.velocity.markup.html.VelocityPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.velocity.markup.html.VelocityPanel.java

Source

/*
 * 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 org.apache.wicket.velocity.markup.html;

import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
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.resource.ResourceUtil;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.IStringResourceStream;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.util.string.Strings;

/**
 * Panel that displays the result of rendering a <a
 * href="http://jakarta.apache.org/velocity">Velocity</a> template. The template itself can be any
 * {@link StringResourceStream} implementation, of which there are a number of convenient
 * implementations in the {@link org.apache.wicket.util} package. The model can be any normal
 * {@link Map}, which will be used to create the {@link VelocityContext}.
 * <p>
 * <b>Note:</b> Be sure to properly initialize the Velocity engine before using
 * {@link VelocityPanel }.
 * </p>
 */
public abstract class VelocityPanel extends Panel
        implements IMarkupResourceStreamProvider, IMarkupCacheKeyProvider {
    private static final long serialVersionUID = 1L;

    /**
     * Convenience factory method to create a {@link VelocityPanel} instance with a given
     * {@link IStringResourceStream template resource}.
     * 
     * @param id
     *            Component id
     * @param model
     *            optional model for variable substitution.
     * @param templateResource
     *            The template resource
     * @return an instance of {@link VelocityPanel}
     */
    @SuppressWarnings("rawtypes")
    public static VelocityPanel forTemplateResource(final String id, final IModel<? extends Map> model,
            final IResourceStream templateResource) {
        if (templateResource == null) {
            throw new IllegalArgumentException("argument templateResource must be not null");
        }

        return new VelocityPanel(id, model) {
            private static final long serialVersionUID = 1L;

            @Override
            protected IResourceStream getTemplateResource() {
                return templateResource;
            }
        };
    }

    private transient String stackTraceAsString;

    private transient String evaluatedTemplate;

    /**
     * Construct.
     * 
     * @param id
     *            Component id
     * @param templateResource
     *            The velocity template as a string resource
     * @param model
     *            Model with variables that can be substituted by Velocity.
     */
    @SuppressWarnings("rawtypes")
    public VelocityPanel(final String id, final IModel<? extends Map> model) {
        super(id, model);
    }

    /**
     * Gets a reader for the velocity template.
     * 
     * @return reader for the velocity template
     */
    private Reader getTemplateReader() {
        final IResourceStream resource = getTemplateResource();
        if (resource == null) {
            throw new IllegalArgumentException("getTemplateResource must return a resource");
        }

        final String template = ResourceUtil.readString(resource);
        if (template != null) {
            return new StringReader(template);
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
        if (!Strings.isEmpty(stackTraceAsString)) {
            // TODO: only display the velocity 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 (!throwVelocityExceptions()) {
            // 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.
     */
    protected boolean escapeHtml() {
        return false;
    }

    /**
     * Returns the template resource passed to the constructor.
     * 
     * @return The template resource
     */
    protected abstract IResourceStream getTemplateResource();

    /**
     * Evaluates the template and returns the result.
     * 
     * @param templateReader
     *            used to read the template
     * @return the result of evaluating the velocity template
     */
    private String evaluateVelocityTemplate(final Reader templateReader) {
        if (evaluatedTemplate == null) {
            // Get model as a map
            @SuppressWarnings("rawtypes")
            final Map map = (Map) getDefaultModelObject();

            // create a Velocity context object using the model if set
            final VelocityContext ctx = new VelocityContext(map);

            // 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
            final String logTag = getId();
            try {
                // execute the velocity script and capture the output in writer
                Velocity.evaluate(ctx, writer, logTag, templateReader);

                // replace the tag's body the Velocity 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 (Exception 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.
     */
    protected boolean parseGeneratedMarkup() {
        return false;
    }

    /**
     * Whether any velocity 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.
     * 
     * @return Whether any velocity exceptions should be thrown or trapped. The default is false.
     */
    protected boolean throwVelocityExceptions() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final IResourceStream getMarkupResourceStream(final MarkupContainer container,
            final Class<?> containerClass) {
        Reader reader = getTemplateReader();
        if (reader == null) {
            throw new WicketRuntimeException("could not find velocity template for panel: " + this);
        }

        // evaluate the template and return a new StringResourceStream
        StringBuilder sb = new StringBuilder();
        sb.append("<wicket:panel>");
        sb.append(evaluateVelocityTemplate(reader));
        sb.append("</wicket:panel>");
        return new StringResourceStream(sb.toString());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String getCacheKey(final MarkupContainer container, final Class<?> containerClass) {
        // don't cache the evaluated template
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onDetach() {
        super.onDetach();
        stackTraceAsString = null;
        evaluatedTemplate = null;
    }
}