1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts.tiles.xmlDefinition;
23
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.nio.file.InvalidPathException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.StringTokenizer;
37
38 import jakarta.servlet.ServletContext;
39 import jakarta.servlet.ServletRequest;
40 import jakarta.servlet.http.HttpServletRequest;
41 import jakarta.servlet.http.HttpSession;
42
43 import org.apache.struts.tiles.DefinitionsFactoryException;
44 import org.apache.struts.tiles.FactoryNotFoundException;
45 import org.apache.struts.tiles.taglib.ComponentConstants;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.xml.sax.SAXException;
49
50
51
52
53
54
55
56
57
58
59
60
61
62 public class I18nFactorySet extends FactorySet {
63 private static final long serialVersionUID = 3883838354881166525L;
64
65
66
67
68 private transient final Logger log =
69 LoggerFactory.getLogger(I18nFactorySet.class);
70
71
72
73
74 public static final String DEFINITIONS_CONFIG_PARAMETER_NAME =
75 "definitions-config";
76
77
78
79
80 public static final String PARSER_DETAILS_PARAMETER_NAME =
81 "definitions-parser-details";
82
83
84
85
86 public static final String PARSER_VALIDATE_PARAMETER_NAME =
87 "definitions-parser-validate";
88
89
90
91
92 public static final String DEFAULT_DEFINITION_FILENAMES[] =
93 {
94 "/WEB-INF/tileDefinitions.xml",
95 "/WEB-INF/componentDefinitions.xml",
96 "/WEB-INF/instanceDefinitions.xml" };
97
98
99
100
101 public static final String FILENAME_EXTENSION = ".xml";
102
103
104
105
106 protected DefinitionsFactory defaultFactory = null;
107
108
109
110
111
112
113 protected transient XmlParser xmlParser;
114
115
116
117
118
119 protected boolean isValidatingParser = false;
120
121
122
123
124
125 protected int parserDetailLevel = 0;
126
127
128
129
130 private ArrayList<String> filenames = null;
131
132
133
134
135 private HashMap<String, DefinitionsFactory> loaded = null;
136
137
138
139
140
141 public I18nFactorySet() {
142 super();
143 }
144
145
146
147
148
149
150
151
152 public I18nFactorySet(ServletContext servletContext, Map<String, Object> properties)
153 throws DefinitionsFactoryException {
154
155 initFactory(servletContext, properties);
156 }
157
158
159
160
161
162
163
164
165
166
167
168 public void initFactory(ServletContext servletContext, Map<String, Object> properties)
169 throws DefinitionsFactoryException {
170
171
172 String value = (String) properties.get(PARSER_VALIDATE_PARAMETER_NAME);
173 if (value != null) {
174 isValidatingParser = Boolean.valueOf(value).booleanValue();
175 }
176
177 value = (String) properties.get(PARSER_DETAILS_PARAMETER_NAME);
178 if (value != null) {
179 try {
180 parserDetailLevel = Integer.valueOf(value).intValue();
181
182 } catch (NumberFormatException ex) {
183 log.error("Bad format for parameter '{}'. Integer expected.",
184 PARSER_DETAILS_PARAMETER_NAME);
185 }
186 }
187
188
189
190
191 String filename = (String) properties.get(DEFINITIONS_CONFIG_PARAMETER_NAME);
192 if (filename != null) {
193 try {
194 initFactory(servletContext, filename);
195 log.debug("Factory initialized from file '{}'.", filename);
196
197 } catch (FileNotFoundException ex) {
198 log.error("{} : Can't find file '{}'", ex.getMessage(), filename);
199 throw new FactoryNotFoundException(
200 ex.getMessage() + " : Can't find file '" + filename + "'", ex);
201 }
202
203 } else {
204 for (int i = 0; i < DEFAULT_DEFINITION_FILENAMES.length; i++) {
205 filename = DEFAULT_DEFINITION_FILENAMES[i];
206 try {
207 initFactory(servletContext, filename);
208 log.info("Factory initialized from file '{}'.", filename);
209 } catch (FileNotFoundException ex) {
210
211 }
212 }
213 }
214
215 }
216
217
218
219
220
221
222
223
224
225
226 protected void initFactory(
227 ServletContext servletContext,
228 String proposedFilename)
229 throws DefinitionsFactoryException, FileNotFoundException {
230
231
232 StringTokenizer tokenizer = new StringTokenizer(proposedFilename, ",");
233 this.filenames = new ArrayList<>(tokenizer.countTokens());
234 while (tokenizer.hasMoreTokens()) {
235 final String fn = tokenizer.nextToken().trim();
236 if (fn.isEmpty()) {
237 log.warn("Factory initialization - ignore empty file");
238 continue;
239 } else if (fn.contains("\u0000")) {
240 log.warn("Factory initialization - ignore file with nul-characters '{}'", fn.replace('\u0000', '_') );
241 continue;
242 }
243
244 try {
245 final Path p = Paths.get(fn).normalize();
246 if (p.toString().charAt(0) != '.') {
247 this.filenames.add(p.toString());
248 } else {
249 log.warn("Factory initialization - path not normalized '{}'", p.toString());
250 }
251 } catch (InvalidPathException e) {
252 log.warn("Factory initialization - illegal path '{}'", e.getInput(), e);
253 }
254 }
255
256 loaded = new HashMap<>();
257 defaultFactory = createDefaultFactory(servletContext);
258 log.debug("default factory: {}", defaultFactory);
259 }
260
261
262
263
264
265 protected DefinitionsFactory getDefaultFactory() {
266 return defaultFactory;
267 }
268
269
270
271
272
273
274
275
276
277
278 protected DefinitionsFactory createDefaultFactory(ServletContext servletContext)
279 throws DefinitionsFactoryException, FileNotFoundException {
280
281 XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
282 if (rootXmlConfig == null) {
283 throw new FileNotFoundException();
284 }
285
286 rootXmlConfig.resolveInheritances();
287
288 log.debug(rootXmlConfig.toString());
289
290 DefinitionsFactory factory = new DefinitionsFactory(rootXmlConfig);
291 log.debug("factory loaded : {}", factory);
292
293 return factory;
294 }
295
296
297
298
299
300
301
302
303 protected Object getDefinitionsFactoryKey(
304 String name,
305 ServletRequest request,
306 ServletContext servletContext) {
307
308 Locale locale = null;
309 try {
310 HttpSession session = ((HttpServletRequest) request).getSession(false);
311 if (session != null) {
312 locale = (Locale) session.getAttribute(ComponentConstants.LOCALE_KEY);
313 }
314
315 } catch (ClassCastException ex) {
316 log.error("I18nFactorySet.getDefinitionsFactoryKey");
317 ex.printStackTrace();
318 }
319
320 return locale;
321 }
322
323
324
325
326
327
328
329
330
331
332 protected DefinitionsFactory createFactory(
333 Object key,
334 ServletRequest request,
335 ServletContext servletContext)
336 throws DefinitionsFactoryException {
337
338 if (key == null) {
339 return getDefaultFactory();
340 }
341
342
343 List<String> possiblePostfixes = calculateSuffixes((Locale) key);
344
345
346
347
348 XmlDefinitionsSet lastXmlFile = null;
349 DefinitionsFactory factory = null;
350 String curPostfix = null;
351 int i = 0;
352
353 for (i = possiblePostfixes.size() - 1; i >= 0; i--) {
354 curPostfix = possiblePostfixes.get(i);
355
356
357 factory = loaded.get(curPostfix);
358 if (factory != null) {
359 return factory;
360 }
361
362
363 lastXmlFile = parseXmlFiles(servletContext, curPostfix, null);
364 if (lastXmlFile != null) {
365 break;
366 }
367 }
368
369
370
371 if (lastXmlFile == null) {
372 return getDefaultFactory();
373 }
374
375
376 String lastPostfix = curPostfix;
377 XmlDefinitionsSet rootXmlConfig = parseXmlFiles(servletContext, "", null);
378 for (int j = 0; j < i; j++) {
379 curPostfix = possiblePostfixes.get(j);
380 parseXmlFiles(servletContext, curPostfix, rootXmlConfig);
381 }
382
383 rootXmlConfig.extend(lastXmlFile);
384 rootXmlConfig.resolveInheritances();
385
386 factory = new DefinitionsFactory(rootXmlConfig);
387 loaded.put(lastPostfix, factory);
388
389 log.debug("factory loaded : {}", factory);
390
391
392 return factory;
393 }
394
395
396
397
398
399 private List<String> calculateSuffixes(Locale locale) {
400
401 List<String> suffixes = new ArrayList<>(3);
402 String language = locale.getLanguage();
403 String country = locale.getCountry();
404 String variant = locale.getVariant();
405
406 StringBuilder suffix = new StringBuilder();
407 suffix.append('_');
408 suffix.append(language);
409 if (language.length() > 0) {
410 suffixes.add(suffix.toString());
411 }
412
413 suffix.append('_');
414 suffix.append(country);
415 if (country.length() > 0) {
416 suffixes.add(suffix.toString());
417 }
418
419 suffix.append('_');
420 suffix.append(variant);
421 if (variant.length() > 0) {
422 suffixes.add(suffix.toString());
423 }
424
425 return suffixes;
426
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444 protected XmlDefinitionsSet parseXmlFiles(
445 ServletContext servletContext,
446 String postfix,
447 XmlDefinitionsSet xmlDefinitions)
448 throws DefinitionsFactoryException {
449
450 if (postfix != null && postfix.length() == 0) {
451 postfix = null;
452 }
453
454
455 for (String filename : filenames) {
456 String fn = concatPostfix(filename, postfix);
457 xmlDefinitions = parseXmlFile(servletContext, fn, xmlDefinitions);
458 }
459
460 return xmlDefinitions;
461 }
462
463
464
465
466
467
468
469
470
471
472
473
474
475 protected XmlDefinitionsSet parseXmlFile(
476 ServletContext servletContext,
477 String filename,
478 XmlDefinitionsSet xmlDefinitions)
479 throws DefinitionsFactoryException {
480
481 InputStream input = null;
482 try {
483 input = servletContext.getResourceAsStream(filename);
484
485
486
487 if (null == input) {
488 try {
489 final String realPath = servletContext.getRealPath(filename);
490 if (realPath != null) {
491 input = new FileInputStream(realPath);
492 }
493 } catch (Exception e) {
494 }
495 }
496
497
498
499 if (input == null) {
500 input = getClass().getResourceAsStream(filename);
501 }
502
503
504 if (input == null) {
505 log.debug("Can't open file '{}'", filename);
506 return xmlDefinitions;
507 }
508
509
510
511
512 if (true) {
513 xmlParser = new XmlParser();
514 xmlParser.setValidating(isValidatingParser);
515 }
516
517
518 if (xmlDefinitions == null) {
519 xmlDefinitions = new XmlDefinitionsSet();
520 }
521
522 xmlParser.parse(input, xmlDefinitions);
523
524 } catch (SAXException ex) {
525 if (log.isDebugEnabled()) {
526 log.debug("Error while parsing file '{}'.", filename);
527 ex.printStackTrace();
528 }
529 throw new DefinitionsFactoryException(
530 "Error while parsing file '" + filename + "'. " + ex.getMessage(),
531 ex);
532
533 } catch (IOException ex) {
534 throw new DefinitionsFactoryException(
535 "IO Error while parsing file '" + filename + "'. " + ex.getMessage(),
536 ex);
537 } finally {
538 if (input != null) {
539 try {
540 input.close();
541 } catch (IOException ioe) {
542 log.warn("Error closing input stream", ioe);
543 }
544 }
545 }
546
547 return xmlDefinitions;
548 }
549
550
551
552
553
554
555
556
557
558 private String concatPostfix(String name, String postfix) {
559 if (postfix == null) {
560 return name;
561 }
562
563
564
565 int dotIndex = name.lastIndexOf(".");
566 int lastNameStart = name.lastIndexOf(java.io.File.pathSeparator);
567 if (dotIndex < 1 || dotIndex < lastNameStart) {
568 return name + postfix;
569 }
570
571 String ext = name.substring(dotIndex);
572 name = name.substring(0, dotIndex);
573 return name + postfix + ext;
574 }
575
576
577
578
579
580 public String toString() {
581 StringBuilder buff = new StringBuilder("I18nFactorySet : \n");
582 buff.append("--- default factory ---\n");
583 buff.append(defaultFactory.toString());
584 buff.append("\n--- other factories ---\n");
585 for (Object factory : factories.values()) {
586 buff.append(factory.toString()).append("---------- \n");
587 }
588 return buff.toString();
589 }
590 }