How to control JAXB in-memory schema generation ordering/sequence?

五迷三道 提交于 2019-12-19 10:28:21

问题


I've got 3 xsd files that depend on each other to build my element definitions. Each xsd file has its own namespace. When I generate my classes using JAXB xjc, I get 3 corresponding packages. So far so good.

My problem comes when I want to do schema validation with the unmarshaller. In order to avoid having to read in the xsd files, I generate the schemas on the fly from the class in question being unmarshalled. However, since the class depends on objects from 2 other packages, it is unable to generate the schemas unless I specify all 3 packages. Already, that isn't a very practical solution, as it requires me to know ahead of time the object hierarchy/dependency tree, and specify the package list accordingly.

My bigger problem comes when I try to create a new schema from the 3 generated schemas using the SchemaFactory (SchemaFactory.newSchema(Source[])). Apparently, the order in which the schemas are provided to the schema factory is critical for it to resolve dependencies. If the first schema in the array depends on a type definition from the last element in the array, I get a resolve error:

org.xml.sax.SAXParseException: src-resolve: Cannot resolve the name 'ns1:InCalculationDataType' to a(n) 'type definition' component.

If I modify the order, and put the 3rd schema first, it succeeds without error.

This makes it nearly impossible to write a method fairly generic, but rather have to code for each XSD case individually.

Is there anything I can do to alleviate this problem? Is there some way to force the SchemaFactory to read everything first and only then generate its errors if it finds any? I know you can create an ErrorHandler, however the JavaDocs indicate that if it throws a Fatal error, any further processing is unreliable.

EDIT

Just for my own peace of mind, I tried to create an error handler which ignored non-fatal errors (just logged them), however the generated schema was unreliable and was unable to properly validate xml errors. Consequently, it had no value to me.

END EDIT

Any suggestions or thoughts would be appreciated.

Thanks!

Eric


回答1:


After much searching, I finally found the answer. Hopefully this will help someone else. There are already other threads on StackOverflow relating to this issue, but without knowing the proper keywords, I wasn't finding the responses.

The solution is to use an LSResourceResolver for the schema factory. ie:

schemaFactory.setResourceResolver(new LSResourceResolver(){})

where LSResourceResolver() is responsible for returning the include/import resource that is required by the XSD.

Searching for LSResourceResolver in SO found a few useful threads: https://stackoverflow.com/a/3830649/827480, https://stackoverflow.com/a/2342859/827480

I will try to post my own solution later when I have a little more time, but it closely follows what was already suggested in the two above links (mine is a little more simplified by using Strings instead of streams...).

EDIT

As promised, here is the snippet of code I ended up with:

        // get the schemas used by this class
        final Map<String, String> schemas = new HashMap<String,String>();
        schemas.putAll(generateSchemas(jc));

        List<StreamSource> sources = new ArrayList<StreamSource>();
        for( String schema : schemas.values() )
            sources.add( new StreamSource( new ByteArrayInputStream(schema.getBytes())));

        SchemaFactory sf = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
        sf.setResourceResolver(new LSResourceResolver() {
            @Override
            public LSInput resolveResource(String type, final String namespaceURI, String publicId, String systemId, String baseURI){
                logger.debug( "Need to resolve Resource: " + namespaceURI );
                return new LSInput(){
                    @Override
                    public String getStringData() {
                        // return the schema if found
                        if( schemas.containsKey(namespaceURI)){
                            if( logger.isTraceEnabled())
                                logger.trace("resourceResolver: Resolving schema for namespace: " + namespaceURI + schemas.get(namespaceURI) );
                            return schemas.get(namespaceURI);
                        }
                        else
                            return null;
                    }
                    @Override
                    public Reader getCharacterStream() {
                        return null;
                    }
                    @Override
                    public void setCharacterStream(Reader paramReader) {
                    }
                    @Override
                    public InputStream getByteStream() {
                        return null;
                    }
                    @Override
                    public void setByteStream(InputStream paramInputStream) {
                    }
                    @Override
                    public void setStringData(String paramString) {
                    }
                    @Override
                    public String getSystemId() {
                        return null;
                    }
                    @Override
                    public void setSystemId(String paramString) {
                    }
                    @Override
                    public String getPublicId() {
                        return null;
                    }
                    @Override
                    public void setPublicId(String paramString) {
                    }
                    @Override
                    public String getBaseURI() {
                        return null;
                    }
                    @Override
                    public void setBaseURI(String paramString) {
                    }
                    @Override
                    public String getEncoding() {
                        return null;
                    }
                    @Override
                    public void setEncoding(String paramString) {
                    }
                    @Override
                    public boolean getCertifiedText() {
                        return false;
                    }
                    @Override
                    public void setCertifiedText(boolean paramBoolean) {
                    }
                };
            }
        });

        // validate the schema
        u.setSchema(sf.newSchema(sources.toArray(new StreamSource[]{})));

and method generateSchemas(jc):

private Map<String, String> generateSchemas (JAXBContext jaxbContext) throws JAXBException{
    // generate the schemas
    final Map<String, ByteArrayOutputStream> schemaStreams = new LinkedHashMap<String,ByteArrayOutputStream>();

    try {
        jaxbContext.generateSchema(new SchemaOutputResolver(){
            @Override
            public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException {
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                logger.debug( "GenerateSchemas: adding namespace: " + namespaceUri);
                schemaStreams.put(namespaceUri, out);
                StreamResult streamResult = new StreamResult(out);
                streamResult.setSystemId("");
                return streamResult;
            }});
    } catch (IOException e) {
        // no IO being performed.  Can safely ignore any IO exception.
    }

    // convert to a list of string
    Map<String,String> schemas = new LinkedHashMap<String,String>();
    for( Map.Entry<String, ByteArrayOutputStream> entry : schemaStreams.entrySet() ){
        String schema = entry.getValue().toString();
        String namespace = entry.getKey();
        schemas.put(namespace, schema);
    }

    // done
    return schemas;
}

END EDIT

I hope this can help someone else in the future.

Thanks,

Eric



来源:https://stackoverflow.com/questions/8842047/how-to-control-jaxb-in-memory-schema-generation-ordering-sequence

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!