o.o
o.o

Reputation: 3751

Java, Validate XML against an embedded schema in WSDL

I have a .wsdl file with an embedded schema. I want to validate an XML file/string using that .wsdl file (the same way you would validate against an .xsd). The schema is between the <types> tag. I have this so far:

public boolean validate(String xmlString) {
    try {
        // Convert to input stream
        InputStream xml = new ByteArrayInputStream(xmlString.getBytes());

        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(new File("wsdl_filepath"));

        // Validate against wsdl
        Validator validator = schema.newValidator();
        validator.validate(new StreamSource (xml));

        // XML Message is valid
        return true;

    } catch (SAXException e) {
        e.printStackTrace();
        return false;

    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

This doesn't work, however. This validator works if validating against an .xsd. I'm not sure how to modify it to validate against the embedded schema.

Any help would be appreciated, thank you!

Upvotes: 5

Views: 5216

Answers (2)

sawprogramming
sawprogramming

Reputation: 867

Expanding on @morten's answer, I've found that one can generate the schema files using the JAXB-annotated classes instead of grabbing the schemas directly from the WSDL file. Adding the resource resolver to the schema factory is still required when using this method.

private static Schema createSchemaFromType(final Class<?> type) throws Exception {
    final AtomicInteger          sequence            = new AtomicInteger(1);
    final ByteArrayOutputStream  schemasOutputStream = new ByteArrayOutputStream();
    final DocumentBuilderFactory dbFactory           = DocumentBuilderFactory.newInstance();
    dbFactory.setNamespaceAware(true);

    // Generate the schemas from the JAXB annotated classes and put the output in the schemasOutputStream.
    JAXBContext.newInstance(type).generateSchema(new SchemaOutputResolver() {
        @Override
        public Result createOutput(final String namespaceUri, final String suggestedFileName) {
            final Result result = new StreamResult(schemasOutputStream);
            result.setSystemId("sys" + sequence.getAndIncrement());
            return result;
        }
    });

    // The result of the previous operation puts all schemas in a byte array. Transform the byte array output into
    // a string, which will contain a number of different schemas, then split the string to put each individual
    // schema in its own string.
    final byte[]                 schemasOutputBytes           = schemasOutputStream.toByteArray();
    final String                 schemasOutputString          = new String(schemasOutputBytes, StandardCharsets.UTF_8);
    final String                 schemasOutputStringDelimited = schemasOutputString.replace("<?xml", "<<<START OF SCHEMA>>><?xml");
    final String[]               schemaStrings                = schemasOutputStringDelimited.split("<<<START OF SCHEMA>>>");
    final Map<String, DOMSource> schemasSourcesByNamespace    = new HashMap<>();

    // Map the schema documents by their target namespace.
    for (final String schemaString : schemaStrings) {
        if (schemaString.trim().length() > 0) {
            final String               schema                = schemaString.replace("schemaLocation=\".*?\"", "");
            final ByteArrayInputStream schemaInputStream     = new ByteArrayInputStream(schema.getBytes(StandardCharsets.UTF_8));
            final DocumentBuilder      documentBuilder       = dbFactory.newDocumentBuilder();
            final Document             schemaDocument        = documentBuilder.parse(schemaInputStream);
            final Element              schemaElement         = schemaDocument.getDocumentElement();
            final String               schemaTargetNamespace = schemaElement.getAttribute("targetNamespace");
            schemasSourcesByNamespace.put(schemaTargetNamespace, new DOMSource(schemaDocument));
        }
    }

    // Create the schema factory in a way that it can resolve the schemas for all the namespaces previously created.
    final DOMImplementationRegistry domImplementationRegistry = DOMImplementationRegistry.newInstance();
    final DOMImplementationLS       domImplementation         = (DOMImplementationLS) domImplementationRegistry.getDOMImplementation("LS");
    final SchemaFactory             schemaFactory             = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    schemaFactory.setResourceResolver(new LSResourceResolver() {
        @Override
        public LSInput resolveResource(
            final String type,
            final String namespaceUri,
            final String publicId,
            final String systemId,
            final String baseUri
        ) {
            final Source xmlSource = schemasSourcesByNamespace.get(namespaceUri);
            if (xmlSource != null) {
                final LSInput               input        = domImplementation.createLSInput();
                final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                final Result                outputTarget = new StreamResult(outputStream);

                try {
                    transformerFactory.newTransformer().transform(xmlSource, outputTarget);
                } catch (final TransformerException e) {
                    throw new RuntimeException(
                        "Failed to transform schema document for namespace '" + namespaceUri + "' into an " +
                        "output stream due to an unexpected error.",
                        e
                    );
                }

                final InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
                input.setByteStream (is);
                input.setSystemId   (systemId);
                return input;
            } else {
                return null;
            }
        }
    });

    // Create the schema using the schema sources.
    return schemaFactory.newSchema(schemasSourcesByNamespace.values().toArray(new DOMSource[]{}));
}

Upvotes: 0

morten
morten

Reputation: 401

Here is what worked for me to extract the schemas from wsld:

public static Schema makeSchema(String pathToWsdl)
    throws ParserConfigurationException, IOException, SAXException, InstantiationException, IllegalAccessException, ClassNotFoundException {
    // read wsdl document
    File wsdlFile = new File(pathToWsdl);
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    dbFactory.setNamespaceAware(true);
    DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
    Document wsdlDoc = dBuilder.parse(wsdlFile);

    // read namespace declarations from wsdl document, in case they are referred from a schema
    NamedNodeMap attributes = wsdlDoc.getDocumentElement().getAttributes();
    Map<String, String> namespacesFromWsdlDocument = new HashMap<>();
    for (int i = 0; i < attributes.getLength(); i++) {
        Node n = attributes.item(i);
        if (n.getNamespaceURI() != null && n.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/")) {
            namespacesFromWsdlDocument
                .put(n.getLocalName(), n.getNodeValue());
        }
    }

    // read the schema nodes from the wsdl
    NodeList schemas = wsdlDoc.getElementsByTagNameNS("http://www.w3.org/2001/XMLSchema", "schema");
    Map<String, DOMSource> sources = new HashMap<>();
    for (int i = 0; i < schemas.getLength(); i++) {
        // create a document for each schema and copy the source schema
        Document schema = dBuilder.newDocument();
        Element schemaElement = (Element)schema.importNode(schemas.item(i), true);

        // add all non-existing namespace declarations from the wsdl node
        String targetNs = schemaElement.getAttribute("targetNamespace");
        for (Map.Entry<String, String> ns : namespacesFromWsdlDocument.entrySet()) {
            String name = ns.getKey();
            String value = ns.getValue();
            if (schemaElement.getAttributeNodeNS("http://www.w3.org/2000/xmlns/", name) == null) {
                schemaElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + name, value);
            }
        }

        // map schemas by their target namespace
        schema.appendChild(schemaElement);
        DOMSource domSource = new DOMSource(schema);
        sources.put(targetNs, domSource);
    }

    SchemaFactory factory =
        SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

    // Create a ResourceResolver that can find the correct schema from the map
    DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();

    final DOMImplementationLS domImplementationLS = (DOMImplementationLS) registry.getDOMImplementation("LS");
    factory.setResourceResolver(new LSResourceResolver() {
        @Override public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
            Source xmlSource = sources.get(namespaceURI);
            if (xmlSource != null) {
                LSInput input = domImplementationLS.createLSInput();
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                Result outputTarget = new StreamResult(outputStream);
                try {
                    TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
                } catch (TransformerException e) {
                    e.printStackTrace();
                }
                InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
                input.setByteStream(is);
                input.setSystemId(systemId);
                return input;
            } else {
                return null;
            }
        }
    });

    // create the schema object from the sources
    return factory.newSchema(sources.values().toArray(new DOMSource[]{}));
}

Upvotes: 5

Related Questions