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 RESOURCE_DIR = -385546674;
61    private static final int CREATE_SESSION_SECRET = 413906616;
62    private static final int CHECK_SESSION_SECRET = 275994924;
63    private static final int PREVENT_FRAME_ATTACKS = 270456726;
64    private static final int SET_NOSNIFF_HEADER = -1238451304;
65    private static final int CONTENT_SECURITY_POLICY = 1207440139;
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 RESOURCE_PATH = 933304964;
75    private static final int FALLBACK = 761243362;
76    private static final int VERSIONED = -1407102089;
77    private static final int RESOURCES = -1983070683;
78    private static final int EXCLUDES = 1994055129;
79    private static final int SANITIZER = 1807639849;
80    private static final int SANITIZER_CLASS = -974266412;
81    private static final int SCRIPT = -907685685;
82    private static final int STYLE = 109780401;
83    private static final int PROPERTIES = -926053069;
84    private static final int ENTRY = 96667762;
85    private static final int MIME_TYPES = 1081186720;
86    private static final int MIME_TYPE = -242217677;
87    private static final int EXTENSION = -612557761;
88    private static final int TYPE = 3575610;
89  
90    private TobagoConfigFragment tobagoConfig;
91    private RendererConfig currentRenderer;
92    private ThemeImpl currentTheme;
93    private Boolean production;
94    private boolean exclude;
95    private StringBuilder buffer;
96    private Properties properties;
97    private String entryKey;
98    private String extension;
99    private String type;
100 
101   private Stack<String> stack;
102 
103   public TobagoConfigParser() {
104   }
105 
106   public TobagoConfigFragment parse(final URL url)
107       throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
108 
109     if (LOG.isInfoEnabled()) {
110       LOG.info("Parsing configuration file: '{}'", url);
111     }
112 
113     final TobagoConfigVersion version = new TobagoConfigVersion(url);
114 
115     // todo: Is there a solution that validate with both, DTD and XSD?
116 
117     if (version.isSchema()) {
118       validate(url, version);
119     }
120 
121     InputStream inputStream = null;
122     try {
123       inputStream = url.openStream();
124       final SAXParserFactory factory = SAXParserFactory.newInstance();
125       if (!version.isSchema()) {
126         factory.setValidating(true);
127       }
128       final SAXParser saxParser = factory.newSAXParser();
129       saxParser.parse(inputStream, this);
130     } finally {
131       IoUtils.closeQuietly(inputStream);
132     }
133     return tobagoConfig;
134   }
135 
136   @Override
137   public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
138     super.ignorableWhitespace(ch, start, length);
139   }
140 
141   @Override
142   public void startDocument() throws SAXException {
143 
144     buffer = new StringBuilder();
145     stack = new Stack<String>();
146   }
147 
148   @Override
149   public void endDocument() throws SAXException {
150     assert stack.empty();
151     stack = null;
152   }
153 
154   @Override
155   public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
156       throws SAXException {
157 
158     // No unused content should be collected, specially text mixed with tags.
159     assert buffer.toString().trim().length() == 0;
160 
161     buffer.setLength(0);
162     stack.add(qName);
163 
164     switch (qName.hashCode()) {
165 
166       case TOBAGO_CONFIG:
167         tobagoConfig = new TobagoConfigFragment();
168         break;
169 
170       case CONTENT_SECURITY_POLICY:
171         final String mode = attributes.getValue("mode");
172         tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode));
173         break;
174 
175       case RENDERERS:
176         if (currentTheme != null) {
177           currentTheme.setRenderersConfig(new RenderersConfigImpl());
178         } else {
179           tobagoConfig.setRenderersConfig(new RenderersConfigImpl());
180         }
181         break;
182 
183       case RENDERER:
184         currentRenderer = new RendererConfig();
185         break;
186 
187       case THEME_DEFINITION:
188         currentTheme = new ThemeImpl();
189         tobagoConfig.addThemeDefinition(currentTheme);
190         break;
191 
192       case RESOURCES:
193         production = Boolean.parseBoolean(attributes.getValue("production"));
194         break;
195 
196       case EXCLUDES:
197         exclude = true;
198         break;
199 
200       case SCRIPT:
201         final ThemeScript script = new ThemeScript();
202         script.setName(attributes.getValue("name"));
203         if (production) {
204           currentTheme.getProductionResources().addScript(script, exclude);
205         } else {
206           currentTheme.getResources().addScript(script, exclude);
207         }
208         break;
209 
210       case STYLE:
211         final ThemeStyle style = new ThemeStyle();
212         style.setName(attributes.getValue("name"));
213         if (production) {
214           currentTheme.getProductionResources().addStyle(style, exclude);
215         } else {
216           currentTheme.getResources().addStyle(style, exclude);
217         }
218         break;
219 
220       case PROPERTIES:
221         properties = new Properties();
222         break;
223 
224       case ENTRY:
225         entryKey = attributes.getValue("key");
226         break;
227 
228       case NAME:
229       case ORDERING:
230       case BEFORE:
231       case AFTER:
232       case RESOURCE_DIR:
233       case THEME_CONFIG:
234       case DEFAULT_THEME:
235       case SUPPORTED_THEME:
236       case SUPPORTED_MARKUP:
237       case MARKUP:
238       case CREATE_SESSION_SECRET:
239       case CHECK_SESSION_SECRET:
240       case PREVENT_FRAME_ATTACKS:
241       case SET_NOSNIFF_HEADER:
242       case DIRECTIVE:
243       case THEME_DEFINITIONS:
244       case DISPLAY_NAME:
245       case RESOURCE_PATH:
246       case VERSIONED:
247       case FALLBACK:
248       case SANITIZER:
249       case SANITIZER_CLASS:
250       case MIME_TYPES:
251       case MIME_TYPE:
252       case EXTENSION:
253       case TYPE:
254         break;
255 
256       default:
257         LOG.warn("Ignoring unknown start tag <" + qName + "> with hashCode=" + qName.hashCode());
258     }
259   }
260 
261   @Override
262   public void characters(final char[] ch, final int start, final int length) throws SAXException {
263     buffer.append(ch, start, length);
264   }
265 
266   @Override
267   public void endElement(final String uri, final String localName, final String qName) throws SAXException {
268     assert qName.equals(stack.peek());
269 
270     final String text = buffer.toString().trim();
271     buffer.setLength(0);
272 
273     switch (qName.hashCode()) {
274 
275       case NAME:
276         final String parent = stack.get(stack.size() - 2);
277         switch (parent.hashCode()) {
278 
279           case TOBAGO_CONFIG:
280             tobagoConfig.setName(text);
281             break;
282 
283           case BEFORE:
284             tobagoConfig.addBefore(text);
285             break;
286 
287           case AFTER:
288             tobagoConfig.addAfter(text);
289             break;
290 
291           case RENDERER:
292             currentRenderer.setName(text);
293             if (currentTheme != null) {
294               ((RenderersConfigImpl) currentTheme.getRenderersConfig()).addRenderer(currentRenderer);
295             } else {
296               ((RenderersConfigImpl) tobagoConfig.getRenderersConfig()).addRenderer(currentRenderer);
297             }
298             break;
299 
300           case THEME_DEFINITION:
301             currentTheme.setName(text);
302             break;
303 
304           default:
305             LOG.warn("Ignoring unknown parent <" + qName + "> of tag <name>");
306         }
307         break;
308 
309       case DEFAULT_THEME:
310         tobagoConfig.setDefaultThemeName(text);
311         break;
312 
313       case SUPPORTED_THEME:
314         tobagoConfig.addSupportedThemeName(text);
315         break;
316 
317       case RESOURCE_DIR:
318         tobagoConfig.addResourceDir(text);
319         break;
320 
321       case CREATE_SESSION_SECRET:
322         tobagoConfig.setCreateSessionSecret(text);
323         break;
324 
325       case CHECK_SESSION_SECRET:
326         tobagoConfig.setCheckSessionSecret(text);
327         break;
328 
329       case PREVENT_FRAME_ATTACKS:
330         tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
331         break;
332 
333       case SET_NOSNIFF_HEADER:
334         tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text));
335         break;
336 
337       case DIRECTIVE:
338         tobagoConfig.getContentSecurityPolicy().getDirectiveList().add(text);
339         break;
340 
341       case MARKUP:
342         currentRenderer.addSupportedMarkup(text);
343         break;
344 
345       case DISPLAY_NAME:
346         currentTheme.setDisplayName(text);
347         break;
348 
349       case RESOURCE_PATH:
350         currentTheme.setResourcePath(text);
351         break;
352 
353       case FALLBACK:
354         currentTheme.setFallbackName(text);
355         break;
356 
357       case THEME_DEFINITION:
358         currentTheme = null;
359         break;
360 
361       case VERSIONED:
362         currentTheme.setVersioned(Boolean.parseBoolean(text));
363         break;
364 
365       case RESOURCES:
366         production = null;
367         break;
368 
369       case EXCLUDES:
370         exclude = false;
371         break;
372 
373       case SANITIZER_CLASS:
374         tobagoConfig.setSanitizerClass(text);
375         break;
376 
377       case SANITIZER:
378         if (properties != null) {
379           tobagoConfig.setSanitizerProperties(properties);
380         }
381         properties = null;
382         break;
383 
384       case ENTRY:
385         properties.setProperty(entryKey, text);
386         entryKey = null;
387         break;
388 
389       case EXTENSION:
390         extension = text;
391         break;
392 
393       case TYPE:
394         type = text;
395         break;
396 
397       case MIME_TYPE:
398         tobagoConfig.addMimeType(extension, type);
399         break;
400 
401       case TOBAGO_CONFIG:
402       case THEME_CONFIG:
403       case ORDERING:
404       case BEFORE:
405       case AFTER:
406       case SUPPORTED_MARKUP:
407       case CONTENT_SECURITY_POLICY:
408       case THEME_DEFINITIONS:
409       case RENDERERS:
410       case RENDERER:
411       case SCRIPT:
412       case STYLE:
413       case PROPERTIES:
414       case MIME_TYPES:
415         break;
416 
417       default:
418         LOG.warn("Ignoring unknown end tag <" + qName + ">");
419     }
420 
421     stack.pop();
422   }
423 
424   @Override
425   public void warning(final SAXParseException e) throws SAXException {
426     throw e;
427   }
428 
429   @Override
430   public void error(final SAXParseException e) throws SAXException {
431     throw e;
432   }
433 
434   @Override
435   public void fatalError(final SAXParseException e) throws SAXException {
436     throw e;
437   }
438 
439   private void validate(final URL url, final TobagoConfigVersion version)
440       throws URISyntaxException, SAXException, IOException {
441 
442     final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
443     final Schema schema;
444     if ("3.0".equals(version.getVersion())) {
445       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_0));
446     } else if ("2.0.6".equals(version.getVersion())) {
447       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0_6));
448     } else if ("2.0".equals(version.getVersion())) {
449       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0));
450     } else if ("1.6".equals(version.getVersion())) {
451       LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'");
452       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6));
453     } else if ("1.5".equals(version.getVersion())) {
454       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5));
455     } else {
456       throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'");
457     }
458     final Validator validator = schema.newValidator();
459     final Source source = new StreamSource(url.openStream());
460 
461     validator.validate(source);
462   }
463 
464 }