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.Stack;
46  
47  public class TobagoConfigParser extends TobagoConfigEntityResolver {
48  
49    private static final Logger LOG = LoggerFactory.getLogger(TobagoConfigParser.class);
50  
51    private static final int TOBAGO_CONFIG = -1498874611;
52    private static final int NAME = 3373707;
53    private static final int ORDERING = 1234314708;
54    private static final int BEFORE = -1392885889;
55    private static final int AFTER = 92734940;
56    private static final int THEME_CONFIG = 1930630086;
57    private static final int DEFAULT_THEME = -114431171;
58    private static final int SUPPORTED_THEME = -822303766;
59    private static final int RESOURCE_DIR = -385546674;
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 CONTENT_SECURITY_POLICY = 1207440139;
64    private static final int DIRECTIVE = -962590641;
65    private static final int RENDERERS = 1839650832;
66    private static final int RENDERER = -494845757;
67    private static final int SUPPORTED_MARKUP = 71904295;
68    private static final int MARKUP = -1081305560;
69    private static final int THEME_DEFINITIONS = -255617156;
70    private static final int THEME_DEFINITION = 1515774935;
71    private static final int DISPLAY_NAME = 1568910518;
72    private static final int RESOURCE_PATH = 933304964;
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 SCRIPT = -907685685;
77    private static final int STYLE = 109780401;
78  
79    private TobagoConfigFragment tobagoConfig;
80    private RendererConfig currentRenderer;
81    private ThemeImpl currentTheme;
82    private Boolean production;
83    private StringBuilder buffer;
84  
85    private Stack<String> stack;
86  
87    public TobagoConfigParser() {
88    }
89  
90    public TobagoConfigFragment parse(URL url)
91        throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
92  
93      if (LOG.isInfoEnabled()) {
94        LOG.info("Parsing configuration file: '{}'", url);
95      }
96  
97      final TobagoConfigVersion version = new TobagoConfigVersion(url);
98  
99      // todo: Is there a solution that validate with both, DTD and XSD?
100 
101     if (version.isSchema()) {
102       validate(url, version);
103     }
104 
105     InputStream inputStream = null;
106     try {
107       inputStream = url.openStream();
108       final SAXParserFactory factory = SAXParserFactory.newInstance();
109       if (!version.isSchema()) {
110         factory.setValidating(true);
111       }
112       final SAXParser saxParser = factory.newSAXParser();
113       saxParser.parse(inputStream, this);
114     } finally {
115       IoUtils.closeQuietly(inputStream);
116     }
117     return tobagoConfig;
118   }
119 
120   @Override
121   public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
122     super.ignorableWhitespace(ch, start, length);
123   }
124 
125   @Override
126   public void startDocument() throws SAXException {
127 
128     buffer = new StringBuilder();
129     stack = new Stack<String>();
130   }
131 
132   @Override
133   public void endDocument() throws SAXException {
134     assert stack.empty();
135     stack = null;
136   }
137 
138   @Override
139   public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
140 
141     // No unused content should be collected, specially text mixed with tags.
142     assert buffer.toString().trim().length() == 0;
143 
144     buffer.setLength(0);
145     stack.add(qName);
146 
147     switch (qName.hashCode()) {
148 
149       case TOBAGO_CONFIG:
150         tobagoConfig = new TobagoConfigFragment();
151         break;
152 
153       case CONTENT_SECURITY_POLICY:
154         final String mode = attributes.getValue("mode");
155         tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode));
156         break;
157 
158       case RENDERERS:
159         if (currentTheme != null) {
160           currentTheme.setRenderersConfig(new RenderersConfigImpl());
161         } else {
162           tobagoConfig.setRenderersConfig(new RenderersConfigImpl());
163         }
164         break;
165 
166       case RENDERER:
167         currentRenderer = new RendererConfig();
168         break;
169 
170       case THEME_DEFINITION:
171         currentTheme = new ThemeImpl();
172         tobagoConfig.addThemeDefinition(currentTheme);
173         break;
174 
175       case RESOURCES:
176         production = Boolean.parseBoolean(attributes.getValue("production"));
177         break;
178 
179       case SCRIPT:
180         final ThemeScript script = new ThemeScript();
181         script.setName(attributes.getValue("name"));
182         if (production) {
183           currentTheme.getProductionResources().addScript(script);
184         } else {
185           currentTheme.getResources().addScript(script);
186         }
187         break;
188 
189       case STYLE:
190         final ThemeStyle style = new ThemeStyle();
191         style.setName(attributes.getValue("name"));
192         if (production) {
193           currentTheme.getProductionResources().addStyle(style);
194         } else {
195           currentTheme.getResources().addStyle(style);
196         }
197         break;
198 
199       case NAME:
200       case ORDERING:
201       case BEFORE:
202       case AFTER:
203       case RESOURCE_DIR:
204       case THEME_CONFIG:
205       case DEFAULT_THEME:
206       case SUPPORTED_THEME:
207       case SUPPORTED_MARKUP:
208       case MARKUP:
209       case CREATE_SESSION_SECRET:
210       case CHECK_SESSION_SECRET:
211       case PREVENT_FRAME_ATTACKS:
212       case DIRECTIVE:
213       case THEME_DEFINITIONS:
214       case DISPLAY_NAME:
215       case RESOURCE_PATH:
216       case VERSIONED:
217       case FALLBACK:
218         break;
219 
220       default:
221         LOG.warn("Ignoring unknown start tag <" + qName + ">");
222     }
223   }
224 
225   @Override
226   public void characters(char[] ch, int start, int length) throws SAXException {
227     buffer.append(ch, start, length);
228   }
229 
230   @Override
231   public void endElement(String uri, String localName, String qName) throws SAXException {
232     assert qName.equals(stack.peek());
233 
234     final String text = buffer.toString().trim();
235     buffer.setLength(0);
236 
237     switch (qName.hashCode()) {
238 
239       case NAME:
240         final String parent = stack.get(stack.size() - 2);
241         switch (parent.hashCode()) {
242 
243           case TOBAGO_CONFIG:
244             tobagoConfig.setName(text);
245             break;
246 
247           case BEFORE:
248             tobagoConfig.addBefore(text);
249             break;
250 
251           case AFTER:
252             tobagoConfig.addAfter(text);
253             break;
254 
255           case RENDERER:
256             currentRenderer.setName(text);
257             if (currentTheme != null) {
258               ((RenderersConfigImpl) currentTheme.getRenderersConfig()).addRenderer(currentRenderer);
259             } else {
260               ((RenderersConfigImpl) tobagoConfig.getRenderersConfig()).addRenderer(currentRenderer);
261             }
262             break;
263 
264           case THEME_DEFINITION:
265             currentTheme.setName(text);
266             break;
267 
268           default:
269             LOG.warn("Ignoring unknown parent <" + qName + "> of tag <name>");
270         }
271         break;
272 
273       case DEFAULT_THEME:
274         tobagoConfig.setDefaultThemeName(text);
275         break;
276 
277       case SUPPORTED_THEME:
278         tobagoConfig.addSupportedThemeName(text);
279         break;
280 
281       case RESOURCE_DIR:
282         tobagoConfig.addResourceDir(text);
283         break;
284 
285       case CREATE_SESSION_SECRET:
286         tobagoConfig.setCreateSessionSecret(text);
287         break;
288 
289       case CHECK_SESSION_SECRET:
290         tobagoConfig.setCheckSessionSecret(text);
291         break;
292 
293       case PREVENT_FRAME_ATTACKS:
294         tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
295         break;
296 
297       case DIRECTIVE:
298         tobagoConfig.getContentSecurityPolicy().getDirectiveList().add(text);
299         break;
300 
301       case MARKUP:
302         currentRenderer.addSupportedMarkup(text);
303         break;
304 
305       case DISPLAY_NAME:
306         currentTheme.setDisplayName(text);
307         break;
308 
309       case RESOURCE_PATH:
310         currentTheme.setResourcePath(text);
311         break;
312 
313       case FALLBACK:
314         currentTheme.setFallbackName(text);
315         break;
316 
317       case THEME_DEFINITION:
318         currentTheme = null;
319         break;
320 
321       case VERSIONED:
322         currentTheme.setVersioned(Boolean.parseBoolean(text));
323         break;
324 
325       case RESOURCES:
326         production = null;
327         break;
328 
329       case TOBAGO_CONFIG:
330       case THEME_CONFIG:
331       case ORDERING:
332       case BEFORE:
333       case AFTER:
334       case SUPPORTED_MARKUP:
335       case CONTENT_SECURITY_POLICY:
336       case THEME_DEFINITIONS:
337       case RENDERERS:
338       case RENDERER:
339       case SCRIPT:
340       case STYLE:
341         break;
342 
343       default:
344         LOG.warn("Ignoring unknown end tag <" + qName + ">");
345     }
346 
347     stack.pop();
348   }
349 
350   @Override
351   public void warning(SAXParseException e) throws SAXException {
352     throw e;
353   }
354 
355   @Override
356   public void error(SAXParseException e) throws SAXException {
357     throw e;
358   }
359 
360   @Override
361   public void fatalError(SAXParseException e) throws SAXException {
362     throw e;
363   }
364 
365   private void validate(final URL url, final TobagoConfigVersion version)
366       throws URISyntaxException, SAXException, IOException {
367 
368     final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
369     final Schema schema;
370     if ("2.0".equals(version.getVersion())) {
371       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0));
372     } else if ("1.6".equals(version.getVersion())) {
373       LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'");
374       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6));
375     } else if ("1.5".equals(version.getVersion())) {
376       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5));
377     } else {
378       throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'");
379     }
380     final Validator validator = schema.newValidator();
381     final Source source = new StreamSource(url.openStream());
382 
383     validator.validate(source);
384   }
385 
386 }