views:

1044

answers:

3

I am using jaxb for my application configurations

I feel like I am doing something really crooked and I am looking for a way to not need an actual file or this transaction.

As you can see in code I:

1.create a schema into a file from my JaxbContext (from my class annotation actually) 2.set this schema file in order to allow true validation when I unmarshal

JAXBContext context = JAXBContext.newInstance(clazz);
Schema mySchema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schemaFile);
jaxbContext.generateSchema(new MySchemaOutputResolver()); // ultimately creates schemaFile   
Unmarshaller u = m_context.createUnmarshaller();
u.setSchema(mySchema);
u.unmarshal(...);

do any of you know how I can validate jaxb without needing to create a schema file that sits in my computer?

Do I need to create a schema for validation, it looks redundant when I get it by JaxbContect.generateSchema ?

How do you do this?

A: 

I believe you just need to set a ValidationEventHandler on your unmarshaller. Something like this:

public class JAXBValidator extends ValidationEventCollector {
    @Override
    public boolean handleEvent(ValidationEvent event) {
        if (event.getSeverity() == event.ERROR ||
            event.getSeverity() == event.FATAL_ERROR)
        {
            ValidationEventLocator locator = event.getLocator();
            // change RuntimeException to something more appropriate
            throw new RuntimeException("XML Validation Exception:  " +
                event.getMessage() + " at row: " + locator.getLineNumber() +
                " column: " + locator.getColumnNumber());
        }

        return true;
    }
}

And in your code:

Unmarshaller u = m_context.createUnmarshaller();
u.setEventHandler(new JAXBValidator());
u.unmarshal(...);
Jason Day
Thanks for your response, as far as understand When unmarshalling without a schema you are effectively not validating the XML.for example @XmlElement(required=true) annotation will not be validated without a schema. am I wrong?
ekeren
I had assumed that you had a schema to begin with, and generated your JAXB object from it with xjc. Is that not the case? Even if it's not though, I believe the unmarshaller can still validate as long as all the elements are annotated correctly.
Jason Day
Not I have started with an annotated class and not a schema. I can create the schema out of the class using the generateSchema call on JaxbContext. The problem is that if I do not supply a schema unmarshal validation is poor. It warn about unexpected elements but doesn't warn about elements duplication and required elements for example.
ekeren
If you want to validate the XML, you will need a schema. You can generate the schema once using schemagen, which comes with JAXB, instead of generating it at runtime from the context.
Jason Day
Thanks Jason, do you know if I can generate an in-memory schema that jaxb can validate with... I really don't want to create schema files that needs to be packaged with my application, I have some legacy code issue.
ekeren
I am not aware of any way to do that, sorry.
Jason Day
I found a solution, written as Answer
ekeren
A: 

Found a way to do it, I am using Streams for creation of Schema and reading the schema.

JAXBContext context = JAXBContext.newInstance(clazz)
Unmarshaller u = context.createUnmarshaller(); 
final PipedInputStream in = new PipedInputStream();
context.generateSchema(new SchemaOutputResolver(){
  @Override
  public Result createOutput(String namespaceUri,   String suggestedFileName) throws IOException {
    StreamResult streamResult = new StreamResult(new PipedOutputStream(in));
    streamResult.setSystemId("");
    return streamResult;
  }});
u.setSchema(SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI ).newSchema(new StreamSource(in,"")));

The only thing I do not understand is what is the meaning of the set/getSystemId as you can see I am using blank String "".

ekeren
+1  A: 

Regarding ekeren's solution above, it's not a good idea to use PipedOutputStream/PipedInputStream in a single thread, lest you overflow the buffer and cause a deadlock. ByteArrayOutputStream/ByteArrayInputStream works, but if your JAXB classes generate multiple schemas (in different namespaces) you need multiple StreamSources.

I ended up with this:

JAXBContext jc = JAXBContext.newInstance(Something.class);
final List<ByteArrayOutputStream> outs = new ArrayList<ByteArrayOutputStream>();
jc.generateSchema(new SchemaOutputResolver(){
    @Override
    public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        outs.add(out);
        StreamResult streamResult = new StreamResult(out);
        streamResult.setSystemId("");
        return streamResult;
    }});
StreamSource[] sources = new StreamSource[outs.size()];
for (int i=0; i<outs.size(); i++) {
    ByteArrayOutputStream out = outs.get(i);
    // to examine schema: System.out.append(new String(out.toByteArray()));
    sources[i] = new StreamSource(new ByteArrayInputStream(out.toByteArray()),"");
}
SchemaFactory sf = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
m.setSchema(sf.newSchema(sources));
m.marshal(docs, new DefaultHandler());  // performs the schema validation
seanf
Thanks, I have also changed my solution to byteArrayInputStream but I wasn't aware about multiple schema
ekeren