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> _listPluginsAutoIncludes = new ArrayList<>( );
75 private Map<String, String> _mapPluginsAutoImports = new HashMap<>( );
76 private Map<String, Object> _mapSharedVariables = new HashMap<>( );
77 private Map<String, Configuration> _mapConfigurations = new HashMap<>( );
78 private String _strDefaultPath;
79 private int _nTemplateUpdateDelay;
80 private boolean _bAcceptIncompatibleImprovements;
81
82
83
84
85
86 @Override
87 public void setTemplateUpdateDelay( int nTemplateUpdateDelay )
88 {
89 _nTemplateUpdateDelay = nTemplateUpdateDelay;
90 }
91
92
93
94
95 @Override
96 public void addPluginMacros( String strFileName )
97 {
98 _listPluginsAutoIncludes.add( strFileName );
99 }
100
101
102
103
104 @Override
105 public void addPluginAutoInclude( String strFileName )
106 {
107 _listPluginsAutoIncludes.add( strFileName );
108 }
109
110
111
112
113 @Override
114 public void addPluginAutoImport( String strNamespace, String strFileName )
115 {
116 _mapPluginsAutoImports.put( strNamespace, strFileName );
117 }
118
119
120
121
122 @Override
123 public void setSharedVariable( String name, Object obj )
124 {
125 _mapSharedVariables.put( name, obj );
126 }
127
128
129
130
131 @Override
132 public void init( String strTemplatePath )
133 {
134 _strDefaultPath = strTemplatePath;
135 _bAcceptIncompatibleImprovements = false;
136 }
137
138
139
140
141 @Override
142 public void init( String strTemplatePath, boolean bAcceptIncompatibleImprovements )
143 {
144 _strDefaultPath = strTemplatePath;
145 _bAcceptIncompatibleImprovements = bAcceptIncompatibleImprovements;
146 }
147
148
149
150
151 @Override
152 public HtmlTemplate loadTemplate( String strPath, String strTemplate )
153 {
154 return loadTemplate( strPath, strTemplate, null, null );
155 }
156
157
158
159
160 @Override
161 public HtmlTemplate loadTemplate( String strPath, String strTemplate, Locale locale, Object rootMap )
162 {
163 Configuration cfg = _mapConfigurations.get( strPath );
164
165 if ( cfg == null )
166 {
167 cfg = initConfig( _strDefaultPath, Locale.getDefault( ) );
168 }
169
170 return processTemplate( cfg, strTemplate, rootMap, locale );
171 }
172
173
174
175
176
177 @Override
178 public HtmlTemplate loadTemplateFromStringFtl(String strTemplateData, Locale locale, Object rootMap) {
179 try {
180 String strContentKey = getHash(strTemplateData);
181 return loadTemplateFromStringFtl(strContentKey, strTemplateData, locale, rootMap, false);
182 }
183 catch (NoSuchAlgorithmException | UnsupportedEncodingException hashEx) {
184
185 throw new LuteceFreemarkerException(
186 "Can not create hash for template content " + strTemplateData + hashEx.getMessage(), hashEx);
187
188 }
189
190 }
191
192
193
194
195
196
197 @Override
198 public HtmlTemplate loadTemplateFromStringFtl(String strTemplateName,String strTemplateData, Locale locale, Object rootMap,boolean bResetCacheTemplate) {
199
200
201 Configuration cfg = _mapConfigurations.get(_strDefaultPath);
202
203 if (cfg == null) {
204 cfg = initConfig(_strDefaultPath, Locale.getDefault());
205 }
206
207 MultiTemplateLoader mtl = (MultiTemplateLoader) cfg.getTemplateLoader();
208 if ( bResetCacheTemplate || ((StringTemplateLoader) mtl.getTemplateLoader(1)).findTemplateSource(strTemplateName) == null) {
209 ((StringTemplateLoader) mtl.getTemplateLoader(1)).putTemplate(strTemplateName, strTemplateData);
210 }
211
212 return processTemplate(cfg, strTemplateName, rootMap, locale);
213
214
215 }
216
217
218
219
220
221 @Override
222 public void resetConfiguration( )
223 {
224 _mapConfigurations = new HashMap<>( );
225 }
226
227
228
229
230 @Override
231 public void resetCache( )
232 {
233 for ( Configuration cfg : _mapConfigurations.values( ) )
234 {
235 cfg.clearTemplateCache( );
236 }
237 }
238
239
240
241
242
243 public void initConfig( Locale locale )
244 {
245 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
246 if ( cfg == null )
247 {
248 initConfig( _strDefaultPath, locale );
249 }
250 }
251
252
253
254
255
256
257
258
259
260
261 private Configuration initConfig( String strPath, Locale locale )
262 {
263 try
264 {
265 Configuration cfg = buildConfiguration( locale );
266
267 File directory = new File( this.getAbsolutePathFromRelativePath( strPath ) );
268 FileTemplateLoader ftl1 = new FileTemplateLoader( directory );
269 StringTemplateLoader stringLoader = new StringTemplateLoader( );
270
271 TemplateLoader [ ] loaders = new TemplateLoader [ ] {
272 ftl1, stringLoader
273 };
274
275 MultiTemplateLoader mtl = new MultiTemplateLoader( loaders );
276 cfg.setTemplateLoader( mtl );
277
278
279 _mapConfigurations.put( strPath, cfg );
280 return cfg;
281 }
282 catch( IOException | TemplateException e )
283 {
284 throw new LuteceFreemarkerException( e.getMessage( ), e );
285 }
286
287 }
288
289
290
291
292
293
294
295
296
297
298 private Configuration buildConfiguration( Locale locale ) throws TemplateException
299 {
300 Version version = ( _bAcceptIncompatibleImprovements ) ? Configuration.VERSION_2_3_28 : Configuration.VERSION_2_3_0;
301 Configuration cfg = new Configuration( version );
302
303
304 for ( String strFileName : _listPluginsAutoIncludes )
305 {
306 cfg.addAutoInclude( strFileName );
307 }
308
309
310 for ( Map.Entry<String, String> importEntry : _mapPluginsAutoImports.entrySet( ) )
311 {
312 cfg.addAutoImport( importEntry.getKey( ), importEntry.getValue( ) );
313 }
314
315 for ( Entry<String, Object> entry : _mapSharedVariables.entrySet( ) )
316 {
317 cfg.setSharedVariable( entry.getKey( ), entry.getValue( ) );
318 }
319
320
321 cfg.setLazyAutoImports( true );
322
323
324 cfg.setLocalizedLookup( false );
325
326
327 cfg.setNumberFormat( NUMBER_FORMAT_PATTERN );
328
329
330 cfg.setSetting( SETTING_DATE_FORMAT, this.getDefaultPattern( locale ) );
331
332
333 cfg.setTemplateUpdateDelayMilliseconds( ( ( long ) _nTemplateUpdateDelay ) * 1000L );
334 return cfg;
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350 private HtmlTemplate processTemplate( Configuration cfg, String strTemplate, Object rootMap, Locale locale )
351 {
352 HtmlTemplate template = null;
353
354 try
355 {
356 Template ftl;
357
358 if ( locale == null )
359 {
360 ftl = cfg.getTemplate( strTemplate );
361 }
362 else
363 {
364 ftl = cfg.getTemplate( strTemplate, locale );
365 }
366
367 StringWriter writer = new StringWriter( 1024 );
368
369 ftl.setDateFormat( this.getDefaultPattern( locale ) );
370
371 ftl.process( rootMap, writer );
372 template = new HtmlTemplate( writer.toString( ) );
373 }
374 catch( IOException | TemplateException e )
375 {
376 throw new LuteceFreemarkerException( e.getMessage( ), e );
377 }
378
379 return template;
380 }
381
382
383
384
385 @Override
386 public List<String> getAutoIncludes( )
387 {
388 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
389 if ( cfg == null )
390 {
391 cfg = initConfig( _strDefaultPath, Locale.getDefault( ) );
392 }
393 return cfg.getAutoIncludes( );
394 }
395
396
397
398
399 @Override
400 public void addAutoInclude( String strFile )
401 {
402 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
403 if ( cfg != null )
404 {
405 cfg.addAutoInclude( strFile );
406 }
407 }
408
409
410
411
412 @Override
413 public void removeAutoInclude( String strFile )
414 {
415 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
416 if ( cfg != null )
417 {
418 cfg.removeAutoInclude( strFile );
419 }
420 }
421
422
423
424
425 @Override
426 public Map<String,String> getAutoImports( )
427 {
428 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
429 if ( cfg == null )
430 {
431 cfg = initConfig( _strDefaultPath, Locale.getDefault( ) );
432 }
433 return cfg.getAutoImports( );
434 }
435
436
437
438
439 @Override
440 public void addAutoImport( String strNamespace, String strFile )
441 {
442 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
443 if ( cfg != null )
444 {
445 cfg.addAutoImport( strNamespace, strFile );
446 }
447 }
448
449
450
451
452 @Override
453 public void removeAutoImport( String strNamespace )
454 {
455 Configuration cfg = _mapConfigurations.get( _strDefaultPath );
456 if ( cfg != null )
457 {
458 cfg.removeAutoImport( strNamespace );
459 }
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473
474 private static String getHash( String message ) throws UnsupportedEncodingException, NoSuchAlgorithmException
475 {
476
477 byte [ ] byteChaine;
478 byteChaine = message.getBytes( CONSTANT_HASH_ENCODING );
479 MessageDigest md = MessageDigest.getInstance( CONSTANT_HASH_DIGEST );
480 byte [ ] hash = md.digest( byteChaine );
481
482
483 StringBuilder sb = new StringBuilder( 2 * hash.length );
484 for ( byte b : hash )
485 {
486 sb.append( String.format( "%02x", b & 0xff ) );
487 }
488
489 return sb.toString( );
490
491
492
493 }
494
495
496
497 }