View Javadoc

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