View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.config;
21  
22  import org.apache.myfaces.tobago.context.ThemeImpl;
23  import org.apache.myfaces.tobago.context.ThemeScript;
24  import org.apache.myfaces.tobago.context.ThemeStyle;
25  import org.apache.myfaces.tobago.internal.util.IoUtils;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  import org.xml.sax.Attributes;
29  import org.xml.sax.SAXException;
30  import org.xml.sax.SAXParseException;
31  
32  import javax.xml.XMLConstants;
33  import javax.xml.parsers.ParserConfigurationException;
34  import javax.xml.parsers.SAXParser;
35  import javax.xml.parsers.SAXParserFactory;
36  import javax.xml.transform.Source;
37  import javax.xml.transform.stream.StreamSource;
38  import javax.xml.validation.Schema;
39  import javax.xml.validation.SchemaFactory;
40  import javax.xml.validation.Validator;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.net.URISyntaxException;
44  import java.net.URL;
45  import java.util.Properties;
46  import java.util.Stack;
47  
48  public class TobagoConfigParser extends TobagoConfigEntityResolver {
49  
50    private static final Logger LOG = LoggerFactory.getLogger(TobagoConfigParser.class);
51  
52    private static final int TOBAGO_CONFIG = -1498874611;
53    private static final int NAME = 3373707;
54    private static final int ORDERING = 1234314708;
55    private static final int BEFORE = -1392885889;
56    private static final int AFTER = 92734940;
57    private static final int THEME_CONFIG = 1930630086;
58    private static final int DEFAULT_THEME = -114431171;
59    private static final int SUPPORTED_THEME = -822303766;
60    private static final int CREATE_SESSION_SECRET = 413906616;
61    private static final int CHECK_SESSION_SECRET = 275994924;
62    private static final int PREVENT_FRAME_ATTACKS = 270456726;
63    private static final int SET_NOSNIFF_HEADER = -1238451304;
64    private static final int CONTENT_SECURITY_POLICY = 1207440139;
65    private static final int CHECK_SECURITY_ANNOTATIONS = -1870701636;
66    private static final int DIRECTIVE = -962590641;
67    private static final int RENDERERS = 1839650832;
68    private static final int RENDERER = -494845757;
69    private static final int SUPPORTED_MARKUP = 71904295;
70    private static final int MARKUP = -1081305560;
71    private static final int THEME_DEFINITIONS = -255617156;
72    private static final int THEME_DEFINITION = 1515774935;
73    private static final int DISPLAY_NAME = 1568910518;
74    private static final int FALLBACK = 761243362;
75    private static final int VERSIONED = -1407102089;
76    private static final int RESOURCES = -1983070683;
77    private static final int EXCLUDES = 1994055129;
78    private static final int SANITIZER = 1807639849;
79    private static final int SANITIZER_CLASS = -974266412;
80    private static final int SCRIPT = -907685685;
81    private static final int STYLE = 109780401;
82    private static final int PROPERTIES = -926053069;
83    private static final int ENTRY = 96667762;
84    private static final int MIME_TYPES = 1081186720;
85    private static final int MIME_TYPE = -242217677;
86    private static final int EXTENSION = -612557761;
87    private static final int TYPE = 3575610;
88  
89    private TobagoConfigFragment tobagoConfig;
90    private RendererConfig currentRenderer;
91    private ThemeImpl currentTheme;
92    private Boolean production;
93    private boolean exclude;
94    private StringBuilder buffer;
95    private Properties properties;
96    private String entryKey;
97    private String extension;
98    private String type;
99  
100   private Stack<String> stack;
101   private String version;
102 
103   public TobagoConfigParser() {
104     version = Package.getPackage("org.apache.myfaces.tobago.internal.config").getImplementationVersion();
105     LOG.debug("Tobago version: {}", version);
106   }
107 
108   public TobagoConfigFragment parse(final URL url)
109       throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
110 
111     if (LOG.isInfoEnabled()) {
112       LOG.info("Parsing configuration file: '{}'", url);
113     }
114 
115     final TobagoConfigVersion version = new TobagoConfigVersion(url);
116 
117     // todo: Is there a solution that validate with both, DTD and XSD?
118 
119     if (version.isSchema()) {
120       validate(url, version);
121     }
122 
123     InputStream inputStream = null;
124     try {
125       inputStream = url.openStream();
126       final SAXParserFactory factory = SAXParserFactory.newInstance();
127       if (!version.isSchema()) {
128         factory.setValidating(true);
129       }
130       final SAXParser saxParser = factory.newSAXParser();
131       saxParser.parse(inputStream, this);
132     } finally {
133       IoUtils.closeQuietly(inputStream);
134     }
135     return tobagoConfig;
136   }
137 
138   @Override
139   public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
140     super.ignorableWhitespace(ch, start, length);
141   }
142 
143   @Override
144   public void startDocument() throws SAXException {
145 
146     buffer = new StringBuilder();
147     stack = new Stack<String>();
148   }
149 
150   @Override
151   public void endDocument() throws SAXException {
152     assert stack.empty();
153     stack = null;
154   }
155 
156   @Override
157   public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
158       throws SAXException {
159 
160     // No unused content should be collected, specially text mixed with tags.
161     assert buffer.toString().trim().length() == 0;
162 
163     buffer.setLength(0);
164     stack.add(qName);
165 
166     switch (qName.hashCode()) {
167 
168       case TOBAGO_CONFIG:
169         tobagoConfig = new TobagoConfigFragment();
170         break;
171 
172       case CONTENT_SECURITY_POLICY:
173         final String mode = attributes.getValue("mode");
174         tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode));
175         break;
176 
177       case RENDERERS:
178         if (currentTheme != null) {
179           currentTheme.setRenderersConfig(new RenderersConfigImpl());
180         } else {
181           tobagoConfig.setRenderersConfig(new RenderersConfigImpl());
182         }
183         break;
184 
185       case RENDERER:
186         currentRenderer = new RendererConfig();
187         break;
188 
189       case THEME_DEFINITION:
190         currentTheme = new ThemeImpl();
191         tobagoConfig.addThemeDefinition(currentTheme);
192         break;
193 
194       case RESOURCES:
195         production = Boolean.parseBoolean(attributes.getValue("production"));
196         break;
197 
198       case EXCLUDES:
199         exclude = true;
200         break;
201 
202       case SCRIPT:
203         final ThemeScript script = new ThemeScript();
204         final String scriptName = attributes.getValue("name").replace("/_version/", '/' + version + '/');
205         script.setName(scriptName);
206         if (production) {
207           currentTheme.getProductionResources().addScript(script, exclude);
208         } else {
209           currentTheme.getResources().addScript(script, exclude);
210         }
211         break;
212 
213       case STYLE:
214         final ThemeStyle style = new ThemeStyle();
215         final String styleName = attributes.getValue("name").replace("/_version/", '/' + version + '/');
216         style.setName(styleName);
217         if (production) {
218           currentTheme.getProductionResources().addStyle(style, exclude);
219         } else {
220           currentTheme.getResources().addStyle(style, exclude);
221         }
222         break;
223 
224       case PROPERTIES:
225         properties = new Properties();
226         break;
227 
228       case ENTRY:
229         entryKey = attributes.getValue("key");
230         break;
231 
232       case NAME:
233       case ORDERING:
234       case BEFORE:
235       case AFTER:
236       case THEME_CONFIG:
237       case DEFAULT_THEME:
238       case SUPPORTED_THEME:
239       case SUPPORTED_MARKUP:
240       case MARKUP:
241       case CREATE_SESSION_SECRET:
242       case CHECK_SESSION_SECRET:
243       case CHECK_SECURITY_ANNOTATIONS:
244       case PREVENT_FRAME_ATTACKS:
245       case SET_NOSNIFF_HEADER:
246       case DIRECTIVE:
247       case THEME_DEFINITIONS:
248       case DISPLAY_NAME:
249       case VERSIONED:
250       case FALLBACK:
251       case SANITIZER:
252       case SANITIZER_CLASS:
253       case MIME_TYPES:
254       case MIME_TYPE:
255       case EXTENSION:
256       case TYPE:
257         break;
258 
259       default:
260         LOG.warn("Ignoring unknown start tag <" + qName + "> with hashCode=" + qName.hashCode());
261     }
262   }
263 
264   @Override
265   public void characters(final char[] ch, final int start, final int length) throws SAXException {
266     buffer.append(ch, start, length);
267   }
268 
269   @Override
270   public void endElement(final String uri, final String localName, final String qName) throws SAXException {
271     assert qName.equals(stack.peek());
272 
273     final String text = buffer.toString().trim();
274     buffer.setLength(0);
275 
276     switch (qName.hashCode()) {
277 
278       case NAME:
279         final String parent = stack.get(stack.size() - 2);
280         switch (parent.hashCode()) {
281 
282           case TOBAGO_CONFIG:
283             tobagoConfig.setName(text);
284             break;
285 
286           case BEFORE:
287             tobagoConfig.addBefore(text);
288             break;
289 
290           case AFTER:
291             tobagoConfig.addAfter(text);
292             break;
293 
294           case RENDERER:
295             currentRenderer.setName(text);
296             if (currentTheme != null) {
297               ((RenderersConfigImpl) currentTheme.getRenderersConfig()).addRenderer(currentRenderer);
298             } else {
299               ((RenderersConfigImpl) tobagoConfig.getRenderersConfig()).addRenderer(currentRenderer);
300             }
301             break;
302 
303           case THEME_DEFINITION:
304             currentTheme.setName(text);
305             break;
306 
307           default:
308             LOG.warn("Ignoring unknown parent <" + qName + "> of tag <name>");
309         }
310         break;
311 
312       case DEFAULT_THEME:
313         tobagoConfig.setDefaultThemeName(text);
314         break;
315 
316       case SUPPORTED_THEME:
317         tobagoConfig.addSupportedThemeName(text);
318         break;
319 
320       case CREATE_SESSION_SECRET:
321         tobagoConfig.setCreateSessionSecret(text);
322         break;
323 
324       case CHECK_SESSION_SECRET:
325         tobagoConfig.setCheckSessionSecret(text);
326         break;
327 
328       case PREVENT_FRAME_ATTACKS:
329         tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
330         break;
331 
332       case SET_NOSNIFF_HEADER:
333         tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text));
334         break;
335 
336       case CHECK_SECURITY_ANNOTATIONS:
337         tobagoConfig.setCheckSecurityAnnotations(Boolean.parseBoolean(text));
338         break;
339 
340       case DIRECTIVE:
341         tobagoConfig.getContentSecurityPolicy().getDirectiveList().add(text);
342         break;
343 
344       case MARKUP:
345         currentRenderer.addSupportedMarkup(text);
346         break;
347 
348       case DISPLAY_NAME:
349         currentTheme.setDisplayName(text);
350         break;
351 
352       case FALLBACK:
353         currentTheme.setFallbackName(text);
354         break;
355 
356       case THEME_DEFINITION:
357         currentTheme = null;
358         break;
359 
360       case VERSIONED:
361         currentTheme.setVersioned(Boolean.parseBoolean(text));
362         break;
363 
364       case RESOURCES:
365         production = null;
366         break;
367 
368       case EXCLUDES:
369         exclude = false;
370         break;
371 
372       case SANITIZER_CLASS:
373         tobagoConfig.setSanitizerClass(text);
374         break;
375 
376       case SANITIZER:
377         if (properties != null) {
378           tobagoConfig.setSanitizerProperties(properties);
379         }
380         properties = null;
381         break;
382 
383       case ENTRY:
384         properties.setProperty(entryKey, text);
385         entryKey = null;
386         break;
387 
388       case EXTENSION:
389         extension = text;
390         break;
391 
392       case TYPE:
393         type = text;
394         break;
395 
396       case MIME_TYPE:
397         tobagoConfig.addMimeType(extension, type);
398         break;
399 
400       case TOBAGO_CONFIG:
401       case THEME_CONFIG:
402       case ORDERING:
403       case BEFORE:
404       case AFTER:
405       case SUPPORTED_MARKUP:
406       case CONTENT_SECURITY_POLICY:
407       case THEME_DEFINITIONS:
408       case RENDERERS:
409       case RENDERER:
410       case SCRIPT:
411       case STYLE:
412       case PROPERTIES:
413       case MIME_TYPES:
414         break;
415 
416       default:
417         LOG.warn("Ignoring unknown end tag <" + qName + ">");
418     }
419 
420     stack.pop();
421   }
422 
423   @Override
424   public void warning(final SAXParseException e) throws SAXException {
425     throw e;
426   }
427 
428   @Override
429   public void error(final SAXParseException e) throws SAXException {
430     throw e;
431   }
432 
433   @Override
434   public void fatalError(final SAXParseException e) throws SAXException {
435     throw e;
436   }
437 
438   private void validate(final URL url, final TobagoConfigVersion version)
439       throws URISyntaxException, SAXException, IOException {
440 
441     final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
442     final Schema schema;
443     if ("3.1".equals(version.getVersion())) {
444       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_1));
445     } else if ("3.0".equals(version.getVersion())) {
446       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_0));
447     } else if ("2.0.6".equals(version.getVersion())) {
448       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0_6));
449     } else if ("2.0".equals(version.getVersion())) {
450       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0));
451     } else if ("1.6".equals(version.getVersion())) {
452       LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'");
453       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6));
454     } else if ("1.5".equals(version.getVersion())) {
455       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5));
456     } else {
457       throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'");
458     }
459     final Validator validator = schema.newValidator();
460     final Source source = new StreamSource(url.openStream());
461 
462     validator.validate(source);
463   }
464 
465 }