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 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 AUTO_ACCESS_KEY_FROM_LABEL = 2070339882;
85    private static final int CLASSIC_DATE_TIME_PICKER = -879394870; // "classic-date-time-picker".hashCode()
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 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 SCRIPT:
197         final ThemeScript script = new ThemeScript();
198         script.setName(attributes.getValue("name"));
199         if (production) {
200           currentTheme.getProductionResources().addScript(script);
201         } else {
202           currentTheme.getResources().addScript(script);
203         }
204         break;
205 
206       case STYLE:
207         final ThemeStyle style = new ThemeStyle();
208         style.setName(attributes.getValue("name"));
209         if (production) {
210           currentTheme.getProductionResources().addStyle(style);
211         } else {
212           currentTheme.getResources().addStyle(style);
213         }
214         break;
215 
216       case PROPERTIES:
217         properties = new Properties();
218         break;
219 
220       case ENTRY:
221         entryKey = attributes.getValue("key");
222         break;
223 
224       case NAME:
225       case ORDERING:
226       case BEFORE:
227       case AFTER:
228       case RESOURCE_DIR:
229       case THEME_CONFIG:
230       case DEFAULT_THEME:
231       case SUPPORTED_THEME:
232       case SUPPORTED_MARKUP:
233       case MARKUP:
234       case CREATE_SESSION_SECRET:
235       case CHECK_SESSION_SECRET:
236       case PREVENT_FRAME_ATTACKS:
237       case SET_NOSNIFF_HEADER:
238       case DIRECTIVE:
239       case THEME_DEFINITIONS:
240       case DISPLAY_NAME:
241       case RESOURCE_PATH:
242       case VERSIONED:
243       case FALLBACK:
244       case SANITIZER:
245       case SANITIZER_CLASS:
246       case AUTO_ACCESS_KEY_FROM_LABEL:
247       case CLASSIC_DATE_TIME_PICKER:
248       case MIME_TYPES:
249       case MIME_TYPE:
250       case EXTENSION:
251       case TYPE:
252         break;
253 
254       default:
255         LOG.warn("Ignoring unknown start tag <" + qName + ">");
256     }
257   }
258 
259   @Override
260   public void characters(final char[] ch, final int start, final int length) throws SAXException {
261     buffer.append(ch, start, length);
262   }
263 
264   @Override
265   public void endElement(final String uri, final String localName, final String qName) throws SAXException {
266     assert qName.equals(stack.peek());
267 
268     final String text = buffer.toString().trim();
269     buffer.setLength(0);
270 
271     switch (qName.hashCode()) {
272 
273       case NAME:
274         final String parent = stack.get(stack.size() - 2);
275         switch (parent.hashCode()) {
276 
277           case TOBAGO_CONFIG:
278             tobagoConfig.setName(text);
279             break;
280 
281           case BEFORE:
282             tobagoConfig.addBefore(text);
283             break;
284 
285           case AFTER:
286             tobagoConfig.addAfter(text);
287             break;
288 
289           case RENDERER:
290             currentRenderer.setName(text);
291             if (currentTheme != null) {
292               ((RenderersConfigImpl) currentTheme.getRenderersConfig()).addRenderer(currentRenderer);
293             } else {
294               ((RenderersConfigImpl) tobagoConfig.getRenderersConfig()).addRenderer(currentRenderer);
295             }
296             break;
297 
298           case THEME_DEFINITION:
299             currentTheme.setName(text);
300             break;
301 
302           default:
303             LOG.warn("Ignoring unknown parent <" + qName + "> of tag <name>");
304         }
305         break;
306 
307       case DEFAULT_THEME:
308         tobagoConfig.setDefaultThemeName(text);
309         break;
310 
311       case SUPPORTED_THEME:
312         tobagoConfig.addSupportedThemeName(text);
313         break;
314 
315       case RESOURCE_DIR:
316         tobagoConfig.addResourceDir(text);
317         break;
318 
319       case CREATE_SESSION_SECRET:
320         tobagoConfig.setCreateSessionSecret(text);
321         break;
322 
323       case CHECK_SESSION_SECRET:
324         tobagoConfig.setCheckSessionSecret(text);
325         break;
326 
327       case PREVENT_FRAME_ATTACKS:
328         tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
329         break;
330 
331       case SET_NOSNIFF_HEADER:
332         tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text));
333         break;
334 
335       case DIRECTIVE:
336         tobagoConfig.getContentSecurityPolicy().getDirectiveList().add(text);
337         break;
338 
339       case MARKUP:
340         currentRenderer.addSupportedMarkup(text);
341         break;
342 
343       case DISPLAY_NAME:
344         currentTheme.setDisplayName(text);
345         break;
346 
347       case RESOURCE_PATH:
348         currentTheme.setResourcePath(text);
349         break;
350 
351       case FALLBACK:
352         currentTheme.setFallbackName(text);
353         break;
354 
355       case THEME_DEFINITION:
356         currentTheme = null;
357         break;
358 
359       case VERSIONED:
360         currentTheme.setVersioned(Boolean.parseBoolean(text));
361         break;
362 
363       case RESOURCES:
364         production = null;
365         break;
366 
367       case SANITIZER_CLASS:
368         tobagoConfig.setSanitizerClass(text);
369         break;
370 
371       case SANITIZER:
372         if (properties != null) {
373           tobagoConfig.setSanitizerProperties(properties);
374         }
375         properties = null;
376         break;
377 
378       case ENTRY:
379         properties.setProperty(entryKey, text);
380         entryKey = null;
381         break;
382 
383       case AUTO_ACCESS_KEY_FROM_LABEL:
384         tobagoConfig.setAutoAccessKeyFromLabel(Boolean.parseBoolean(text));
385         break;
386 
387       case CLASSIC_DATE_TIME_PICKER:
388         tobagoConfig.setClassicDateTimePicker(text);
389         break;
390 
391       case EXTENSION:
392         extension = text;
393         break;
394 
395       case TYPE:
396         type = text;
397         break;
398 
399       case MIME_TYPE:
400         tobagoConfig.addMimeType(extension, type);
401         break;
402 
403       case TOBAGO_CONFIG:
404       case THEME_CONFIG:
405       case ORDERING:
406       case BEFORE:
407       case AFTER:
408       case SUPPORTED_MARKUP:
409       case CONTENT_SECURITY_POLICY:
410       case THEME_DEFINITIONS:
411       case RENDERERS:
412       case RENDERER:
413       case SCRIPT:
414       case STYLE:
415       case PROPERTIES:
416       case MIME_TYPES:
417         break;
418 
419       default:
420         LOG.warn("Ignoring unknown end tag <" + qName + ">");
421     }
422 
423     stack.pop();
424   }
425 
426   @Override
427   public void warning(final SAXParseException e) throws SAXException {
428     throw e;
429   }
430 
431   @Override
432   public void error(final SAXParseException e) throws SAXException {
433     throw e;
434   }
435 
436   @Override
437   public void fatalError(final SAXParseException e) throws SAXException {
438     throw e;
439   }
440 
441   private void validate(final URL url, final TobagoConfigVersion version)
442       throws URISyntaxException, SAXException, IOException {
443 
444     final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
445     final Schema schema;
446     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 }