View Javadoc

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