Webwork2のVelocity

以前Webwork2で、VelocityToolが使えないと書きました。
今以前の日記を読み返したら理由を書いてなかった・・・
WebworkでVelocityToolServletを使用することは可能なのですが、
その場合、$stack、$webwork、$ognlが使えません。
VelocityToolが使えてWebworkの変数も使えるようにはできないのか?
そこで、いろいろサイトを巡り参考にして作ってみました。
一部id:khiさんのWikiを参考にさせていただきました。感謝です。
以下にソースをはっておきます。字が小さくて申し訳ないです。
不具合等あれば教えて下さい。


まず、WebWorkVelocityViewServlet
これはWebworkのJIRAにあったものをそのまま頂いて、少し手を加えました。
web.xmlにはVelocityViewServletと同じように、
velocity.propertiesとtoolbox.xmlをパラメータとして渡してあげてください。ここで定義したToolは後で出てくるResultでも使えます。
このまま使うと勝手にHTMLエスケープされて表示されます。
そこでエスケープしてはいけないところをUnescapeToolで対応します。

package hogehoge.velocity;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspFactory;
import javax.servlet.jsp.PageContext;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.velocity.Template;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.servlet.VelocityServlet;
import org.apache.velocity.tools.view.context.ChainedContext;
import org.apache.velocity.tools.view.servlet.VelocityViewServlet;

import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.webwork.config.Configuration;
import com.opensymphony.webwork.views.velocity.VelocityManager;
import com.opensymphony.xwork.ActionContext;

/**
 * @created 24-Sep-2004
 * @author Nick Curry
 */
public class WebWorkVelocityViewServlet extends VelocityViewServlet {
    //~ Instance fields
    // ////////////////////////////////////////////////////////
    private VelocityManager velocityManager;
    
    protected EventCartridge eventCartrigde;

    //~ Constructors
    // ///////////////////////////////////////////////////////////
    public WebWorkVelocityViewServlet() {
        velocityManager = VelocityManager.getInstance();
    }

