/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.testutil; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.net.URL; import java.util.List; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.eclipse.skalli.model.ExtensibleEntityBase; import org.eclipse.skalli.model.ExtensionEntityBase; import org.eclipse.skalli.services.extension.ExtensionService; import org.eclipse.skalli.services.extension.ExtensionServices; import org.eclipse.skalli.services.extension.rest.RestConverter; import org.eclipse.skalli.services.extension.rest.RestUtils; import org.junit.Assert; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; @SuppressWarnings("nls") public class SchemaValidationUtils { public static void validate(Object o, RestConverter<?> converter, String xsdFile) throws Exception { DocumentBuilderFactory dbf = getDocumentBuilderFactory(xsdFile); StringBufferHierarchicalStreamWriter writer = new StringBufferHierarchicalStreamWriter(); SchemaValidationUtils.marshal(o, converter, writer); validate(o, writer, dbf); } public static void validate(List<?> list, RestConverter<?> converter, String xsdFile) throws Exception { DocumentBuilderFactory dbf = getDocumentBuilderFactory(xsdFile); for (Object o : list) { StringBufferHierarchicalStreamWriter writer = new StringBufferHierarchicalStreamWriter(); SchemaValidationUtils.marshal(o, converter, writer); validate(o, writer, dbf); } } public static <T extends ExtensionEntityBase> void validate(List<? extends ExtensibleEntityBase> extensibles, Class<T> extensionClass, String xsdFile) throws Exception { DocumentBuilderFactory dbf = getDocumentBuilderFactory(xsdFile); for (ExtensibleEntityBase extensible : extensibles) { StringBufferHierarchicalStreamWriter writer = new StringBufferHierarchicalStreamWriter(); T extension = extensible.getExtension(extensionClass); if (extension != null) { boolean done = SchemaValidationUtils.marshalExtension(extensible, extension, writer); if (!done) { Assert.fail("Failed to marshal extension " + extensionClass + " for entity " + extensible); } validate(extensible, writer, dbf); } } } private static DocumentBuilderFactory getDocumentBuilderFactory(String xsdFile) throws Exception { URL schemaFile = RestUtils.findSchemaResource(xsdFile); String mergedSchema = SchemaValidationUtils.resolveIncludes(schemaFile); SchemaFactory xsFact = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = xsFact.newSchema(new StreamSource(new StringReader(mergedSchema))); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setValidating(false); dbf.setSchema(schema); return dbf; } private static void validate(final Object o, HierarchicalStreamWriter writer, DocumentBuilderFactory dbf) throws Exception { final String xml = writer.toString(); Reader reader = new StringReader(xml); DocumentBuilder db = dbf.newDocumentBuilder(); db.setErrorHandler(new ErrorHandler() { @Override public void warning(SAXParseException exception) throws SAXException { Assert.fail(o.toString() + ": " + exception.getMessage() + "\n" + xml); } @Override public void error(SAXParseException exception) throws SAXException { Assert.fail(o.toString() + ": " + exception.getMessage() + "\n" + xml); } @Override public void fatalError(SAXParseException exception) throws SAXException { Assert.fail(o.toString() + ": " + exception.getMessage() + "\n" + xml); } }); db.parse(new InputSource(reader)); } private static String resolveIncludes(URL schema) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); Document schemaDOM = dbf.newDocumentBuilder().parse(new InputSource(schema.openStream())); NodeList includes = schemaDOM.getElementsByTagName("xsd:include"); while (includes.getLength() > 0) { for (int i = 0; i < includes.getLength(); ++i) { Node includeNode = includes.item(i); Node includeParent = includeNode.getParentNode(); NamedNodeMap attributes = includeNode.getAttributes(); String schemaLocation = attributes.getNamedItem("schemaLocation").getTextContent(); URL includeFile = RestUtils.findSchemaResource(schemaLocation); Document includeDOM = dbf.newDocumentBuilder().parse(new InputSource(includeFile.openStream())); Element schemaRoot = schemaDOM.getDocumentElement(); Element includeRoot = includeDOM.getDocumentElement(); NodeList children = includeRoot.getChildNodes(); for (int j = 0; j < children.getLength(); ++j) { schemaRoot.insertBefore(schemaDOM.importNode(children.item(j), true), includeNode); } includeParent.removeChild(includeNode); } includes = schemaDOM.getElementsByTagName("xsd:include"); } StringWriter out = new StringWriter(); Transformer xform = TransformerFactory.newInstance().newTransformer(); xform.setOutputProperty(OutputKeys.INDENT, "yes"); xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); xform.transform(new DOMSource(schemaDOM), new StreamResult(out)); return out.toString(); } private static <T extends ExtensionEntityBase> boolean marshalExtension(ExtensibleEntityBase extensible, T extension, HierarchicalStreamWriter writer) { boolean done = false; for (ExtensionService<?> extensionService : ExtensionServices.getAll()) { RestConverter<?> converter = extensionService.getRestConverter("https://localhost"); Class<? extends ExtensionEntityBase> extensionClass = extension.getClass(); if (converter != null && extensionClass.equals(converter.getConversionClass())) { marshal(extension, new ConverterWrapper("https://localhost", converter, extensionService.getShortName(), extensible.isInherited(extensionClass)), writer); done = true; break; } } return done; } private static void marshal(Object o, RestConverter<?> converter, HierarchicalStreamWriter writer) { MarshallingContext context = new MarshallingContextMock(writer); converter.marshal(o, writer, context); } }