我有3个相互依赖的xsd文件来构建我的元素定义。每个xsd文件都有自己的命名空间。当我使用JAXB xjc生成我的类时,我得到3个相应的包。到目前为止一切都很好。
当我想使用unmarshaller进行模式验证时,我的问题出现了。为了避免必须读入xsd文件,我会从有问题的类中动态生成模式。但是,由于该类依赖于来自其他2个包的对象,因此除非我指定所有3个包,否则它无法生成模式。这已经不是一个非常实用的解决方案,因为它要求我提前知道对象层次结构/依赖关系树,并相应地指定包列表。
当我尝试使用SchemaFactory(SchemaFactory.newSchema(Source []))从3个生成的模式创建新模式时,我遇到了更大的问题。显然,模式提供给模式工厂的顺序对于解析依赖关系至关重要。如果数组中的第一个模式依赖于数组中最后一个元素的类型定义,则会出现解决错误:
org.xml.sax.SAXParseException: src-resolve: Cannot resolve the name 'ns1:InCalculationDataType' to a(n) 'type definition' component.
如果我修改了订单,并将第3个架构放在首位,那么它会成功而不会出错。
这使得编写一个相当通用的方法几乎是不可能的,而是必须单独为每个XSD案例编写代码。
我能做些什么来缓解这个问题?是否有某种方法可以强制SchemaFactory首先读取所有内容,然后只有在找到任何内容时才会生成错误?我知道你可以创建一个ErrorHandler,但是JavaDocs表明如果它抛出致命错误,任何进一步的处理都是不可靠的。
修改
为了我自己的安心,我尝试创建一个错误处理程序,忽略非致命错误(只记录它们),但生成的模式不可靠,无法正确验证xml错误。因此,它对我没有任何价值。
结束编辑
任何建议或想法都将不胜感激。
谢谢!
埃里克
答案 0 :(得分:5)
经过多次搜索,我终于找到了答案。希望这会帮助别人。 StackOverflow上已经存在与此问题相关的其他线程,但在不知道正确的关键字的情况下,我没有找到响应。
解决方案是将LSResourceResolver用于架构工厂。即:
schemaFactory.setResourceResolver(new LSResourceResolver(){})
其中LSResourceResolver()负责返回XSD所需的包含/导入资源。
在SO中搜索LSResourceResolver找到了一些有用的线程:https://stackoverflow.com/a/3830649/827480,https://stackoverflow.com/a/2342859/827480
稍后当我有更多时间时,我会尝试发布我自己的解决方案,但它会严格遵循上面两个链接中已经建议的内容(通过使用字符串而不是流来更简化我的...)
修改强>
正如所承诺的,以下是我最终获得的代码片段:
// 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[]{})));
和方法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;
}
结束编辑
我希望将来可以帮助其他人。
谢谢,
埃里克