1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 * <result name="success" type="velocity">
81 * <param name="location">foo.vm</param>
82 * </result>
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
162
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 }