how can i unmarshall in jaxb and enjoy the schema validation without using an explicit schema file
Asked Answered
O

4

14

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?

Obcordate answered 8/4, 2010 at 22:6 Comment(4)
Why don't you read the schema from the classpath resource?Chon
I am not sure I understand, can you elaborate?Obcordate
I can load my schema file from my classpath but I am trying to avoid creating a schema file, as you can see above I am creating the file and using it after a second I can actually delete it after I finish marshaling . Am I missing some big picture here? Thanks for your helpObcordate
If the schema is in a String, you can do this to create a Schema object: Schema schema = schemaFactory.newSchema(new StringSource(sSchema));Punak
H
13

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
Hammons answered 23/4, 2010 at 6:54 Comment(2)
Thanks, I have also changed my solution to byteArrayInputStream but I wasn't aware about multiple schemaObcordate
@seanf. Your solution works well, however the sequence of generated Results does not match the order of dependencies. ie: outs[0] may be dependent on outs[1]. Consequently, when creating a newSchema(sources), it fails as it does not find the necessary dependencies defined prior to use. Is there a way to ensure that the order is the same as is required in the dependencies? (ie: outs[1] is generated before outs[0])? See my thread on this issue at https://mcmap.net/q/472879/-how-to-control-jaxb-in-memory-schema-generation-ordering-sequence/827480.Lierne
G
3

I had the exact issue and found a solution in the Apache Axis 2 source code:

protected List<DOMResult> generateJaxbSchemas(JAXBContext context) throws IOException {
    final List<DOMResult> results = new ArrayList<DOMResult>();
    context.generateSchema(new SchemaOutputResolver() {
        @Override
        public Result createOutput(String ns, String file) throws IOException {
            DOMResult result = new DOMResult();
            result.setSystemId(file);
            results.add(result);
            return result;
        }
     });
    return results;
}

and after you've acquired your list of DOMResults that represent the schemas, you will need to transform them into DOMSource objects before you can feed them into a schema generator. This second step might look something like this:

Unmarshaller u = myJAXBContext.createUnmarshaller();
List<DOMSource> dsList = new ArrayList<DOMSource>();   
for(DOMResult domresult : myDomList){
    dsList.add(new DOMSource(domresult.getNode()));
}
String schemaLang = "http://www.w3.org/2001/XMLSchema";
SchemaFactory sFactory = SchemaFactory.newInstance(schemaLang);
Schema schema = sFactory.newSchema((DOMSource[]) dsList.toArray(new DOMSource[0]));
u.setSchema(schema);            
Gorse answered 21/10, 2011 at 18:6 Comment(1)
Thanks for that. It worked well, and no spurious Schema files, streams etc. As to why we need to jump through these obscure hoops just to get JAXB to validate what it should do anyway -- because it's XML!Slifka
K
1

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(...);
Kazimir answered 9/4, 2010 at 0:2 Comment(8)
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?Obcordate
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.Kazimir
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.Obcordate
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.Kazimir
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.Obcordate
I am not aware of any way to do that, sorry.Kazimir
I found a solution, written as AnswerObcordate
There is a bug in JAXB RI, no validation event is thrown for wrong enums: #12147806 .Sciolism
E
-1

If you use maven using jaxb2-maven-plugin can help you. It generates schemas in generate-resources phase.

Erstwhile answered 5/2, 2014 at 13:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.