解组具有DTD相对路径的文档时的JAXB SAXParseException

时间:2010-08-27 18:46:32

标签: xml jaxb dtd

我有一个类从第三方源解组xml(我无法控制内容)。这是解组的片段:

JAXBContext jContext = JAXBContext.newInstance("com.optimumlightpath.it.aspenoss.xsd"); 
Unmarshaller unmarshaller = jContext.createUnmarshaller() ;
StringReader xmlStr = new StringReader(str.value);
Connections conns = (Connections) unmarshaller.unmarshal(xmlStr); 

Connections是使用xjc生成的类dtd-> xsd->类。包com.optimumlightpath.it.aspenoss.xsd包含所有这些类。

我收到的xml包含DOCTYPE中的相对路径。基本上str.value上面包含:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE Connections SYSTEM "./dtd/Connections.dtd">
<Connections>
...
</Connections>

这作为java 1.5应用程序成功运行。为了避免上述错误,我不得不在项目根目录下创建一个./dtd目录并包含所有dtd文件(不知道为什么我必须这样做但我们会这样做)。

我已经在Tomcat5.5上创建了一个使用上述类的Web服务。我在unmarshal线上得到[org.xml.sax.SAXParseException: Relative URI "./dtd/Connections.dtd"; can not be resolved without a document URI.]。我尝试在每个relavant文件夹(项目根目录,WebContent,WEB-INF,tomcat工作目录等)中创建./dtd无济于事。

问题#1:我在哪里可以找到./dtd,以便该类在作为tomcat webservice运行时可以找到它?我是否需要执行tomcat或service配置才能识别目录?

问题2:为什么该类甚至首先需要dtd文件?它是否具有在dtd-&gt; xsd-&gt;类的注释中解组所需的所有信息?我已经阅读了许多关于禁用验证,设置EntityResource和其他解决方案的帖子,但是这个类并不总是作为Web服务部署,我不希望有两个代码序列。

3 个答案:

答案 0 :(得分:8)

当从InputStream或Reader解组时,解析器不知道文档的systemId(uri / location),因此它无法解析相对路径。似乎解析器尝试使用当前工作目录来解析引用,该目录仅在从ide或命令行运行时才有效。为了覆盖这种行为并自行解决,你需要实现一个EntityResolver,正如Blaise Doughan所提到的那样。

经过一些实验,我找到了一种标准的方法。您需要从SAXSource解组,而XMLReader又由InputSourcepublic class Main { private static final String FEATURE_NAMESPACES = "http://xml.org/sax/features/namespaces"; private static final String FEATURE_NAMESPACE_PREFIXES = "http://xml.org/sax/features/namespace-prefixes"; public static void main(String[] args) throws JAXBException, IOException, SAXException { JAXBContext ctx = JAXBContext.newInstance(Root.class); Unmarshaller unmarshaller = ctx.createUnmarshaller(); XMLReader xmlreader = XMLReaderFactory.createXMLReader(); xmlreader.setFeature(FEATURE_NAMESPACES, true); xmlreader.setFeature(FEATURE_NAMESPACE_PREFIXES, true); xmlreader.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { // TODO: Check if systemId really references root.dtd return new InputSource(Root.class.getResourceAsStream("root.dtd")); } }); String xml = "<!DOCTYPE root SYSTEM './root.dtd'><root><element>test</element></root>"; InputSource input = new InputSource(new StringReader(xml)); Source source = new SAXSource(xmlreader, input); Root root = (Root)unmarshaller.unmarshal(source); System.out.println(root.getElement()); } } 构建。在此示例中,dtd位于带注释的类旁边,因此可以在类路径中找到。

<强> Main.java

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
    @XmlElement
    private String element;

    public String getElement() {
        return element;
    }

    public void setElement(String element) {
        this.element = element;
    }
}

<强> Root.java

<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT root (element)>
<!ELEMENT element (#PCDATA)>

<强> root.dtd

{{1}}

答案 1 :(得分:2)

  

问题2:为什么班级平均   首先需要dtd文件吗?

不是JAXB实现正在寻找DTD,它是底层解析器。

  

问题#1:我在哪里可以找到./dtd   这样班级就可以在跑步时找到它   作为tomcat webservice?

我不确定,但在下面我将展示一种方法,您可以使用MOXy JAXB实施(我是技术主管)在多种环境中工作。

提议的解决方案

创建一个从类路径加载DTD的EntityResolver。通过这种方式,您可以将DTD与应用程序打包在一起,无论部署环境如何,您都可以随时了解DTD。

public class DtdEntityResolver implements EntityResolver {

    public InputSource resolveEntity(String publicId, String systemId)
            throws SAXException, IOException {
        InputStream dtd = getClass().getClassLoader().getResourceAsStream("dtd/Connections.dtd");
        return new InputSource(dtd);
    }

}

然后使用MOXy JAXB实现,您可以转换为底层实现并设置EntityResolver。

import org.eclipse.persistence.jaxb.JAXBHelper;
...
JAXBContext jContext = JAXBContext.newInstance("com.optimumlightpath.it.aspenoss.xsd");
Unmarshaller unmarshaller = jContext.createUnmarshaller() ;
JAXBHelper.getUnmarshaller(unmarshaller).getXMLUnmarshaller().setEntityResolver(new DtdEntityResolver());
StringReader xmlStr = new StringReader(str.value);
Connections conns =(Connections) unmarshaller.unmarshal(xmlStr);

答案 2 :(得分:1)

以下是使用EntityResolver界面给出的答案的另一种变体。我的情况是将相对外部XML实体从一个XML文件解析到文件夹层次结构中的另一个XML文件。下面的构造函数的参数是XML“working”文件夹,而不是进程的工作目录。

public class FileEntityResolver implements EntityResolver {
    private static final URI USER_DIR = SystemUtils.getUserDir().toURI();

    private URI root;

    public FileEntityResolver(File root) {
        this.root = root.toURI();
    }

    @Override @SuppressWarnings("resource")
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        URI systemURI;
        try {
            systemURI = new URI(systemId);
        } catch (URISyntaxException e) {
            return null;
        }

        URI relative = USER_DIR.relativize(systemURI);
        URI resolved = root.resolve(relative);

        File f = new File(resolved);
        FileReader fr = new FileReader(f);
        // SAX will close the file reader for us
        return new InputSource(fr);
    }
}