View Javadoc

1   /*
2    * $Id: FreemarkerResult.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.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   * &lt;result name="success" type="freemarker"&gt;foo.ftl&lt;/result&gt;
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      * Struts results are constructed for each result execution
115      *
116      * the current context is availible to subclasses via these protected fields
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         // Give subclasses a chance to hook into preprocessing
172         if (preTemplateProcess(template, model)) {
173             try {
174                 // Process the template
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                 // Give subclasses a chance to hook into postprocessing
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(); //Added for NullPointException
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 }