Skip to content

Commit

Permalink
[liquibase#71] Implement validation against explicit schema
Browse files Browse the repository at this point in the history
This change is backward compatible: users can still define their
changelog files without explicit schema, it will work as expected.

This commit introduces a schema detector, from which the proper
validation strategy is deduced.
  • Loading branch information
Florent Biville committed Apr 22, 2016
1 parent 19b8a3c commit 588ea53
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 31 deletions.
@@ -0,0 +1,24 @@
/**
* Copyright 2014-2016 the original author or authors.
*
* 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.liquigraph.core.io.xml;

import javax.xml.transform.dom.DOMSource;
import java.util.Collection;

interface DomSourceValidator {

Collection<String> validate(DOMSource source) throws Exception;
}
@@ -0,0 +1,34 @@
/**
* Copyright 2014-2016 the original author or authors.
*
* 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.liquigraph.core.io.xml;

import javax.xml.transform.dom.DOMSource;

public class DomSourceValidatorFactory {

private final SchemaDetector schemaDetector;

public DomSourceValidatorFactory() {
schemaDetector = new SchemaDetector();
}

public DomSourceValidator createValidator(DOMSource source) {
if (schemaDetector.hasExplicitSchema(source)) {
return new ExplicitSchemaValidator();
}
return new ImplicitSchemaValidator();
}
}
@@ -0,0 +1,78 @@
/**
* Copyright 2014-2016 the original author or authors.
*
* 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.liquigraph.core.io.xml;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Collection;

class ExplicitSchemaValidator implements DomSourceValidator {

private final SAXParserFactory saxParserFactory;

public ExplicitSchemaValidator() {
saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setValidating(true);
saxParserFactory.setNamespaceAware(true);
}

@Override
public Collection<String> validate(DOMSource changelog) throws Exception {
SchemaErrorHandler errorHandler = new SchemaErrorHandler();
XMLReader reader = saxParser().getXMLReader();
reader.setErrorHandler(errorHandler);
parse(changelog, reader);
return errorHandler.getErrors();
}

private SAXParser saxParser() throws ParserConfigurationException, SAXException {
SAXParser saxParser = saxParserFactory.newSAXParser();
try {
saxParser.setProperty(
"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema"
);
} catch (SAXNotRecognizedException | SAXNotSupportedException ignored) {
}
return saxParser;
}

// quick'n'dirty: this is super inefficient
private void parse(DOMSource changelog, XMLReader reader) throws IOException, TransformerException, SAXException {
try (StringWriter writer = new StringWriter()) {
StreamResult result = new StreamResult(writer);
TransformerFactory.newInstance().newTransformer().transform(changelog, result);
try (InputStream inputStream = new ByteArrayInputStream(writer.toString().getBytes("UTF-8"))) {
reader.parse(new InputSource(inputStream));
}
}
}
}
@@ -0,0 +1,59 @@
/**
* Copyright 2014-2016 the original author or authors.
*
* 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.liquigraph.core.io.xml;

import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;

class ImplicitSchemaValidator implements DomSourceValidator {

private final Schema schema;

public ImplicitSchemaValidator() {
schema = implicitSchema("/schema/changelog.xsd");
}

@Override
public Collection<String> validate(DOMSource source) throws Exception {
SchemaErrorHandler errorHandler = new SchemaErrorHandler();
validator(errorHandler).validate(source);
return errorHandler.getErrors();
}

private static Schema implicitSchema(String name) {
try (InputStream stream = XmlSchemaValidator.class.getResourceAsStream(name)) {
return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(new StreamSource(stream));
} catch (SAXException | IOException e) {
throw new IllegalStateException(e);
}
}

private Validator validator(SchemaErrorHandler customErrorHandler) {
Validator validator = schema.newValidator();
validator.setErrorHandler(customErrorHandler);
return validator;
}

}
@@ -0,0 +1,71 @@
/**
* Copyright 2014-2016 the original author or authors.
*
* 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.liquigraph.core.io.xml;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import javax.xml.transform.dom.DOMSource;
import java.util.Iterator;

class SchemaDetector {

public boolean hasExplicitSchema(DOMSource changelog) {
Node node = changelog.getNode();
if (node.getNodeType() == Node.DOCUMENT_NODE) {
return hasExplicitSchema(rootElement(node));
}
return hasExplicitSchema(node);
}

private Node rootElement(Node document) {
Iterator<Node> iterator = new NodeListIterator(document.getChildNodes());
while (iterator.hasNext()) {
Node node = iterator.next();
if (node.getNodeType() == Node.ELEMENT_NODE
&& node.getNodeName().equals("changelog")) {
return node;
}
}
return null;
}

private boolean hasExplicitSchema(Node node) {
if (node == null) {
return false;
}
if (node.getNodeType() != Node.ELEMENT_NODE) {
return false;
}

NamedNodeMap attributes = node.getAttributes();
if (attributes == null) {
return false;
}
return findAttributeByName(attributes, "xsi:noNamespaceSchemaLocation");
}

private boolean findAttributeByName(NamedNodeMap attributes, String name) {
int i = 0;
while (i < attributes.getLength()) {
if (name.equals(attributes.item(i).getNodeName())) {
return true;
}
i++;
}
return false;
}
}
Expand Up @@ -22,7 +22,6 @@
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import java.util.ArrayList;
import java.util.Collection;

/**
Expand Down
Expand Up @@ -16,32 +16,14 @@
package org.liquigraph.core.io.xml;

import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;

import static java.lang.String.format;

public class XmlSchemaValidator {

private final Schema schema;

public XmlSchemaValidator() {
try (InputStream stream = getClass().getResourceAsStream("/schema/changelog.xsd")) {
schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(new StreamSource(stream));
} catch (SAXException | IOException e) {
throw new IllegalStateException(e);
}
}

/**
* Validate the fully resolved changelog containing all migrations
*
Expand All @@ -55,21 +37,19 @@ public Collection<String> validateSchema(Node changelog) {

private Collection<String> validate(DOMSource changelog) {
try {
SchemaErrorHandler customErrorHandler = new SchemaErrorHandler();
validator(customErrorHandler).validate(changelog);
return customErrorHandler.getErrors();
} catch (IOException | SAXException e) {
return parse(changelog);
} catch (Exception e) {
throw new IllegalArgumentException(
format("Exception while reading changelog: %n\t%s.", e.getMessage()),
e
);
}
}

private Validator validator(SchemaErrorHandler customErrorHandler) {
Validator validator = schema.newValidator();
validator.setErrorHandler(customErrorHandler);
return validator;
private Collection<String> parse(DOMSource changelog) throws Exception {
return new DomSourceValidatorFactory()
.createValidator(changelog)
.validate(changelog);
}

}

0 comments on commit 588ea53

Please sign in to comment.