1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 * <result name="success" type="freemarker">foo.ftl</result>
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
108
109
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
165 if (preTemplateProcess(template, model)) {
166 try {
167
168 template.process(model, getWriter());
169 } finally {
170
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();
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 }