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