/* * Copyright 2016 Anno van Vliet * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.openfire.plugin; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * An utility to convert a file to XML, and validate the XML against a set of XML Schema files. * * @author Anno van Vliet * */ public class UserSchemaValidator { private static final Logger Log = LoggerFactory.getLogger(UserSchemaValidator.class); private static final CharSequence STRICT_DECLARATION_MSG = "The matching wildcard is strict, but no declaration can be found for element"; private final InputStream source; private final Source[] schemaSources; /** * Construct a Validator object which parses and validates a input source. * * @param source XML input document * @param schemaFile zero or more schema files. */ UserSchemaValidator(InputStream source, String... schemaFile) { this.source = source; List<Source> sourceList = new ArrayList<Source>(); for (String schema : schemaFile) { try { URL schemaURL = this.getClass().getClassLoader().getResource(schema); if (schemaURL != null) { sourceList.add(new StreamSource(schemaURL.openStream())); } else { Log.warn("Cannot find schema definition " + schema); } } catch (IOException e) { Log.warn("Cannot open schema definition " + schema + " : " + e.getMessage()); Log.debug("", e); } } schemaSources = new Source[sourceList.size()]; sourceList.toArray(schemaSources); } /** * Perform a validate and a parse of the specified source document. * Validating is done with the specified schemafiles. * * @return A Document when the validation s successful. */ Document validateAndParse() { ValidatorErrorHandler handler = new ValidatorErrorHandler(); try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); documentBuilderFactory.setXIncludeAware(true); documentBuilderFactory.setValidating(false); // We don't want xml:base and xml:lang attributes in the output. documentBuilderFactory.setFeature( "http://apache.org/xml/features/xinclude/fixup-base-uris", false); documentBuilderFactory.setFeature( "http://apache.org/xml/features/xinclude/fixup-language", false); if ( schemaSources.length > 0 ) { Log.info("Checking Schema's"); SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); schemaFactory.setErrorHandler(handler); Schema schema = schemaFactory.newSchema(schemaSources); documentBuilderFactory.setSchema(schema); Log.info("Start validating document"); } else { Log.info("Loading document"); } DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); handler.reset(); // Communicate some info about the Xincludes in the imported file. These imports should be resolvable by the server. documentBuilder.setEntityResolver( new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { Log.info(String.format("resolved Entity:%s %s", (publicId != null ? publicId : "") , systemId)); return null; } } ); documentBuilder.setErrorHandler(handler); Document result = documentBuilder.parse(source); if (result != null && handler.isValid()) { return result; } else { Log.warn(String.format("document is invalid. %1$d errors found.", handler.getNrOfErrors())); return null; } } catch (Exception e) { Log.warn(String.format("document validation failed. %1$d errors found.", handler.getNrOfErrors())); Log.debug("", e); return null; } } private class ValidatorErrorHandler implements ErrorHandler { private int nrOfErrors = 0; public void error(SAXParseException e) { if (e.getMessage().contains(STRICT_DECLARATION_MSG)) { Log.warn("This error indicates there is no XML Schema to validate the refered element: " + e.getLocalizedMessage()); } else { Log.warn("ERROR:" + e.getLocalizedMessage()); nrOfErrors++; } } public void fatalError(SAXParseException e) { Log.error("Fatal:" + e.getLocalizedMessage()); nrOfErrors++; } public void warning(SAXParseException e) { Log.error("Warning:" + e.getLocalizedMessage()); } public void reset() { nrOfErrors = 0; } public boolean isValid() { return nrOfErrors == 0; } /** * @return the nrOfErrors */ public int getNrOfErrors() { return nrOfErrors; } } }