views:

600

answers:

2

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.

+1  A: 

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
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.
fwgx
But if you have jaxb-classes, they should have been generated from a schema. Why not include the schema in your project?
Arne Burmeister
@Arne: JAXB doesn't *have* to work with a schema and generated code, it works perfectly well without a schema.
skaffman
@skaffman: right, sorry, but I am an interface first fan ;-)
Arne Burmeister
+3  A: 

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.

fwgx
Interesting, I didn't know that. I took the liberty of adding some info to your answer, for future reference.
skaffman