1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 * <result name="success" type="freemarker">foo.ftl</result>
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
102
103
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
154 if (preTemplateProcess(template, model)) {
155 try {
156
157 template.process(model, getWriter());
158 } finally {
159
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();
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 }