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