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