    //~ Methods
    // ////////////////////////////////////////////////////////////////
    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);
        // initialize our VelocityManager
        velocityManager.init(servletConfig.getServletContext());
        servletConfig.getServletContext().setAttribute("webwork.servlet", this);
        initEventCartridge(servletConfig);
    }

    protected Context createContext(HttpServletRequest request,
            HttpServletResponse response) {
        ChainedContext ctx = new ChainedContext(velocityManager.createContext(
                ActionContext.getContext().getValueStack(), request, response),
                request, response, getServletContext());
        /* if we have a toolbox manager, get a toolbox from it */
        if (toolboxManager != null) {
            ctx.setToolbox(toolboxManager.getToolboxContext(ctx));
        }
        eventCartrigde.attachToContext(ctx);
        return ctx;
    }

    protected Template handleRequest(HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse, Context context)
            throws Exception {
        String servletPath = (String) httpServletRequest
                .getAttribute("javax.servlet.include.servlet_path");
        if (servletPath == null) {
            servletPath = httpServletRequest.getServletPath();
        }
        return getTemplate(servletPath, getEncoding());
    }

    /**
     * This method extends the VelocityServlet's loadConfiguration method by
     * performing the following actions:
     * <ul>
     * <li>invokes VelocityServlet.loadConfiguration to create a properties
     * object</li>
     * <li>alters the RESOURCE_LOADER to include a class loader</li>
     * <li>configures the class loader using the WebWorkResourceLoader</li>
     * </ul>
     * 
     * @param servletConfig
     * @throws IOException
     * @throws FileNotFoundException
     * @see org.apache.velocity.servlet.VelocityServlet#loadConfiguration
     */
    protected ExtendedProperties loadConfiguration(ServletConfig servletConfig)
            throws IOException, FileNotFoundException {
        return ExtendedProperties.convertProperties(velocityManager
                .loadConfiguration(servletConfig.getServletContext()));
    }

    /**
     * create a PageContext and render the template to PageContext.getOut()
     * 
     * @see VelocityServlet#mergeTemplate(Template, Context,
     *      HttpServletResponse) for additional documentation
     */
    protected void mergeTemplate(Template template, Context context,
            HttpServletResponse response) throws ResourceNotFoundException,
            ParseErrorException, MethodInvocationException, IOException,
            UnsupportedEncodingException, Exception {
        // save the old PageContext
        PageContext oldPageContext = ServletActionContext.getPageContext();
        // create a new PageContext
        JspFactory jspFactory = JspFactory.getDefaultFactory();
        HttpServletRequest request = (HttpServletRequest) context
                .get(VelocityManager.REQUEST);
        PageContext pageContext = jspFactory.getPageContext(this, request,
                response, null, true, 8192, true);
        // put the new PageContext into ActionContext
        ActionContext actionContext = ActionContext.getContext();
        actionContext.put(ServletActionContext.PAGE_CONTEXT, pageContext);
        try {
            Writer writer = pageContext.getOut();
            template.merge(context, writer);
            writer.flush();
        } finally {
            // perform cleanup
            jspFactory.releasePageContext(pageContext);
            actionContext
                    .put(ServletActionContext.PAGE_CONTEXT, oldPageContext);
        }
    }

    private String getEncoding() {
        // todo look into converting this to using XWork/WebWork2 encoding rules
        try {
            return Configuration.getString("webwork.i18n.encoding");
        } catch (IllegalArgumentException e) {
            return RuntimeSingleton.getString(RuntimeSingleton.OUTPUT_ENCODING,
                    DEFAULT_OUTPUT_ENCODING);
        }
    }

    protected void initEventCartridge(ServletConfig config)
            throws ServletException {
        eventCartrigde = new EventCartridge();
        eventCartrigde.addEventHandler(new ReferenceInsertionEventHandler() {
            public Object referenceInsert(String reference, Object value) {
                	//System.out.println("value:" + value + ":reference:"+reference);
                if (value == null || value instanceof Unescape) {
                    return value;
                } else {
                    //System.out.println(StringUtils.escape(value.toString()));
                    //ここは好きなメソッドを使ってください。
                    return StringEscapeUtils.escapeHtml(value.toString());
                }
            }
        });
    }
}

次にこの中で使っているUnescapeのソース

package hogehoge.velocity;

public class Unescape {
    private Object object;
    
    protected Unescape(Object object){
        this.object = object;
    }
    
    public String toString(){
        return object == null ? null:object.toString();
    }

}

それのToolバージョン

package hogehoge.velocity;

public class UnescapeTool {
    
    public static Unescape unescape(Object object){
        return object == null ? null : new Unescape(object);
    }

}

でもって、WebworkのResult部分のVelocityResult(Tool版)
xwork-default.xmlを書き換えるか、resutl typeを新しく定義してやってください。

/*
 * Copyright (c) 2002-2003 by OpenSymphony
 * All rights reserved.
 */
package hogehoge.velocity;

import java.io.Writer;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspFactory;
import javax.servlet.jsp.PageContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;

import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.webwork.config.Configuration;
import com.opensymphony.webwork.dispatcher.WebWorkResultSupport;
import com.opensymphony.webwork.views.velocity.VelocityManager;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.util.OgnlValueStack;


/**
 * Using the Servlet container's {@link JspFactory}, this result mocks a JSP execution environment
 * and then displays a Velocity template that will be streamed directly to the servlet output. <p>
 * <p/>
 * This result follows the same rules from {@link WebWorkResultSupport}.
 *
 * @author <a href="mailto:matt@indigoegg.com">Matt Ho</a>
 */
public class VelocityResult extends WebWorkResultSupport {
    //~ Static fields/initializers /////////////////////////////////////////////

    private static final Log log = LogFactory.getLog(VelocityResult.class);

    //~ Methods ////////////////////////////////////////////////////////////////

