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