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.views.freemarker;
23
24 import java.io.CharArrayWriter;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.util.Locale;
28
29 import javax.servlet.ServletContext;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.apache.struts2.ServletActionContext;
34 import org.apache.struts2.dispatcher.StrutsResultSupport;
35 import org.apache.struts2.views.util.ResourceUtil;
36
37 import com.opensymphony.xwork2.ActionContext;
38 import com.opensymphony.xwork2.ActionInvocation;
39 import com.opensymphony.xwork2.LocaleProvider;
40 import com.opensymphony.xwork2.inject.Inject;
41 import com.opensymphony.xwork2.util.ValueStack;
42
43 import freemarker.template.Configuration;
44 import freemarker.template.ObjectWrapper;
45 import freemarker.template.Template;
46 import freemarker.template.TemplateException;
47 import freemarker.template.TemplateExceptionHandler;
48 import freemarker.template.TemplateModel;
49 import freemarker.template.TemplateModelException;
50
51
52 /***
53 * <!-- START SNIPPET: description -->
54 *
55 * Renders a view using the Freemarker template engine.
56 * <p>
57 * The FreemarkarManager class configures the template loaders so that the
58 * template location can be either
59 * </p>
60 *
61 * <ul>
62 *
63 * <li>relative to the web root folder. eg <code>/WEB-INF/views/home.ftl</code>
64 * </li>
65 *
66 * <li>a classpath resuorce. eg <code>/com/company/web/views/home.ftl</code></li>
67 *
68 * </ul>
69 *
70 * <!-- END SNIPPET: description -->
71 *
72 * <b>This result type takes the following parameters:</b>
73 *
74 * <!-- START SNIPPET: params -->
75 *
76 * <ul>
77 *
78 * <li><b>location (default)</b> - the location of the template to process.</li>
79 *
80 * <li><b>parse</b> - true by default. If set to false, the location param will
81 * not be parsed for Ognl expressions.</li>
82 *
83 * <li><b>contentType</b> - defaults to "text/html" unless specified.</li>
84 *
85 * <li><b>writeIfCompleted</b> - false by default, write to stream only if there isn't any error
86 * processing the template. Setting template_exception_handler=rethrow in freemarker.properties
87 * will have the same effect.</li>
88 *
89 * </ul>
90 *
91 * <!-- END SNIPPET: params -->
92 *
93 * <b>Example:</b>
94 *
95 * <pre>
96 * <!-- START SNIPPET: example -->
97 *
98 * <result name="success" type="freemarker">foo.ftl</result>
99 *
100 * <!-- END SNIPPET: example -->
101 * </pre>
102 */
103 public class FreemarkerResult extends StrutsResultSupport {
104
105 private static final long serialVersionUID = -3778230771704661631L;
106
107 protected ActionInvocation invocation;
108 protected Configuration configuration;
109 protected ObjectWrapper wrapper;
110 protected FreemarkerManager freemarkerManager;
111 private Writer writer;
112 private boolean writeIfCompleted = false;
113
114
115
116
117
118 protected String location;
119 private String pContentType = "text/html";
120
121 public FreemarkerResult() {
122 super();
123 }
124
125 public FreemarkerResult(String location) {
126 super(location);
127 }
128
129 @Inject
130 public void setFreemarkerManager(FreemarkerManager mgr) {
131 this.freemarkerManager = mgr;
132 }
133
134 public void setContentType(String aContentType) {
135 pContentType = aContentType;
136 }
137
138 /***
139 * allow parameterization of the contentType
140 * the default being text/html
141 */
142 public String getContentType() {
143 return pContentType;
144 }
145
146 /***
147 * Execute this result, using the specified template locationArg.
148 * <p/>
149 * The template locationArg has already been interoplated for any variable substitutions
150 * <p/>
151 * this method obtains the freemarker configuration and the object wrapper from the provided hooks.
152 * It them implements the template processing workflow by calling the hooks for
153 * preTemplateProcess and postTemplateProcess
154 */
155 public void doExecute(String locationArg, ActionInvocation invocation) throws IOException, TemplateException {
156 this.location = locationArg;
157 this.invocation = invocation;
158 this.configuration = getConfiguration();
159 this.wrapper = getObjectWrapper();
160
161 if (!locationArg.startsWith("/")) {
162 ActionContext ctx = invocation.getInvocationContext();
163 HttpServletRequest req = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
164 String base = ResourceUtil.getResourceBase(req);
165 locationArg = base + "/" + locationArg;
166 }
167
168 Template template = configuration.getTemplate(locationArg, deduceLocale());
169 TemplateModel model = createModel();
170
171
172 if (preTemplateProcess(template, model)) {
173 try {
174
175 Writer writer = getWriter();
176 if (isWriteIfCompleted() || configuration.getTemplateExceptionHandler() == TemplateExceptionHandler.RETHROW_HANDLER) {
177 CharArrayWriter charArrayWriter = new CharArrayWriter();
178 try {
179 template.process(model, charArrayWriter);
180 charArrayWriter.flush();
181 charArrayWriter.writeTo(writer);
182 } finally {
183 if (charArrayWriter != null)
184 charArrayWriter.close();
185 }
186 } else {
187 template.process(model, writer);
188 }
189 } finally {
190
191 postTemplateProcess(template, model);
192 }
193 }
194 }
195
196 /***
197 * This method is called from {@link #doExecute(String, ActionInvocation)} to obtain the
198 * FreeMarker configuration object that this result will use for template loading. This is a
199 * hook that allows you to custom-configure the configuration object in a subclass, or to fetch
200 * it from an IoC container.
201 * <p/>
202 * <b>
203 * The default implementation obtains the configuration from the ConfigurationManager instance.
204 * </b>
205 */
206 protected Configuration getConfiguration() throws TemplateException {
207 return freemarkerManager.getConfiguration(ServletActionContext.getServletContext());
208 }
209
210 /***
211 * This method is called from {@link #doExecute(String, ActionInvocation)} to obtain the
212 * FreeMarker object wrapper object that this result will use for adapting objects into template
213 * models. This is a hook that allows you to custom-configure the wrapper object in a subclass.
214 * <p/>
215 * <b>
216 * The default implementation returns {@link Configuration#getObjectWrapper()}
217 * </b>
218 */
219 protected ObjectWrapper getObjectWrapper() {
220 return configuration.getObjectWrapper();
221 }
222
223
224 public void setWriter(Writer writer) {
225 this.writer = writer;
226 }
227
228 /***
229 * The default writer writes directly to the response writer.
230 */
231 protected Writer getWriter() throws IOException {
232 if(writer != null) {
233 return writer;
234 }
235 return ServletActionContext.getResponse().getWriter();
236 }
237
238 /***
239 * Build the instance of the ScopesHashModel, including JspTagLib support
240 * <p/>
241 * Objects added to the model are
242 * <p/>
243 * <ul>
244 * <li>Application - servlet context attributes hash model
245 * <li>JspTaglibs - jsp tag lib factory model
246 * <li>Request - request attributes hash model
247 * <li>Session - session attributes hash model
248 * <li>request - the HttpServletRequst object for direct access
249 * <li>response - the HttpServletResponse object for direct access
250 * <li>stack - the OgnLValueStack instance for direct access
251 * <li>ognl - the instance of the OgnlTool
252 * <li>action - the action itself
253 * <li>exception - optional : the JSP or Servlet exception as per the servlet spec (for JSP Exception pages)
254 * <li>struts - instance of the StrutsUtil class
255 * </ul>
256 */
257 protected TemplateModel createModel() throws TemplateModelException {
258 ServletContext servletContext = ServletActionContext.getServletContext();
259 HttpServletRequest request = ServletActionContext.getRequest();
260 HttpServletResponse response = ServletActionContext.getResponse();
261 ValueStack stack = ServletActionContext.getContext().getValueStack();
262
263 Object action = null;
264 if(invocation!= null ) action = invocation.getAction();
265 return freemarkerManager.buildTemplateModel(stack, action, servletContext, request, response, wrapper);
266 }
267
268 /***
269 * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call. The base implementation
270 * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
271 * the action does not the configuration's locale is returned. Override this method to provide different behaviour,
272 */
273 protected Locale deduceLocale() {
274 if (invocation.getAction() instanceof LocaleProvider) {
275 return ((LocaleProvider) invocation.getAction()).getLocale();
276 } else {
277 return configuration.getLocale();
278 }
279 }
280
281 /***
282 * the default implementation of postTemplateProcess applies the contentType parameter
283 */
284 protected void postTemplateProcess(Template template, TemplateModel data) throws IOException {
285 }
286
287 /***
288 * Called before the execution is passed to template.process().
289 * This is a generic hook you might use in subclasses to perform a specific
290 * action before the template is processed. By default does nothing.
291 * A typical action to perform here is to inject application-specific
292 * objects into the model root
293 *
294 * @return true to process the template, false to suppress template processing.
295 */
296 protected boolean preTemplateProcess(Template template, TemplateModel model) throws IOException {
297 Object attrContentType = template.getCustomAttribute("content_type");
298
299 if (attrContentType != null) {
300 ServletActionContext.getResponse().setContentType(attrContentType.toString());
301 } else {
302 String contentType = getContentType();
303
304 if (contentType == null) {
305 contentType = "text/html";
306 }
307
308 String encoding = template.getEncoding();
309
310 if (encoding != null) {
311 contentType = contentType + "; charset=" + encoding;
312 }
313
314 ServletActionContext.getResponse().setContentType(contentType);
315 }
316
317 return true;
318 }
319
320 /***
321 * @return true write to the stream only when template processing completed successfully (false by default)
322 */
323 public boolean isWriteIfCompleted() {
324 return writeIfCompleted;
325 }
326
327 /***
328 * Writes to the stream only when template processing completed successfully
329 */
330 public void setWriteIfCompleted(boolean writeIfCompleted) {
331 this.writeIfCompleted = writeIfCompleted;
332 }
333 }