/**************************************************************************** * Copyright (C) 2014 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.common.util; import org.openecard.common.interfaces.ObjectValidatorException; import org.openecard.common.interfaces.ObjectSchemaValidator; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import javax.annotation.Nonnull; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.util.JAXBSource; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * Utility class which allows to validate a JAXB object against XML schemas. * * @author Hans-Martin Haase * @author Tobias Wich */ public class JAXBSchemaValidator implements ObjectSchemaValidator { private static final Logger logger = LoggerFactory.getLogger(JAXBSchemaValidator.class); private final Schema schema; private final JAXBContext ctx; /** * Loads a JAXBSchemaValidator instance based on the given JAXB class and schemas. * * @param clazz Class of the JAXB element which shall be validated. * @param schemaNames Resource names of the schemas which shall be used in the validation process. * @return Instance if the schema validator capable of verificating the given schema. * @throws IOException Thrown in case the schemas could not be loaded from the given resources. * @throws SAXException Thrown in case the XML schemas are errornous. * @throws JAXBException Thrown in case the given JAXB class or one of its decendents is invalid. */ public static JAXBSchemaValidator load(@Nonnull Class<?> clazz, String... schemaNames) throws IOException, SAXException, JAXBException { try { if (schemaNames == null || schemaNames.length == 0) { throw new IOException("No schemas given to validate the object."); } else if (schemaNames.length == 1) { URL schemaURL = FileUtils.resolveResourceAsURL(JAXBSchemaValidator.class, schemaNames[0]); return new JAXBSchemaValidator(clazz, schemaURL); } else { StreamSource[] schemaDocuments = convertSchemaStrings2StreamSources(schemaNames); return new JAXBSchemaValidator(clazz, schemaDocuments); } } catch (IOException ex) { logger.error("No schemas given for the validation or the schemas could not be found.", ex); throw ex; } } private JAXBSchemaValidator(Class<?> clazz, URL schemaURL) throws SAXException, JAXBException { schema = getSchemaFactory().newSchema(schemaURL); ctx = getJAXBContext(clazz); } private JAXBSchemaValidator(Class<?> clazz, StreamSource[] schemaDocuments) throws SAXException, JAXBException { schema = getSchemaFactory().newSchema(schemaDocuments); ctx = getJAXBContext(clazz); } /** * Validates a JAXB object against the loaded XML schemas. * * Note: If you pass one single schema into the function the validator tries to load referenced schemas. If you * specify more than one schema you have to specify all other schemas which are referenced in the schemas. This has * to be done in the correct order. The correct ordering is definition first, then use of the definitions. * * @param obj The object to validate. This have to be an JAXB class. * @return TRUE if the object is valid against the schemas else FALSE. * @throws ObjectValidatorException Thrown if the JAXBSource object could not be initialized. */ @Override public boolean validateObject(@Nonnull Object obj) throws ObjectValidatorException { try { Source source = new JAXBSource(ctx, obj); Validator validator= schema.newValidator(); validator.setErrorHandler(new CustomErrorHandler()); validator.validate(source); return true; } catch (SAXException ex) { logger.error("Validation of the input object failed.", ex); return false; } catch (JAXBException ex) { String msg = "Failed to create Source instance from given object."; logger.error(msg, ex); throw new ObjectValidatorException(msg, ex); } catch (IOException ex) { logger.error("No object given for validation.", ex); throw new ObjectValidatorException("No object given for validation.", ex); } } /** * Converts an array of schema name strings to an array of StreamSource objects. * * @param schemaNames Array with schema names. * @return An array of StreamSource object. * @throws IOException Thrown in case a schema file was not found. */ private static StreamSource[] convertSchemaStrings2StreamSources(String[] schemaNames) throws IOException { ArrayList<StreamSource> sources = new ArrayList<>(); for(String schemaName : schemaNames) { StreamSource source = new StreamSource(FileUtils.resolveResourceAsStream(JAXBSchemaValidator.class, schemaName)); sources.add(source); } return sources.toArray(new StreamSource[sources.size()]); } private static SchemaFactory getSchemaFactory() { SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); return sf; } private static JAXBContext getJAXBContext(Class<?> clazz) throws JAXBException { JAXBContext ctx = JAXBContext.newInstance(clazz); return ctx; } private class CustomErrorHandler implements ErrorHandler { @Override public void warning(SAXParseException exception) throws SAXException { // Ignore this. One of the TRs demands to accept as much as possible. logger.warn(exception.getLocalizedMessage()); throw exception; } @Override public void error(SAXParseException exception) throws SAXException { throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { throw exception; } } }