1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.freemarker;
35
36 import info.magnolia.cms.beans.config.ServerConfiguration;
37 import info.magnolia.cms.core.HTMLEscapingAggregationState;
38 import info.magnolia.context.MgnlContext;
39 import info.magnolia.context.WebContext;
40 import info.magnolia.context.HTMLEscapingWebContextWrapper;
41 import info.magnolia.i18nsystem.TranslationService;
42 import info.magnolia.init.MagnoliaConfigurationProperties;
43 import info.magnolia.objectfactory.Components;
44
45 import java.io.IOException;
46 import java.io.Reader;
47 import java.io.Writer;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.Set;
51
52 import javax.inject.Inject;
53 import javax.inject.Singleton;
54 import javax.servlet.GenericServlet;
55 import javax.servlet.ServletContext;
56 import javax.servlet.ServletException;
57
58 import freemarker.cache.TemplateLoader;
59 import freemarker.ext.jsp.TaglibFactory;
60 import freemarker.ext.servlet.FreemarkerServlet;
61 import freemarker.ext.servlet.HttpRequestHashModel;
62 import freemarker.ext.servlet.ServletContextHashModel;
63 import freemarker.template.Configuration;
64 import freemarker.template.ObjectWrapper;
65 import freemarker.template.Template;
66 import freemarker.template.TemplateException;
67 import freemarker.template.TemplateModel;
68 import freemarker.template.TemplateModelException;
69
70
71
72
73
74
75 @Singleton
76 public class FreemarkerHelper {
77
78
79
80
81 @Deprecated
82 public static FreemarkerHelper getInstance() {
83 return Components.getSingleton(FreemarkerHelper.class);
84 }
85
86 private final Configuration cfg;
87 private final TranslationService translationService;
88
89
90 private TaglibFactory taglibFactory;
91 private ServletContextHashModel servletContextHashModel;
92
93 private boolean escapeHtml = false;
94
95 @Inject
96 public FreemarkerHelper(final FreemarkerConfig freemarkerConfig, TranslationService translationService, MagnoliaConfigurationProperties properties) {
97 boolean devMode = properties.getBooleanProperty("magnolia.develop");
98 this.translationService = translationService;
99
100
101 this.cfg = new Configuration() {
102 @Override
103 public Set getSharedVariableNames() {
104 final Set names = super.getSharedVariableNames();
105 names.addAll(freemarkerConfig.getSharedVariables().keySet());
106 return names;
107 }
108
109 @Override
110 public TemplateModel getSharedVariable(String name) {
111 final TemplateModel value = super.getSharedVariable(name);
112 if (value == null) {
113 return freemarkerConfig.getSharedVariables().get(name);
114 }
115 return value;
116 }
117 };
118 cfg.setTemplateExceptionHandler(freemarkerConfig.getTemplateExceptionHandler());
119
120
121
122
123 cfg.setTemplateLoader(new ConfigDelegatingTemplateLoader(freemarkerConfig));
124 cfg.setObjectWrapper(new ConfigDelegatingObjectWrapper(freemarkerConfig));
125
126 cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
127 cfg.setDefaultEncoding("UTF-8");
128 cfg.setURLEscapingCharset("UTF-8");
129 if (devMode) {
130 cfg.setTemplateUpdateDelay(0);
131 }
132 }
133
134
135
136
137 @Deprecated
138 public FreemarkerHelper(final FreemarkerConfig freemarkerConfig, TranslationService transactionService) {
139 this(freemarkerConfig, transactionService, Components.getComponent(MagnoliaConfigurationProperties.class));
140 }
141
142
143
144
145 @Deprecated
146 public FreemarkerHelper(final FreemarkerConfig freemarkerConfig) {
147 this(freemarkerConfig, Components.getComponent(TranslationService.class), Components.getComponent(MagnoliaConfigurationProperties.class));
148 }
149
150
151
152
153 public void render(String templatePath, Object root, Writer out) throws TemplateException, IOException {
154 render(templatePath, null, null, root, out);
155 }
156
157
158
159
160
161
162
163
164
165
166
167 public void render(String templatePath, Locale locale, String i18nBasename, Object root, Writer out) throws TemplateException, IOException {
168 final Locale localeToUse = checkLocale(locale);
169 prepareRendering(localeToUse, i18nBasename, root);
170
171 final Template template = cfg.getTemplate(templatePath, localeToUse);
172 template.process(root, out);
173 }
174
175
176
177
178
179
180
181 public void render(Reader template, Object root, Writer out) throws TemplateException, IOException {
182 render(template, null, null, root, out);
183 }
184
185 protected void render(Reader template, Locale locale, String i18nBasename, Object root, Writer out) throws TemplateException, IOException {
186 final Locale localeToUse = checkLocale(locale);
187 prepareRendering(localeToUse, i18nBasename, root);
188
189 final Template t = new Template("inlinetemplate", template, cfg);
190 t.setLocale(localeToUse);
191 t.process(root, out);
192 }
193
194
195
196
197 protected Locale checkLocale(Locale locale) {
198 if (locale != null) {
199 return locale;
200 } else if (MgnlContext.hasInstance()) {
201 return MgnlContext.getLocale();
202 } else {
203 return Locale.getDefault();
204 }
205 }
206
207
208
209
210 protected void prepareRendering(Locale checkedLocale, String i18nBasename, Object root) {
211 if (root instanceof Map) {
212 final Map<String, Object> data = (Map<String, Object>) root;
213 addDefaultData(data, checkedLocale, i18nBasename);
214 }
215 }
216
217 protected void addDefaultData(Map<String, Object> data, Locale locale, String i18nBasename) {
218 if (MgnlContext.hasInstance()) {
219 data.put("ctx", MgnlContext.getInstance());
220 }
221 if (MgnlContext.isWebContext()) {
222 final WebContext webCtx = MgnlContext.getWebContext();
223
224 if (isEscapeHtml()) {
225 data.put("ctx", new HTMLEscapingWebContextWrapper(webCtx));
226 }
227
228 data.put("contextPath", webCtx.getContextPath());
229 data.put("aggregationState", isEscapeHtml() ?
230 Components.getComponent(HTMLEscapingAggregationState.class) : webCtx.getAggregationState());
231
232 addTaglibSupportData(data, webCtx);
233 }
234
235 data.put("defaultBaseUrl", ServerConfiguration.getInstance().getDefaultBaseUrl());
236
237 data.put("i18n", new MessagesWrapper(i18nBasename, locale, translationService));
238 data.put("i18nAuthoring", new MessagesWrapper(i18nBasename, MgnlContext.getLocale(), translationService));
239
240
241
242
243
244
245
246 }
247
248 protected void addTaglibSupportData(Map<String, Object> data, WebContext webCtx) {
249 final ServletContext servletContext = webCtx.getServletContext();
250 try {
251 data.put(FreemarkerServlet.KEY_JSP_TAGLIBS, checkTaglibFactory(servletContext));
252 data.put(FreemarkerServlet.KEY_APPLICATION_PRIVATE, checkServletContextModel(servletContext));
253 data.put(FreemarkerServlet.KEY_REQUEST_PRIVATE, new HttpRequestHashModel(webCtx.getRequest(), webCtx.getResponse(), cfg.getObjectWrapper()));
254 } catch (ServletException e) {
255
256 throw new RuntimeException("Can't initialize taglib support for FreeMarker: ", e);
257 }
258 }
259
260 protected TaglibFactory checkTaglibFactory(ServletContext servletContext) {
261 if (taglibFactory == null) {
262 taglibFactory = new TaglibFactory(new FreemarkerServletContextWrapper(servletContext));
263 }
264 return taglibFactory;
265 }
266
267 protected ServletContextHashModel checkServletContextModel(ServletContext servletContext) throws ServletException {
268 if (servletContextHashModel == null) {
269
270 final GenericServlet fs = new DoNothingServlet(servletContext);
271 servletContextHashModel = new ServletContextHashModel(fs, cfg.getObjectWrapper());
272 }
273 return servletContextHashModel;
274 }
275
276 protected Configuration getConfiguration() {
277 return cfg;
278 }
279
280 public void setEscapeHtml(boolean escapeHtml) {
281 this.escapeHtml = escapeHtml;
282 }
283
284 public boolean isEscapeHtml() {
285 return escapeHtml;
286 }
287
288 private static class ConfigDelegatingTemplateLoader implements TemplateLoader {
289 private final FreemarkerConfig freemarkerConfig;
290
291 public ConfigDelegatingTemplateLoader(FreemarkerConfig freemarkerConfig) {
292 this.freemarkerConfig = freemarkerConfig;
293 }
294
295 @Override
296 public Object findTemplateSource(String name) throws IOException {
297 return freemarkerConfig.getTemplateLoader().findTemplateSource(name);
298 }
299
300 @Override
301 public long getLastModified(Object templateSource) {
302 return freemarkerConfig.getTemplateLoader().getLastModified(templateSource);
303 }
304
305 @Override
306 public Reader getReader(Object templateSource, String encoding) throws IOException {
307 return freemarkerConfig.getTemplateLoader().getReader(templateSource, encoding);
308 }
309
310 @Override
311 public void closeTemplateSource(Object templateSource) throws IOException {
312 freemarkerConfig.getTemplateLoader().closeTemplateSource(templateSource);
313 }
314 }
315
316 private static class ConfigDelegatingObjectWrapper implements ObjectWrapper {
317 private final FreemarkerConfig freemarkerConfig;
318
319 public ConfigDelegatingObjectWrapper(FreemarkerConfig freemarkerConfig) {
320 this.freemarkerConfig = freemarkerConfig;
321 }
322
323 @Override
324 public TemplateModel wrap(Object obj) throws TemplateModelException {
325 return freemarkerConfig.getObjectWrapper().wrap(obj);
326 }
327 }
328 }