views:

29

answers:

2

Using latest JAXB (Sun) and have a hierarchy of schemas that use import directives between schemas to share type definitions. Schema validation is activated on the setSchema call to Marshaller/Unmarshaller in JAXB which should defer validation to Xerces (using Java 1.5). I don't want to know the order of import directives between schemas when creating the Schema object with SchemaFactory. Unfortunately, I haven't found a Xerces feature/property that allows for this. For example, if a.xsd is pulled into b.xsd with an import then the following code doesn't work:

FileInputStream a = new FileInputStream("a.xsd");
FileInputStream b = new FileInputStream("b.xsd");

Schema schema = SchemaFactory.newInstance(
   XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
      new Source[] { 
          new StreamSource(b),
          new StreamSource(a) 
      }
   );

The order of the Source array has to be a.xsd then b.xsd. Any way around this?

+1  A: 

What if you create a schema on the root Source, and then set a ResourceResolver (LSResourceResolver) to resolve the other imported schemas during the schema creation.

Blaise Doughan
Interesting idea. May not always know the extent of the hierarchy of schemas. So I don't know all of the root sources (basically I am putting the grammars captured by the XJC process into a META-INF file, to catalog, plus the actual schema files into a jar and loading the SchemaFactory at runtime). Would need a way to map the namespaces of the root sources to their associated schema(s).Really no other way to steer Xerces to resolve the include after loading all of the schemas?
Matthew Young
That may still be possible, hopefully someone else will offer that answer. The approach I suggested allows the SchemaFactory to drive the order in which the schema gets resolved without you having to do the ordering. You specify the root schema, and it will ask for new ones as the imports are processed.
Blaise Doughan
Haven't coded the whole way just done a quick test but your idea should work perfect....easier with JRE 1.6 since there is more offered out of the box xerces implementations. Will post my code when I got a working example. Thanks for pointing me in the right direction.
Matthew Young
A: 

Late post of the code.

Generate a validation Schema with:

SchemaFactory factory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setResourceResolver(new SimpleResolver(streams));
....
Schema schemaGrammers = factory.newSchema(streams.toArray(new SchemaSource[0]));

The Schema (schemaGrammers object) gets injected into the Marshaller:

Marshaller m = ...createMarshaller();
m.setSchema(<schemaGrammers>);

And the SimpleResolver implements the LSResourceResolver class:

private class SimpleResolver implements LSResourceResolver {

    private Set<Source> streams;

    public SimpleResolver(Set<Source> streams) {
        this.streams = streams;
    }

    @Override
    public LSInput resolveResource(String type, String namespaceURI,
            String publicId, String systemId, String baseURI) {
        DOMImplementationRegistry registry;
        try {

            registry = DOMImplementationRegistry.newInstance();
            DOMImplementationLS domImplementationLS = (DOMImplementationLS) registry
                    .getDOMImplementation("LS 3.0");

            LSInput ret = domImplementationLS.createLSInput();

            for (Source source : streams) {
                SchemaSource schema = (SchemaSource) source;
                if (schema.getResourceName().equals(
                        schema.getResourceName(systemId))
                        & schema.getTargetNamespace().equals(namespaceURI)) {
                    logger.debug(
                            "Resolved systemid [{}] with namespace [{}]",
                            schema.getResourceName(systemId), namespaceURI);

                    URL url = new URL(schema.getSystemId());
                    URLConnection uc = url.openConnection();

                    ret.setByteStream(uc.getInputStream());
                    ret.setSystemId(systemId);
                    return ret;
                }
            }

        } catch (ClassCastException e) {
            logger.error(e.getMessage());
        } catch (ClassNotFoundException e) {
            logger.error(e.getMessage());
        } catch (InstantiationException e) {
            logger.error(e.getMessage());
        } catch (IllegalAccessException e) {
            logger.error(e.getMessage());
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage());
        } catch (IOException e) {
            logger.error(e.getMessage());
        }

        logger.error("No stream found for system id [{}]", systemId);
        return null;
    }

}

A new input stream has to be created otherwise a conflict occurs. Not sure why (didn't bother to debug the code) but the streams I pass to the constructor [ie. the Set object] have already been read.

Matthew Young