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