All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.template.velocity.spring.view.servlet.ServletVelocityView Maven / Gradle / Ivy

package org.zodiac.template.velocity.spring.view.servlet;

import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.NumberTool;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.NestedIOException;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.AbstractTemplateView;
import org.springframework.web.util.NestedServletException;
import org.zodiac.template.velocity.spring.view.VelocityConfig;

public class ServletVelocityView extends AbstractTemplateView {

    private Map> toolAttributes;

    private String dateToolAttribute;

    private String numberToolAttribute;

    private String encoding;

    private boolean cacheTemplate = false;

    private VelocityEngine velocityEngine;

    private Template template;

    public void setToolAttributes(Map> toolAttributes) {
        this.toolAttributes = toolAttributes;
    }

    public void setDateToolAttribute(String dateToolAttribute) {
        this.dateToolAttribute = dateToolAttribute;
    }

    public void setNumberToolAttribute(String numberToolAttribute) {
        this.numberToolAttribute = numberToolAttribute;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    protected String getEncoding() {
        return this.encoding;
    }

    public void setCacheTemplate(boolean cacheTemplate) {
        this.cacheTemplate = cacheTemplate;
    }

    protected boolean isCacheTemplate() {
        return this.cacheTemplate;
    }

    public void setVelocityEngine(VelocityEngine velocityEngine) {
        this.velocityEngine = velocityEngine;
    }

    protected VelocityEngine getVelocityEngine() {
        return this.velocityEngine;
    }

    /**
     * Invoked on startup. Looks for a single VelocityConfig bean to find the relevant VelocityEngine for this factory.
     */
    @Override
    protected void initApplicationContext() throws BeansException {
        super.initApplicationContext();

        if (getVelocityEngine() == null) {
            // No explicit VelocityEngine: try to autodetect one.
            setVelocityEngine(autodetectVelocityEngine());
        }
    }

    /**
     * Autodetect a VelocityEngine via the ApplicationContext. Called if no explicit VelocityEngine has been specified.
     * 
     * @return The VelocityEngine to use for VelocityViews
     * @throws BeansException if no VelocityEngine could be found
     */
    protected VelocityEngine autodetectVelocityEngine() throws BeansException {
        try {
            VelocityConfig velocityConfig = BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(),
                VelocityConfig.class, true, false);
            return velocityConfig.getVelocityEngine();
        } catch (NoSuchBeanDefinitionException ex) {
            throw new ApplicationContextException(
                "Must define a single VelocityConfig bean in this web application context "
                    + "(may be inherited): ServletVelocityConfigurer is the usual implementation. "
                    + "This bean may be given any name.",
                ex);
        }
    }

