All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.peterphi.std.util.jaxb.XSDValidator Maven / Gradle / Ivy

There is a newer version: 10.1.5
Show newest version
package com.peterphi.std.util.jaxb;

import com.peterphi.std.util.jaxb.exception.SchemaValidationException;
import com.peterphi.std.util.jaxb.type.MultiXSDSchemaFile;
import com.peterphi.std.util.jaxb.type.MultiXSDSchemaFiles;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Wrapper class that performs XSD Validation
 */
public class XSDValidator
{
	private static final String XML_SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema";

	private final Schema schema;


	public XSDValidator(MultiXSDSchemaFiles schemas)
	{
		this(getSources(schemas));
	}


	public XSDValidator(Source... sources)
	{
		this(buildSchema(sources));
	}


	public XSDValidator(Schema schema)
	{
		this.schema = schema;
	}


	private static Schema buildSchema(Source... sources)
	{
		try
		{
			// Retrieve the schema
			SchemaFactory factory = SchemaFactory.newInstance(XML_SCHEMA_NAMESPACE);

			return factory.newSchema(sources);
		}
		catch (SAXException e)
		{
			throw new RuntimeException("Error creating Schema instance!", e);
		}
	}


	private static Source[] getSources(final MultiXSDSchemaFiles schemas)
	{
		return reorderDependencies(schemas.files).stream()
		                                         .map(XSDValidator:: createSource)
		                                         .collect(Collectors.toList())
		                                         .toArray(new Source[schemas.files.size()]);
	}


	private static List reorderDependencies(Collection input)
	{
		final List output = new ArrayList<>(input.size());

		// Produce a list of dependencies for each schema file
		// Iterate across the list, looking for entries whose dependencies are already in the output list
		//
		Map> dependencies = getDependencies(input);

		// Emit the schema files in order with dependencies ordered first
		for (MultiXSDSchemaFile file : input)
			emit(output, new HashSet<>(), dependencies, file);

		assert (output.size() == input.size());
		assert (output.containsAll(input));

		return output;
	}


	private static void emit(final List emitted,
	                         final Set emitting,
	                         final Map> dependencies,
	                         final MultiXSDSchemaFile current)
	{
		if (!emitted.contains(current))
		{
			if (emitting.contains(current))
				throw new IllegalArgumentException("Dependency flattening failed: there are loops in schema dependencies! Emitted " +
				                                   emitted +
				                                   ", emitting " +
				                                   emitting + ", dependencies=" + dependencies);

			emitting.add(current);

			for (MultiXSDSchemaFile dependency : dependencies.get(current))
				emit(emitted, emitting, dependencies, dependency);

			emitted.add(current);
		}
	}


	private static Map> getDependencies(Collection input)
	{
		// Index the schemafiles by their name
		final Map byName = input.stream().collect(Collectors.toMap(s -> s.name, s -> s));

		Map> output = new HashMap<>();

		for (MultiXSDSchemaFile file : input)
		{
			Set dependencies = getDependencies(file).stream().map(byName:: get).collect(Collectors.toSet());

			output.put(file, dependencies);
		}

		return output;
	}


	private static Source createSource(MultiXSDSchemaFile schema)
	{
		return new DOMSource(schema.schemaElement());
	}


	private static List getDependencies(MultiXSDSchemaFile schema)
	{
		final List dependencies = new ArrayList<>();

		final NodeList children = schema.schemaElement().getElementsByTagNameNS(XML_SCHEMA_NAMESPACE, "import");

		for (int i = 0; i < children.getLength(); i++)
		{
			if (children.item(i) instanceof Element)
			{
				final Element element = (Element) children.item(i);

				dependencies.add(element.getAttribute("schemaLocation"));
			}
		}

		return dependencies;
	}


	/**
	 * Validate some XML against this schema
	 *
	 * @param document
	 *
	 * @throws SchemaValidationException
	 * 		if the document does not validate against the schema
	 */
	public void validate(final Node document) throws SchemaValidationException
	{
		try
		{
			final Validator validator = schema.newValidator();

			validator.validate(new DOMSource(document));
		}
		catch (SAXException | IOException e)
		{
			throw new SchemaValidationException(e.getMessage(), e);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy