View Javadoc
1   /*
2    * Copyright 2009 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *     
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.openehealth.ipf.commons.xml;
17  
18  import org.openehealth.ipf.commons.core.modules.api.ValidationException;
19  import org.openehealth.ipf.commons.core.modules.api.Validator;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  import org.xml.sax.ErrorHandler;
23  import org.xml.sax.SAXException;
24  import org.xml.sax.SAXParseException;
25  
26  import javax.xml.XMLConstants;
27  import javax.xml.transform.Source;
28  import javax.xml.validation.Schema;
29  import javax.xml.validation.SchemaFactory;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.ConcurrentMap;
35  
36  /**
37   * Validation of XML documents based on a XML Schema. Before using this class
38   * consider using a validating Parser class.
39   * 
40   * @author Christian Ohr
41   */
42  public class XsdValidator extends AbstractCachingXmlProcessor<Schema> implements Validator<Source, String> {
43      private static final Logger LOG = LoggerFactory.getLogger(XsdValidator.class);
44  
45      private static final ConcurrentMap<String, Loader<Schema>> XSD_CACHE = new ConcurrentHashMap<>();
46      private static final LSResourceResolverImpl RESOURCE_RESOLVER = new LSResourceResolverImpl();
47  
48      private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
49  
50      public XsdValidator() {
51          super(null);
52      }
53  
54      public XsdValidator(ClassLoader classloader) {
55          super(classloader);
56      }
57  
58      @Override
59      protected ConcurrentMap<String, Loader<Schema>> getCache() {
60          return XSD_CACHE;
61      }
62  
63      @Override
64      public void validate(Source message, String schema) {
65          List<ValidationException> exceptions = doValidate(message, schema);
66          if (! exceptions.isEmpty()) {
67              throw new ValidationException(exceptions);
68          }
69      }
70  
71      /**
72       * @param message
73       *            the message to be validated
74       * @param schemaResource
75       *            the XML schema to validate against
76       * @return an array of validation exceptions
77       */
78      protected List<ValidationException> doValidate(Source message, String schemaResource) {
79          try {
80              LOG.debug("Validating XML message");
81              Schema schema = resource(schemaResource);
82              javax.xml.validation.Validator validator = schema.newValidator();
83              CollectingErrorHandler errorHandler = new CollectingErrorHandler();
84              validator.setErrorHandler(errorHandler);
85              validator.validate(message);
86              List<ValidationException> exceptions = errorHandler.getExceptions();
87              if (! exceptions.isEmpty()) {
88                  LOG.debug("Message validation found {} problems", exceptions.size());
89              } else {
90                  LOG.debug("Message validation successful");
91              }
92              return exceptions;
93          } catch (Exception e) {
94              return Collections.singletonList(new ValidationException(
95                      "Unexpected validation failure because " + e.getMessage(), e));
96          }
97      }
98  
99      @Override
100     protected Schema createResource(Object... params) {
101         // SchemaFactory is neither thread-safe nor reentrant
102         SchemaFactory factory = SchemaFactory.newInstance(getSchemaLanguage());
103 
104         // Register resource resolver to resolve external XML schemas
105         factory.setResourceResolver(RESOURCE_RESOLVER);
106         try {
107             return factory.newSchema(resourceContent(params));
108         } catch (SAXException e) {
109             throw new IllegalArgumentException("Could not initialize XSD schema", e);
110         }
111     }
112 
113     public String getSchemaLanguage() {
114         return schemaLanguage;
115     }
116 
117     public void setSchemaLanguage(String schemaLanguage) {
118         this.schemaLanguage = schemaLanguage;
119     }
120 
121     /**
122      * Error handler that collects {@link SAXParseException}s and provides them
123      * wrapped in an {@link ValidationException} array.
124      * 
125      * @author Christian Ohr
126      */
127     private static class CollectingErrorHandler implements ErrorHandler {
128 
129         private final List<ValidationException> exceptions = new ArrayList<>();
130 
131         @Override
132         public void error(SAXParseException exception) throws SAXException {
133             add(exception);
134         }
135 
136         @Override
137         public void fatalError(SAXParseException exception) throws SAXException {
138             add(exception);
139         }
140 
141         @Override
142         public void warning(SAXParseException exception) throws SAXException {
143             // TODO LOG some message
144         }
145 
146         private void add(SAXParseException exception) {
147             exceptions.add(new ValidationException(exception));
148         }
149 
150         public List<ValidationException> getExceptions() {
151             return exceptions;
152         }
153     }
154 }