    /**
     * Creates a Velocity context from the action, loads a Velocity template and executes the
     * template. Output is written to the servlet output stream.
     *
     * @param finalLocation the location of the Velocity template
     * @param invocation    an encapsulation of the action execution state.
     * @throws Exception if an error occurs when creating the Velocity context, loading or executing
     *                   the template or writing output to the servlet response stream.
     */
    public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
        OgnlValueStack stack = ActionContext.getContext().getValueStack();

        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        JspFactory jspFactory = null;
        ServletContext servletContext = ServletActionContext.getServletContext();
        //Servlet servlet = (Servlet) servletContext.getAttribute("webwork.servlet");
        
        WebWorkVelocityViewServlet servlet = (WebWorkVelocityViewServlet) servletContext.getAttribute("webwork.servlet");

        boolean usedJspFactory = false;
        PageContext pageContext = (PageContext) ActionContext.getContext().get(ServletActionContext.PAGE_CONTEXT);

        if (pageContext == null) {
            jspFactory = JspFactory.getDefaultFactory();
            pageContext = jspFactory.getPageContext(servlet, request, response, null, true, 8192, true);
            ActionContext.getContext().put(ServletActionContext.PAGE_CONTEXT, pageContext);
            usedJspFactory = true;
        }

        try {
            VelocityManager velocityManager = VelocityManager.getInstance();
            Template t = getTemplate(stack, velocityManager.getVelocityEngine(), invocation, finalLocation);

            //Context context = createContext(velocityManager, stack, request, response, finalLocation);
            Context context = servlet.createContext(request, response);
            Writer writer = pageContext.getOut();

            if (usedJspFactory) {
                String encoding = getEncoding(finalLocation);
                String contentType = getContentType(finalLocation);

                if (encoding != null) {
                    contentType = contentType + ";charset=" + encoding;
                }

                response.setContentType(contentType);
            }

           // t.merge(context, writer);
            servlet.mergeTemplate(t,context,response);

            if (usedJspFactory) {
                writer.flush();
            }
        } catch (Exception e) {
            log.error("Unable to render Velocity Template, '" + finalLocation + "'", e);
            throw e;
        } finally {
            if (usedJspFactory) {
                jspFactory.releasePageContext(pageContext);
            }
        }

        return;
    }

    /**
     * Retrieve the content type for this template.
     * <p/>
     * People can override this method if they want to provide specific content types for specific templates (eg text/xml).
     *
     * @return The content type associated with this template (default "text/html")
     */
    protected String getContentType(String templateLocation) {
        return "text/html";
    }

    /**
     * Retrieve the encoding for this template.
     * <p/>
     * People can override this method if they want to provide specific encodings for specific templates.
     *
     * @return The encoding associated with this template (defaults to the value of 'webwork.i18n.encoding' property)
     */
    protected String getEncoding(String templateLocation) {
        return (String) Configuration.get("webwork.i18n.encoding");
    }

    /**
     * Given a value stack, a Velocity engine, and an action invocation, this method returns the appropriate
     * Velocity template to render.
     *
     * @param stack      the value stack to resolve the location again (when parse equals true)
     * @param velocity   the velocity engine to process the request against
     * @param invocation an encapsulation of the action execution state.
     * @param location   the location of the template
     * @return the template to render
     * @throws Exception when the requested template could not be found
     */
    protected Template getTemplate(OgnlValueStack stack, VelocityEngine velocity, ActionInvocation invocation, String location) throws Exception {
        if (!location.startsWith("/")) {
            location = invocation.getProxy().getNamespace() + "/" + location;
        }

        Template template = velocity.getTemplate(location);

        return template;
    }

    /**
     * Creates the VelocityContext that we'll use to render this page.
     *
     * @param velocityManager a reference to the velocityManager to use
     * @param stack           the value stack to resolve the location against (when parse equals true)
     * @param location        the name of the template that is being used
     * @return the a minted Velocity context.
     */
    protected Context createContext(VelocityManager velocityManager, OgnlValueStack stack, HttpServletRequest request, HttpServletResponse response, String location) {
        return velocityManager.createContext(stack, request, response);
    }
}