1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.views.freemarker;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.Set;
32
33 import javax.servlet.GenericServlet;
34 import javax.servlet.ServletContext;
35 import javax.servlet.http.HttpServletRequest;
36 import javax.servlet.http.HttpServletResponse;
37 import javax.servlet.http.HttpSession;
38
39 import org.apache.struts2.StrutsConstants;
40 import org.apache.struts2.views.JspSupportServlet;
41 import org.apache.struts2.views.TagLibrary;
42 import org.apache.struts2.views.util.ContextUtil;
43
44 import com.opensymphony.xwork2.inject.Container;
45 import com.opensymphony.xwork2.inject.Inject;
46 import com.opensymphony.xwork2.util.FileManager;
47 import com.opensymphony.xwork2.util.ValueStack;
48 import com.opensymphony.xwork2.util.logging.Logger;
49 import com.opensymphony.xwork2.util.logging.LoggerFactory;
50
51 import freemarker.cache.FileTemplateLoader;
52 import freemarker.cache.MultiTemplateLoader;
53 import freemarker.cache.TemplateLoader;
54 import freemarker.cache.WebappTemplateLoader;
55 import freemarker.ext.beans.BeansWrapper;
56 import freemarker.ext.jsp.TaglibFactory;
57 import freemarker.ext.servlet.HttpRequestHashModel;
58 import freemarker.ext.servlet.HttpRequestParametersHashModel;
59 import freemarker.ext.servlet.HttpSessionHashModel;
60 import freemarker.ext.servlet.ServletContextHashModel;
61 import freemarker.template.ObjectWrapper;
62 import freemarker.template.SimpleHash;
63 import freemarker.template.TemplateException;
64 import freemarker.template.TemplateExceptionHandler;
65 import freemarker.template.TemplateModel;
66
67
68 /***
69 * Static Configuration Manager for the FreemarkerResult's configuration
70 *
71 * <p/>
72 *
73 * Possible extension points are :-
74 * <ul>
75 * <li>createConfiguration method</li>
76 * <li>loadSettings method</li>
77 * <li>getTemplateLoader method</li>
78 * <li>populateContext method</li>
79 * </ul>
80 *
81 * <p/>
82 * <b> createConfiguration method </b><br/>
83 * Create a freemarker Configuration.
84 * <p/>
85 *
86 * <b> loadSettings method </b><br/>
87 * Load freemarker settings, default to freemarker.properties (if found in classpath)
88 * <p/>
89 *
90 * <b> getTemplateLoader method</b><br/>
91 * create a freemarker TemplateLoader that loads freemarker template in the following order :-
92 * <ol>
93 * <li>path defined in ServletContext init parameter named 'templatePath' or 'TemplatePath' (must be an absolute path)</li>
94 * <li>webapp classpath</li>
95 * <li>struts's static folder (under [STRUT2_SOURCE]/org/apache/struts2/static/</li>
96 * </ol>
97 * <p/>
98 *
99 * <b> populateContext method</b><br/>
100 * populate the created model.
101 *
102 */
103 public class FreemarkerManager {
104
105 private static final Logger LOG = LoggerFactory.getLogger(FreemarkerManager.class);
106 public static final String CONFIG_SERVLET_CONTEXT_KEY = "freemarker.Configuration";
107 public static final String KEY_EXCEPTION = "exception";
108
109
110 protected static final String ATTR_APPLICATION_MODEL = ".freemarker.Application";
111 protected static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs";
112 protected static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
113 protected static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters";
114
115
116 public static final String KEY_APPLICATION = "Application";
117 public static final String KEY_REQUEST_MODEL = "Request";
118 public static final String KEY_SESSION_MODEL = "Session";
119 public static final String KEY_JSP_TAGLIBS = "JspTaglibs";
120 public static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters";
121
122 protected String encoding;
123 protected boolean altMapWrapper;
124 protected boolean cacheBeanWrapper;
125 protected int mruMaxStrongSize;
126 protected Map<String,TagLibrary> tagLibraries;
127
128 @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
129 public void setEncoding(String encoding) {
130 this.encoding = encoding;
131 }
132
133 @Inject(StrutsConstants.STRUTS_FREEMARKER_WRAPPER_ALT_MAP)
134 public void setWrapperAltMap(String val) {
135 altMapWrapper = "true".equals(val);
136 }
137
138 @Inject(StrutsConstants.STRUTS_FREEMARKER_BEANWRAPPER_CACHE)
139 public void setCacheBeanWrapper(String val) {
140 cacheBeanWrapper = "true".equals(val);
141 }
142
143 @Inject(StrutsConstants.STRUTS_FREEMARKER_MRU_MAX_STRONG_SIZE)
144 public void setMruMaxStrongSize(String size) {
145 mruMaxStrongSize = Integer.parseInt(size);
146 }
147
148 @Inject
149 public void setContainer(Container container) {
150 Map<String,TagLibrary> map = new HashMap<String,TagLibrary>();
151 Set<String> prefixes = container.getInstanceNames(TagLibrary.class);
152 for (String prefix : prefixes) {
153 map.put(prefix, container.getInstance(TagLibrary.class, prefix));
154 }
155 this.tagLibraries = Collections.unmodifiableMap(map);
156 }
157
158 public synchronized freemarker.template.Configuration getConfiguration(ServletContext servletContext) throws TemplateException {
159 freemarker.template.Configuration config = (freemarker.template.Configuration) servletContext.getAttribute(CONFIG_SERVLET_CONTEXT_KEY);
160
161 if (config == null) {
162 config = createConfiguration(servletContext);
163
164
165 servletContext.setAttribute(CONFIG_SERVLET_CONTEXT_KEY, config);
166 }
167
168 config.setWhitespaceStripping(true);
169
170 return config;
171 }
172
173 protected ScopesHashModel buildScopesHashModel(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper, ValueStack stack) {
174 ScopesHashModel model = new ScopesHashModel(wrapper, servletContext, request, stack);
175
176
177
178 synchronized (servletContext) {
179 ServletContextHashModel servletContextModel = (ServletContextHashModel) servletContext.getAttribute(ATTR_APPLICATION_MODEL);
180
181 if (servletContextModel == null) {
182
183 GenericServlet servlet = JspSupportServlet.jspSupportServlet;
184
185
186 if (servlet != null) {
187 servletContextModel = new ServletContextHashModel(servlet, wrapper);
188 servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel);
189 TaglibFactory taglibs = new TaglibFactory(servletContext);
190 servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
191 }
192
193 }
194
195 model.put(KEY_APPLICATION, servletContextModel);
196 model.put(KEY_JSP_TAGLIBS, (TemplateModel) servletContext.getAttribute(ATTR_JSP_TAGLIBS_MODEL));
197 }
198
199
200 HttpSession session = request.getSession(false);
201 if (session != null) {
202 model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper));
203 } else {
204
205
206 }
207
208
209 HttpRequestHashModel requestModel = (HttpRequestHashModel) request.getAttribute(ATTR_REQUEST_MODEL);
210
211 if ((requestModel == null) || (requestModel.getRequest() != request)) {
212 requestModel = new HttpRequestHashModel(request, response, wrapper);
213 request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
214 }
215
216 model.put(KEY_REQUEST_MODEL, requestModel);
217
218
219
220 HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel) request.getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
221 if (reqParametersModel == null || requestModel.getRequest() != request) {
222 reqParametersModel = new HttpRequestParametersHashModel(request);
223 request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL, reqParametersModel);
224 }
225 model.put(KEY_REQUEST_PARAMETER_MODEL, reqParametersModel);
226
227 return model;
228 }
229
230 protected void populateContext(ScopesHashModel model, ValueStack stack, Object action, HttpServletRequest request, HttpServletResponse response) {
231
232 Map standard = ContextUtil.getStandardContext(stack, request, response);
233 model.putAll(standard);
234
235
236 Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception");
237
238 if (exception == null) {
239 exception = (Throwable) request.getAttribute("javax.servlet.error.JspException");
240 }
241
242 if (exception != null) {
243 model.put(KEY_EXCEPTION, exception);
244 }
245 }
246
247 protected BeansWrapper getObjectWrapper() {
248 StrutsBeanWrapper wrapper = new StrutsBeanWrapper(altMapWrapper);
249 wrapper.setUseCache(cacheBeanWrapper);
250 return wrapper;
251 }
252
253 /***
254 * The default template loader is a MultiTemplateLoader which includes
255 * a ClassTemplateLoader and a WebappTemplateLoader (and a FileTemplateLoader depending on
256 * the init-parameter 'TemplatePath').
257 * <p/>
258 * The ClassTemplateLoader will resolve fully qualified template includes
259 * that begin with a slash. for example /com/company/template/common.ftl
260 * <p/>
261 * The WebappTemplateLoader attempts to resolve templates relative to the web root folder
262 */
263 protected TemplateLoader getTemplateLoader(ServletContext servletContext) {
264
265 FileTemplateLoader templatePathLoader = null;
266
267 String templatePath = servletContext.getInitParameter("TemplatePath");
268 if (templatePath == null) {
269 templatePath = servletContext.getInitParameter("templatePath");
270 }
271
272 if (templatePath != null) {
273 try {
274 templatePathLoader = new FileTemplateLoader(new File(templatePath));
275 } catch (IOException e) {
276 LOG.error("Invalid template path specified: " + e.getMessage(), e);
277 }
278 }
279
280
281
282 return templatePathLoader != null ?
283 new MultiTemplateLoader(new TemplateLoader[]{
284 templatePathLoader,
285 new WebappTemplateLoader(servletContext),
286 new StrutsClassTemplateLoader()
287 })
288 : new MultiTemplateLoader(new TemplateLoader[]{
289 new WebappTemplateLoader(servletContext),
290 new StrutsClassTemplateLoader()
291 });
292 }
293
294 /***
295 * Create the instance of the freemarker Configuration object.
296 * <p/>
297 * this implementation
298 * <ul>
299 * <li>obtains the default configuration from Configuration.getDefaultConfiguration()
300 * <li>sets up template loading from a ClassTemplateLoader and a WebappTemplateLoader
301 * <li>sets up the object wrapper to be the BeansWrapper
302 * <li>loads settings from the classpath file /freemarker.properties
303 * </ul>
304 *
305 * @param servletContext
306 */
307 protected freemarker.template.Configuration createConfiguration(ServletContext servletContext) throws TemplateException {
308 freemarker.template.Configuration configuration = new freemarker.template.Configuration();
309
310 configuration.setTemplateLoader(getTemplateLoader(servletContext));
311
312 configuration.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
313
314 configuration.setObjectWrapper(getObjectWrapper());
315
316 if( mruMaxStrongSize > 0 ) {
317 configuration.setSetting(freemarker.template.Configuration.CACHE_STORAGE_KEY, "strong:" + mruMaxStrongSize);
318 }
319
320 if (encoding != null) {
321 configuration.setDefaultEncoding(encoding);
322 }
323
324 loadSettings(servletContext, configuration);
325
326 return configuration;
327 }
328
329 /***
330 * Load the settings from the /freemarker.properties file on the classpath
331 *
332 * @see freemarker.template.Configuration#setSettings for the definition of valid settings
333 */
334 protected void loadSettings(ServletContext servletContext, freemarker.template.Configuration configuration) {
335 InputStream in = null;
336
337 try {
338 in = FileManager.loadFile("freemarker.properties", FreemarkerManager.class);
339
340 if (in != null) {
341 Properties p = new Properties();
342 p.load(in);
343 configuration.setSettings(p);
344 }
345 } catch (IOException e) {
346 LOG.error("Error while loading freemarker settings from /freemarker.properties", e);
347 } catch (TemplateException e) {
348 LOG.error("Error while loading freemarker settings from /freemarker.properties", e);
349 } finally {
350 if (in != null) {
351 try {
352 in.close();
353 } catch(IOException io) {
354 LOG.warn("Unable to close input stream", io);
355 }
356 }
357 }
358 }
359
360 public SimpleHash buildTemplateModel(ValueStack stack, Object action, ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ObjectWrapper wrapper) {
361 ScopesHashModel model = buildScopesHashModel(servletContext, request, response, wrapper, stack);
362 populateContext(model, stack, action, request, response);
363 if (tagLibraries != null) {
364 for (String prefix : tagLibraries.keySet()) {
365 model.put(prefix, tagLibraries.get(prefix).getFreemarkerModels(stack, request, response));
366 }
367 }
368 return model;
369 }
370 }