View Javadoc

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