【问题标题】:JAXB SAXParseException when unmarshalling document with relative path to DTD使用 DTD 的相对路径解组文档时出现 JAXB SAXParseException
【发布时间】:2011-04-04 23:18:27
【问题描述】:

我有一个从 3rd 方源解组 xml 的类(我无法控制内容)。这是解组的 sn-p:

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->class 类。包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 服务。我在解组线上收到[org.xml.sax.SAXParseException: Relative URI "./dtd/Connections.dtd"; can not be resolved without a document URI.]。我尝试在每个相关文件夹(项目根目录、WebContent、WEB-INF、tomcat 工作目录等)中创建 ./dtd,但无济于事。

问题 #1:我在哪里可以找到 ./dtd 以便类在作为 tomcat Web 服务运行时可以找到它?为了让目录被识别,我需要做任何 tomcat 或服务配置吗?

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

【问题讨论】:

    标签: xml jaxb dtd


    【解决方案1】:

    问题 #2:为什么班级甚至 首先需要 dtd 文件吗?

    寻找 DTD 的不是 JAXB 实现,而是底层解析器。

    问题 #1:我在哪里可以找到 ./dtd 以便班级在运行时可以找到它 作为 tomcat 网络服务?

    我不确定,但下面我将演示一种方法,您可以使用MOXy JAXB 实现(我是技术负责人)来完成这项工作,该实现将在多种环境中工作。

    建议的解决方案

    创建一个从类路径加载 DTD 的 EntityResolver。通过这种方式,您可以将 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);
    

    【讨论】:

    • Blaise - 花时间回复。最初,JAXBHelper 抱怨解组器不是 eclipselink 解组器。所以我用 org.eclipse.persistence.jaxb.JAXBContext 和 javax.xml.bind.Unmarshaller 替换了 javax.xml.bind.JAXBContext 和 org.eclipse.persistence.jaxb.JAXBUnmarshaller。但是,eclipselink JAXBContext 返回一个 javax JAXBContext 类型。 JAXBUnmarshaller 需要 eclipselink 类型,如果我尝试重新转换,我会收到转换异常。有什么想法吗?
    • 您需要在模型类中添加一个名为 jaxb.properties 的文件,其中包含以下条目:javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory跨度>
    • 我可能会在其他时间尝试。尽管需要更多代码更改,但我能够使 Jorn 的答案起作用。正如你所说,它是最便携的。非常感谢两位!!当我得到 15 个代表时,我也可以投票赞成你的答案。
    【解决方案2】:

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

    经过一些试验,我找到了一种标准的方法。您需要从SAXSource 解组,而SAXSource 又由XMLReaderInputSource 构造而成。在本例中,dtd 位于带注释的类旁边,因此可以在类路径中找到。

    Main.java

    public 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());
        }
    }
    

    Root.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.dtd

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

    【讨论】:

    • Jorn - 期待您的回复。我首先尝试 Blaise 的建议,因为它需要最少的代码更改。但是你们俩都提供了非常有用的答案。有没有一种方法可以同时记入两者?
    • 我的方法会奏效,但 Jorn 方法的优势在于您可以与 JAXB 实现无关,这是最便携的解决方案。
    【解决方案3】:

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

    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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多