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 fr.paris.lutece.portal.service.template;
35
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.StringWriter;
39 import java.io.UnsupportedEncodingException;
40 import java.security.MessageDigest;
41 import java.security.NoSuchAlgorithmException;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.Map.Entry;
48
49 import fr.paris.lutece.util.html.HtmlTemplate;
50 import fr.paris.lutece.util.html.exception.LuteceFreemarkerException;
51 import freemarker.cache.FileTemplateLoader;
52 import freemarker.cache.MultiTemplateLoader;
53 import freemarker.cache.StringTemplateLoader;
54 import freemarker.cache.TemplateLoader;
55 import freemarker.template.Configuration;
56 import freemarker.template.Template;
57 import freemarker.template.TemplateException;
58 import freemarker.template.Version;
59
60
61
62
63
64
65 public abstract class AbstractFreeMarkerTemplateService implements IFreeMarkerTemplateService
66 {
67 private static final String NUMBER_FORMAT_PATTERN = "0.######";
68 private static final String SETTING_DATE_FORMAT = "date_format";
69 private static final String CONSTANT_HASH_ENCODING = "UTF-8";
70 private static final String CONSTANT_HASH_DIGEST = "MD5";
71
72
73
74 private List<String> _listPluginsMacros = new ArrayList<>( );
75 private Map<String, Object> _mapSharedVariables = new HashMap<>( );
76 private Map<String, Configuration> _mapConfigurations = new HashMap<>( );
77 private String _strDefaultPath;
78 private int _nTemplateUpdateDelay;
79 private boolean _bAcceptIncompatibleImprovements;
80
81
82
83
84
85 @Override
86 public void setTemplateUpdateDelay( int nTemplateUpdateDelay )
87 {
88 _nTemplateUpdateDelay = nTemplateUpdateDelay;
89 }
90
91
92
93
94 @Override
95 public void addPluginMacros( String strFileName )
96 {
97 _listPluginsMacros.add( strFileName );
98 }
99
100
101
102
103 @Override
104 public void setSharedVariable( String name, Object obj )
105 {
106 _mapSharedVariables.put( name, obj );
107 }
108
109
110
111
112 @Override
113 public void init( String strTemplatePath )
114 {
115 _strDefaultPath = strTemplatePath;
116 _bAcceptIncompatibleImprovements = false;
117 }
118
119
120
121
122 @Override
123 public void init( String strTemplatePath, boolean bAcceptIncompatibleImprovements )
124 {
125 _strDefaultPath = strTemplatePath;
126 _bAcceptIncompatibleImprovements = bAcceptIncompatibleImprovements;
127 }
128
129
130
131
132 @Override
133 public HtmlTemplate loadTemplate( String strPath, String strTemplate )
134 {
135 return loadTemplate( strPath, strTemplate, null, null );
136 }
137
138
139
140
141 @Override
142 public HtmlTemplate loadTemplate( String strPath, String strTemplate, Locale locale, Object rootMap )
143 {
144 Configuration cfg = _mapConfigurations.get( strPath );
145
146 if ( cfg == null )
147 {
148 cfg = initConfig( _strDefaultPath, Locale.getDefault( ) );
149 }
150
151 return processTemplate( cfg, strTemplate, rootMap, locale );
152 }
153
154
155
156
157
158 @Override
159 public HtmlTemplate loadTemplateFromStringFtl(String strTemplateData, Locale locale, Object rootMap) {
160 try {
161 String strContentKey = getHash(strTemplateData);
162 return loadTemplateFromStringFtl(strContentKey, strTemplateData, locale, rootMap, false);
163 }
164 catch (NoSuchAlgorithmException | UnsupportedEncodingException hashEx) {
165
166 throw new LuteceFreemarkerException(
167 "Can not create hash for template content " + strTemplateData + hashEx.getMessage(), hashEx);
168
169 }
170
171 }
172
173
174
175
176
177
178 @Override
179 public HtmlTemplate loadTemplateFromStringFtl(String strTemplateName,String strTemplateData, Locale locale, Object rootMap,boolean bResetCacheTemplate) {
180
181
182 Configuration cfg = _mapConfigurations.get(_strDefaultPath);
183
184 if (cfg == null) {
185 cfg = initConfig(_strDefaultPath, Locale.getDefault());
186 }
187
188 MultiTemplateLoader mtl = (MultiTemplateLoader) cfg.getTemplateLoader();
189 if ( bResetCacheTemplate || ((StringTemplateLoader) mtl.getTemplateLoader(1)).findTemplateSource(strTemplateName) == null) {
190 ((StringTemplateLoader) mtl.getTemplateLoader(1)).putTemplate(strTemplateName, strTemplateData);
191 }
192
193 return processTemplate(cfg, strTemplateName, rootMap, locale);
194
195
196 }
197
198
199
200
201
202 @Override
203 public void resetConfiguration( )
204 {
205 _mapConfigurations = new HashMap<>( );
206 }
207
208
209
210
211 @Override
212 public void resetCache( )
213 {
214 for ( Configuration cfg : _mapConfigurations.values( ) )
215 {
216 cfg.clearTemplateCache( );
217 }
218 }
219
220
221
222
223
224 public void initConfig( Locale locale )
225 {
226 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
227 if ( cfg == null )
228 {
229 initConfig( _strDefaultPath, locale );
230 }
231 }
232
233
234
235
236
237
238
239
240
241
242 private Configuration initConfig( String strPath, Locale locale )
243 {
244 try
245 {
246 Configuration cfg = buildConfiguration( locale );
247
248 File directory = new File( this.getAbsolutePathFromRelativePath( strPath ) );
249 FileTemplateLoader ftl1 = new FileTemplateLoader( directory );
250 StringTemplateLoader stringLoader = new StringTemplateLoader( );
251
252 TemplateLoader [ ] loaders = new TemplateLoader [ ] {
253 ftl1, stringLoader
254 };
255
256 MultiTemplateLoader mtl = new MultiTemplateLoader( loaders );
257 cfg.setTemplateLoader( mtl );
258
259
260 _mapConfigurations.put( strPath, cfg );
261 return cfg;
262 }
263 catch( IOException | TemplateException e )
264 {
265 throw new LuteceFreemarkerException( e.getMessage( ), e );
266 }
267
268 }
269
270
271
272
273
274
275
276
277
278
279 private Configuration buildConfiguration( Locale locale ) throws TemplateException
280 {
281 Version version = ( _bAcceptIncompatibleImprovements ) ? Configuration.VERSION_2_3_28 : Configuration.VERSION_2_3_0;
282 Configuration cfg = new Configuration( version );
283
284
285 for ( String strFileName : _listPluginsMacros )
286 {
287 cfg.addAutoInclude( strFileName );
288 }
289
290 for ( Entry<String, Object> entry : _mapSharedVariables.entrySet( ) )
291 {
292 cfg.setSharedVariable( entry.getKey( ), entry.getValue( ) );
293 }
294
295
296 cfg.setLocalizedLookup( false );
297
298
299 cfg.setNumberFormat( NUMBER_FORMAT_PATTERN );
300
301
302 cfg.setSetting( SETTING_DATE_FORMAT, this.getDefaultPattern( locale ) );
303
304
305 cfg.setTemplateUpdateDelayMilliseconds( ( ( long ) _nTemplateUpdateDelay ) * 1000L );
306 return cfg;
307 }
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 private HtmlTemplate processTemplate( Configuration cfg, String strTemplate, Object rootMap, Locale locale )
323 {
324 HtmlTemplate template = null;
325
326 try
327 {
328 Template ftl;
329
330 if ( locale == null )
331 {
332 ftl = cfg.getTemplate( strTemplate );
333 }
334 else
335 {
336 ftl = cfg.getTemplate( strTemplate, locale );
337 }
338
339 StringWriter writer = new StringWriter( 1024 );
340
341 ftl.setDateFormat( this.getDefaultPattern( locale ) );
342
343 ftl.process( rootMap, writer );
344 template = new HtmlTemplate( writer.toString( ) );
345 }
346 catch( IOException | TemplateException e )
347 {
348 throw new LuteceFreemarkerException( e.getMessage( ), e );
349 }
350
351 return template;
352 }
353
354
355
356
357 @Override
358 public List<String> getAutoIncludes( )
359 {
360 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
361 if ( cfg == null )
362 {
363 cfg = initConfig( _strDefaultPath, Locale.getDefault( ) );
364 }
365 return cfg.getAutoIncludes( );
366 }
367
368
369
370
371 @Override
372 public void addAutoInclude( String strFile )
373 {
374 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
375 if ( cfg != null )
376 {
377 cfg.addAutoInclude( strFile );
378 }
379 }
380
381
382
383
384 @Override
385 public void removeAutoInclude( String strFile )
386 {
387 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
388 if ( cfg != null )
389 {
390 cfg.removeAutoInclude( strFile );
391 }
392 }
393
394
395
396
397
398
399
400
401
402
403
404
405
406 private static String getHash( String message ) throws UnsupportedEncodingException, NoSuchAlgorithmException
407 {
408
409 byte [ ] byteChaine;
410 byteChaine = message.getBytes( CONSTANT_HASH_ENCODING );
411 MessageDigest md = MessageDigest.getInstance( CONSTANT_HASH_DIGEST );
412 byte [ ] hash = md.digest( byteChaine );
413
414
415 StringBuilder sb = new StringBuilder( 2 * hash.length );
416 for ( byte b : hash )
417 {
418 sb.append( String.format( "%02x", b & 0xff ) );
419 }
420
421 return sb.toString( );
422
423
424
425 }
426
427
428
429 }