View Javadoc

    1   /*
    2    * $Id: VelocityResult.java 474191 2006-11-13 08:30:40Z mrdon $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
    10   * with the License.  You may obtain a copy of the License at
    11   *
    12   *  http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing,
    15   * software distributed under the License is distributed on an
    16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    17   * KIND, either express or implied.  See the License for the
    18   * specific language governing permissions and limitations
    19   * under the License.
    20   */
    21  package org.apache.struts2.dispatcher;
    22
    23  import java.io.OutputStreamWriter;
    24  import java.io.Writer;
    25
    26  import javax.servlet.Servlet;
    27  import javax.servlet.ServletContext;
    28  import javax.servlet.http.HttpServletRequest;
    29  import javax.servlet.http.HttpServletResponse;
    30  import javax.servlet.jsp.JspFactory;
    31  import javax.servlet.jsp.PageContext;
    32
    33  import org.apache.commons.logging.Log;
    34  import org.apache.commons.logging.LogFactory;
    35  import org.apache.struts2.ServletActionContext;
    36  import org.apache.struts2.StrutsConstants;
    37  import org.apache.struts2.views.JspSupportServlet;
    38  import org.apache.struts2.views.velocity.VelocityManager;
    39  import org.apache.velocity.Template;
    40  import org.apache.velocity.app.VelocityEngine;
    41  import org.apache.velocity.context.Context;
    42
    43  import com.opensymphony.xwork2.ActionContext;
    44  import com.opensymphony.xwork2.ActionInvocation;
    45  import com.opensymphony.xwork2.inject.Inject;
    46  import com.opensymphony.xwork2.util.ValueStack;
    47
    48
    49  /***
    50   * <!-- START SNIPPET: description -->
    51   *
    52   * Using the Servlet container's {@link JspFactory}, this result mocks a JSP
    53   * execution environment and then displays a Velocity template that will be
    54   * streamed directly to the servlet output.
    55   *
    56   * <!-- END SNIPPET: description -->
    57   * <p/>
    58   * <b>This result type takes the following parameters:</b>
    59   *
    60   * <!-- START SNIPPET: params -->
    61   *
    62   * <ul>
    63   *
    64   * <li><b>location (default)</b> - the location of the template to process.</li>
    65   *
    66   * <li><b>parse</b> - true by default. If set to false, the location param will
    67   * not be parsed for Ognl expressions.</li>
    68   *
    69   * </ul>
    70   * <p>
    71   * This result follows the same rules from {@link StrutsResultSupport}.
    72   * </p>
    73   *
    74   * <!-- END SNIPPET: params -->
    75   *
    76   * <b>Example:</b>
    77   *
    78   * <pre><!-- START SNIPPET: example -->
    79   * &lt;result name="success" type="velocity"&gt;
    80   *   &lt;param name="location"&gt;foo.vm&lt;/param&gt;
    81   * &lt;/result&gt;
    82   * <!-- END SNIPPET: example --></pre>
    83   *
    84   */
    85  public class VelocityResult extends StrutsResultSupport {
    86
    87      private static final long serialVersionUID = 7268830767762559424L;
    88
    89      private static final Log log = LogFactory.getLog(VelocityResult.class);
    90
    91      private String defaultEncoding;
    92      private VelocityManager velocityManager;
    93
    94      public VelocityResult() {
    95          super();
    96      }
    97
    98      public VelocityResult(String location) {
    99          super(location);
    100     }
    101
    102     @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
    103     public void setDefaultEncoding(String val) {
    104         defaultEncoding = val;
    105     }
    106
    107     @Inject
    108     public void setVelocityManager(VelocityManager mgr) {
    109         this.velocityManager = mgr;
    110     }
    111
    112     /***
    113      * Creates a Velocity context from the action, loads a Velocity template and executes the
    114      * template. Output is written to the servlet output stream.
    115      *
    116      * @param finalLocation the location of the Velocity template
    117      * @param invocation    an encapsulation of the action execution state.
    118      * @throws Exception if an error occurs when creating the Velocity context, loading or executing
    119      *                   the template or writing output to the servlet response stream.
    120      */
    121     public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
    122         ValueStack stack = ActionContext.getContext().getValueStack();
    123
    124         HttpServletRequest request = ServletActionContext.getRequest();
    125         HttpServletResponse response = ServletActionContext.getResponse();
    126         JspFactory jspFactory = null;
    127         ServletContext servletContext = ServletActionContext.getServletContext();
    128         Servlet servlet = JspSupportServlet.jspSupportServlet;
    129
    130         velocityManager.init(servletContext);
    131
    132         boolean usedJspFactory = false;
    133         PageContext pageContext = (PageContext) ActionContext.getContext().get(ServletActionContext.PAGE_CONTEXT);
    134
    135         if (pageContext == null && servlet != null) {
    136             jspFactory = JspFactory.getDefaultFactory();
    137             pageContext = jspFactory.getPageContext(servlet, request, response, null, true, 8192, true);
    138             ActionContext.getContext().put(ServletActionContext.PAGE_CONTEXT, pageContext);
    139             usedJspFactory = true;
    140         }
    141
    142         try {
    143             String encoding = getEncoding(finalLocation);
    144             String contentType = getContentType(finalLocation);
    145
    146             if (encoding != null) {
    147                 contentType = contentType + ";charset=" + encoding;
    148             }
    149
    150             Template t = getTemplate(stack, velocityManager.getVelocityEngine(), invocation, finalLocation, encoding);
    151
    152             Context context = createContext(velocityManager, stack, request, response, finalLocation);
    153             Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
    154
    155
    156             response.setContentType(contentType);
    157
    158             t.merge(context, writer);
    159
    160             // always flush the writer (we used to only flush it if this was a jspWriter, but someone asked
    161             // to do it all the time (WW-829). Since Velocity support is being deprecated, we'll oblige :)
    162             writer.flush();
    163         } catch (Exception e) {
    164             log.error("Unable to render Velocity Template, '" + finalLocation + "'", e);
    165             throw e;
    166         } finally {
    167             if (usedJspFactory) {
    168                 jspFactory.releasePageContext(pageContext);
    169             }
    170         }
    171
    172         return;
    173     }
    174
    175     /***
    176      * Retrieve the content type for this template.
    177      * <p/>
    178      * People can override this method if they want to provide specific content types for specific templates (eg text/xml).
    179      *
    180      * @return The content type associated with this template (default "text/html")
    181      */
    182     protected String getContentType(String templateLocation) {
    183         return "text/html";
    184     }
    185
    186     /***
    187      * Retrieve the encoding for this template.
    188      * <p/>
    189      * People can override this method if they want to provide specific encodings for specific templates.
    190      *
    191      * @return The encoding associated with this template (defaults to the value of 'struts.i18n.encoding' property)
    192      */
    193     protected String getEncoding(String templateLocation) {
    194         String encoding = defaultEncoding;
    195         if (encoding == null) {
    196             encoding = System.getProperty("file.encoding");
    197         }
    198         if (encoding == null) {
    199             encoding = "UTF-8";
    200         }
    201         return encoding;
    202     }
    203
    204     /***
    205      * Given a value stack, a Velocity engine, and an action invocation, this method returns the appropriate
    206      * Velocity template to render.
    207      *
    208      * @param stack      the value stack to resolve the location again (when parse equals true)
    209      * @param velocity   the velocity engine to process the request against
    210      * @param invocation an encapsulation of the action execution state.
    211      * @param location   the location of the template
    212      * @param encoding   the charset encoding of the template
    213      * @return the template to render
    214      * @throws Exception when the requested template could not be found
    215      */
    216     protected Template getTemplate(ValueStack stack, VelocityEngine velocity, ActionInvocation invocation, String location, String encoding) throws Exception {
    217         if (!location.startsWith("/")) {
    218             location = invocation.getProxy().getNamespace() + "/" + location;
    219         }
    220
    221         Template template = velocity.getTemplate(location, encoding);
    222
    223         return template;
    224     }
    225
    226     /***
    227      * Creates the VelocityContext that we'll use to render this page.
    228      *
    229      * @param velocityManager a reference to the velocityManager to use
    230      * @param stack           the value stack to resolve the location against (when parse equals true)
    231      * @param location        the name of the template that is being used
    232      * @return the a minted Velocity context.
    233      */
    234     protected Context createContext(VelocityManager velocityManager, ValueStack stack, HttpServletRequest request, HttpServletResponse response, String location) {
    235         return velocityManager.createContext(stack, request, response);
    236     }
    237 }