    /**
     * Check that the Velocity template used for this view exists and is valid.
     * 

* Can be overridden to customize the behavior, for example in case of multiple templates to be rendered into a * single view. */ @Override public boolean checkResource(Locale locale) throws Exception { try { // Check that we can get the template, even if we might subsequently get it again. this.template = getTemplate(getUrl()); return true; } catch (ResourceNotFoundException ex) { if (logger.isDebugEnabled()) { logger.debug("No Velocity view found for URL: " + getUrl()); } return false; } catch (Exception ex) { throw new NestedIOException("Could not load Velocity template for URL [" + getUrl() + "]", ex); } } /** * Process the model map by merging it with the Velocity template. Output is directed to the servlet response. *

* This method can be overridden if custom behavior is needed. */ @Override protected void renderMergedTemplateModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { exposeHelpers(model, request); Context velocityContext = createVelocityContext(model, request, response); exposeHelpers(velocityContext, request, response); exposeToolAttributes(velocityContext, request); doRender(velocityContext, response); } /** * Expose helpers unique to each rendering operation. This is necessary so that different rendering operations can't * overwrite each other's formats etc. *

* Called by {@code renderMergedTemplateModel}. The default implementation is empty. This method can be overridden * to add custom helpers to the model. * * @param model the model that will be passed to the template for merging * @param request current HTTP request * @throws Exception if there's a fatal error while we're adding model attributes * @see #renderMergedTemplateModel */ protected void exposeHelpers(Map model, HttpServletRequest request) throws Exception {} /** * Create a Velocity JSONContext instance for the given model, to be passed to the template for merging. *

* The default implementation delegates to {@link #createVelocityContext(Map)}. Can be overridden for a special * context class, for example ChainedContext which is part of the view package of Velocity Tools. ChainedContext is * needed for initialization of ViewTool instances. *

* Have a look at {@link ServletVelocityToolboxView}, which pre-implements ChainedContext support. This is not part * of the standard ServletVelocityView class in order to avoid a required dependency on the view package of Velocity * Tools. * * @param model the model Map, containing the model attributes to be exposed to the view * @param request current HTTP request * @param response current HTTP response * @return The Velocity JSONContext * @throws Exception if there's a fatal error while creating the context */ protected Context createVelocityContext(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { return createVelocityContext(model); } /** * Create a Velocity JSONContext instance for the given model, to be passed to the template for merging. *

* Default implementation creates an instance of Velocity's VelocityContext implementation class. * * @param model The model Map, containing the model attributes to be exposed to the view. * @return The Velocity JSONContext. * @throws Exception If there's a fatal error while creating the context. * @see org.apache.velocity.VelocityContext */ protected Context createVelocityContext(Map model) throws Exception { return new VelocityContext(model); } /** * Expose helpers unique to each rendering operation. This is necessary so that different rendering operations can't * overwrite each other's formats etc. *

* Called by {@code renderMergedTemplateModel}. Default implementation delegates to * {@code exposeHelpers(velocityContext, request)}. This method can be overridden to add special tools to the * context, needing the servlet response to initialize (see Velocity Tools, for example LinkTool and * ViewTool/ChainedContext). * * @param velocityContext * Velocity context that will be passed to the template * @param request * current HTTP request * @param response * current HTTP response * @throws Exception * if there's a fatal error while we're adding model attributes * @see #exposeHelpers(org.apache.velocity.context.Context, HttpServletRequest) */ protected void exposeHelpers(Context velocityContext, HttpServletRequest request, HttpServletResponse response) throws Exception { exposeHelpers(velocityContext, request); } /** * Expose helpers unique to each rendering operation. This is necessary so that different rendering operations can't * overwrite each other's formats etc. *

* Default implementation is empty. This method can be overridden to add custom helpers to the Velocity context. * * @param velocityContext * Velocity context that will be passed to the template * @param request * current HTTP request * @throws Exception * if there's a fatal error while we're adding model attributes * @see #exposeHelpers(Map, HttpServletRequest) */ protected void exposeHelpers(Context velocityContext, HttpServletRequest request) throws Exception {} /** * Expose the tool attributes, according to corresponding bean property settings. *

* Do not override this method unless for further tools driven by bean properties. Override one of the * {@code exposeHelpers} methods to add custom helpers. * * @param velocityContext * Velocity context that will be passed to the template * @param request * current HTTP request * @throws Exception * if there's a fatal error while we're adding model attributes * @see #setDateToolAttribute * @see #setNumberToolAttribute * @see #exposeHelpers(Map, HttpServletRequest) * @see #exposeHelpers(org.apache.velocity.context.Context, HttpServletRequest, HttpServletResponse) */ protected void exposeToolAttributes(Context velocityContext, HttpServletRequest request) throws Exception { // Expose generic attributes. if (this.toolAttributes != null) { for (Map.Entry> entry : this.toolAttributes.entrySet()) { String attributeName = entry.getKey(); Class toolClass = entry.getValue(); try { Object tool = toolClass.newInstance(); initTool(tool, velocityContext); velocityContext.put(attributeName, tool); } catch (Exception ex) { throw new NestedServletException("Could not instantiate Velocity tool '" + attributeName + "'", ex); } } } // Expose locale-aware DateTool/NumberTool attributes. if (this.dateToolAttribute != null || this.numberToolAttribute != null) { if (this.dateToolAttribute != null) { velocityContext.put(this.dateToolAttribute, new LocaleAwareDateTool(request)); } if (this.numberToolAttribute != null) { velocityContext.put(this.numberToolAttribute, new LocaleAwareNumberTool(request)); } } } /** * Initialize the given tool instance. The default implementation is empty. *

* Can be overridden to check for special callback interfaces, for example the ViewContext interface which is part * of the view package of Velocity Tools. In the particular case of ViewContext, you'll usually also need a special * Velocity context, like ChainedContext which is part of Velocity Tools too. *

* Have a look at {@link ServletVelocityToolboxView}, which pre-implements such a ViewTool check. This is not part * of the standard ServletVelocityView class in order to avoid a required dependency on the view package of Velocity * Tools. * * @param tool the tool instance to initialize * @param velocityContext the Velocity context * @throws Exception if initializion of the tool failed */ protected void initTool(Object tool, Context velocityContext) throws Exception {} /** * Render the Velocity view to the given response, using the given Velocity context which contains the complete * template model to use. *

* The default implementation renders the template specified by the "url" bean property, retrieved via * {@code getTemplate}. It delegates to the {@code mergeTemplate} method to merge the template instance with the * given Velocity context. *

* Can be overridden to customize the behavior, for example to render multiple templates into a single view. * * @param context * the Velocity context to use for rendering * @param response * servlet response (use this to get the OutputStream or Writer) * @throws Exception * if thrown by Velocity * @see #setUrl * @see #getTemplate() * @see #mergeTemplate */ protected void doRender(Context context, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger .debug("Rendering Velocity template [" + getUrl() + "] in ServletVelocityView '" + getBeanName() + "'"); } mergeTemplate(getTemplate(), context, response); } /** * Retrieve the Velocity template to be rendered by this view. *

* By default, the template specified by the "url" bean property will be retrieved: either returning a cached * template instance or loading a fresh instance (according to the "cacheTemplate" bean property) * * @return The Velocity template to render * @throws Exception * if thrown by Velocity * @see #setUrl * @see #setCacheTemplate * @see #getTemplate(String) */ protected Template getTemplate() throws Exception { // We already hold a reference to the template, but we might want to load it // if not caching. Velocity itself caches templates, so our ability to // cache templates in this class is a minor optimization only. if (isCacheTemplate() && this.template != null) { return this.template; } else { return getTemplate(getUrl()); } } /** * Retrieve the Velocity template specified by the given name, using the encoding specified by the "encoding" bean * property. *

* Can be called by subclasses to retrieve a specific template, for example to render multiple templates into a * single view. * * @param name * the file name of the desired template * @return The Velocity template * @throws Exception * if thrown by Velocity * @see org.apache.velocity.app.VelocityEngine#getTemplate */ protected Template getTemplate(String name) throws Exception { return (getEncoding() != null ? getVelocityEngine().getTemplate(name, getEncoding()) : getVelocityEngine().getTemplate(name)); } /** * Merge the template with the context. Can be overridden to customize the behavior. * * @param template * the template to merge * @param context * the Velocity context to use for rendering * @param response * servlet response (use this to get the OutputStream or Writer) * @throws Exception * if thrown by Velocity * @see org.apache.velocity.Template#merge */ protected void mergeTemplate(Template template, Context context, HttpServletResponse response) throws Exception { try { template.merge(context, response.getWriter()); } catch (MethodInvocationException ex) { Throwable cause = ex.getCause(); throw new NestedServletException("Method invocation failed during rendering of Velocity view with name '" + getBeanName() + "': " + ex.getMessage() + "; reference [" + ex.getReferenceName() + "], method '" + ex.getMethodName() + "'", cause == null ? ex : cause); } } /** * Subclass of DateTool from Velocity Tools, using a Spring-resolved Locale and TimeZone instead of the default * Locale. * * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone */ private static class LocaleAwareDateTool extends DateTool { private static final long serialVersionUID = 8726680345499455254L; private final HttpServletRequest request; public LocaleAwareDateTool(HttpServletRequest request) { this.request = request; } @Override public Locale getLocale() { return RequestContextUtils.getLocale(this.request); } @Override public TimeZone getTimeZone() { TimeZone timeZone = RequestContextUtils.getTimeZone(this.request); return (timeZone != null ? timeZone : super.getTimeZone()); } } /** * Subclass of NumberTool from Velocity Tools, using a Spring-resolved Locale instead of the default Locale. * * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale */ private static class LocaleAwareNumberTool extends NumberTool { private static final long serialVersionUID = 494548903620628263L; private final HttpServletRequest request; public LocaleAwareNumberTool(HttpServletRequest request) { this.request = request; } @Override public Locale getLocale() { return RequestContextUtils.getLocale(this.request); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy