13

I've been looking for solutions to this problem for far too long considering how easy it sounds so I've come for some help.

I have an XML Schema which I have used with xjc to create my JAXB binding. This works fine when the XML is well formed. Unfortunately it also doesn't complain when the XML is not well formed. I cannot figure out how to do proper full validation against the schema when I try to unmarshall an XML file.

I have managed to use a ValidationEventCollector to handle events, which works for XML parsing errors such as mismatched tags but doesn't raise any events when there is a tag that is required but is completely absent.

From what I have seen validation can be done againsta schema, but you must know the path to the schema in order to pass it into the setSchema() method. The problem I have is that the path to the schema is stored in the XML header and I can't knwo at run time where the schema is going to be. Which is why it's stored in the XML file:

<?xml version="1.0" encoding="utf-8"?>
<DDSSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="/a/big/long/path/to/a/schema/file/DDSSettings.xsd">
<Field1>1</Field1>
<Field2>-1</Field2>

...etc

Every example I see uses setValidating(true), which is now deprecated, so throws an exception.

This is the Java code I have so far, which seems to only do XML validation, not schema validation:

try {
    JAXBContext jc = new JAXBContext() {
        private final JAXBContext jaxbContext = JAXBContext.newInstance("blah");

        @Override
        public Unmarshaller createUnmarshaller() throws JAXBException {
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            ValidationEventCollector vec = new ValidationEventCollector() {
                @Override
                public boolean handleEvent(ValidationEvent event) throws RuntimeException {
                    ValidationEventLocator vel = event.getLocator();
                    if (event.getSeverity() == event.ERROR || event.getSeverity() == event.FATAL_ERROR) {
                        String error = "XML Validation Exception:  " + event.getMessage() + " at row: " + vel.getLineNumber() + " column: " + vel.getColumnNumber();
                        System.out.println(error);
                    }
                    m_unmarshallingOk = false;
                    return false;
                }
            };
            unmarshaller.setEventHandler(vec);

            return unmarshaller;
        }

        @Override
        public Marshaller createMarshaller() throws JAXBException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        @SuppressWarnings("deprecation")
        public Validator createValidator() throws JAXBException {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    };

    Unmarshaller unmarshaller = jc.createUnmarshaller();
    m_ddsSettings = (com.ultra.DDSSettings)unmarshaller.unmarshal(new File(xmlfileName));
} catch (UnmarshalException ex) {
    Logger.getLogger(UniversalDomainParticipant.class.getName()).log(
    Level.SEVERE,
    null, ex);
} catch (JAXBException ex) {
    Logger.getLogger(UniversalDomainParticipant.class.getName()).log(
    Level.SEVERE,
    null, ex);
}

So what is the proper way to do this validation? I was expecting there to be a validate() method on the JAXB generated classes, but I guess that would be too simple for Java.

ndsmyter
  • 6,535
  • 3
  • 22
  • 37
fwg
  • 1,018
  • 2
  • 10
  • 25

2 Answers2

16

OK, I've found the solution. Using the schema factory to create a schema, but without specifying a schema file makes it work with the noNamespaceSchemaLocation specified in the XML file.

So the code from above has had this added:

SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setSchema(schema);
m_ddsSettings = (com.ultra.DDSSettings)unmarshaller.unmarshal(new File(xmlfileName));

Shame that took the best part of 24 hours to find the answer to!

The javadoc for SchemaFactory.newSchema() says:

For XML Schema, this method creates a Schema object that performs validation by using location hints specified in documents.

The returned Schema object assumes that if documents refer to the same URL in the schema location hints, they will always resolve to the same schema document. This asusmption allows implementations to reuse parsed results of schema documents so that multiple validations against the same schema will run faster.

skaffman
  • 398,947
  • 96
  • 818
  • 769
fwg
  • 1,018
  • 2
  • 10
  • 25
  • Interesting, I didn't know that. I took the liberty of adding some info to your answer, for future reference. – skaffman Mar 23 '10 at 00:14
  • 2
    Instead of specifying the XMLSchema URL explicitly, per the javadocs, use the appropriate value from XMLConstants as specified here: http://docs.oracle.com/javase/6/docs/api/index.html?javax/xml/validation/Schema.html – icfantv Nov 28 '11 at 18:38
  • @icfantv our are right, I use: Schema schema = SchemaFactory.newInstance (XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema (); // but the reference was: http://docs.oracle.com/javase/6/docs/api/index.html?javax/xml/validation/Schema.html - Additionally the thing i didn't understand about this solution is that the url is constant: it does not refer to the location of our own schema (was not obvious to me) – pdem Feb 23 '16 at 13:46
  • oops: direct reference to XMLConstants: http://docs.oracle.com/javase/6/docs/api/javax/xml/XMLConstants.html#W3C_XML_SCHEMA_NS_URI – pdem Feb 23 '16 at 13:52
1

As far as I know, you just have to set the schema with Marshaller.setSchema() to a schema created by the SchemaFactory from your DDSSettings.xsd. This will turn validation on.

Arne Burmeister
  • 20,046
  • 8
  • 53
  • 94
  • But the problem is that I don't know the path to that XSD file at compile time and am not given it at run time- it's stored in the XML file heading only. – fwg Mar 22 '10 at 12:00
  • But if you have jaxb-classes, they should have been generated from a schema. Why not include the schema in your project? – Arne Burmeister Mar 22 '10 at 12:31
  • JAXB doesn't *have* to work with a schema and generated code, it works perfectly well without a schema. – skaffman Mar 22 '10 at 22:41