2010-08-27 71 views
8

我有一個類從第三方來源解析xml(我無法控制內容)。這裏是解組的片段:JAXB SAXParseException當解組文檔具有相對DTD的路徑

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服務。我在編組線上獲得[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->類的註釋中解組的所有信息?我讀過許多關於禁用驗證,設置EntityResource和其他解決方案的文章,但是這個類並不總是作爲一個web服務部署,我不想有兩個代碼鏈。

回答

8

當從InputStream或Reader解析器不知道的systenId文檔(URI /位置),因此它不能解析相對路徑解組。看起來解析器試圖使用當前工作目錄來解析引用,它只在從ide或命令行運行時才起作用。爲了覆蓋這種行爲並解決你自己,你需要實現一個EntityResolver,就像Blaise Doughan所說的那樣。

經過一番嘗試,我發現了一個標準的做法。您需要從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)> 
+0

Jorn - ty你的迴應。我首先嚐試Blaise的建議,因爲它需要最少的代碼更改。但你們都提供了非常有用的答案。我有兩種方法可以稱讚嗎? – 2010-08-30 16:18:56

+0

我的方法可行,但Jorn方法的優點是可以保持JAXB實現不可知性,這是最便攜的解決方案。 – 2010-08-30 16:42:17

2

問題2:爲什麼班級甚至需要首先使用dtd文件?

它不是查找DTD的JAXB實現,而是底層解析器。

問題1:我在哪裏可以找到./dtd ,使類可以找到它運行 爲tomcat的web服務時?

我不知道,但我下面將演示一個方法,你可以使用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); 
+0

Blaise - ty花時間回覆。最初,JAXBHelper抱怨解組織者不是eclipselink unmarshaller。所以我用org.eclipse.persistence.jaxb.JAXBContext和javax.xml.bind.Unmarshaller替換了javax.xml.bind.JAXBContext和org.eclipse.persistence.jaxb.JAXBUnmarshaller。但是,eclipselink JAXBContext返回一個javax JAXBContext類型。 JAXBUnmarshaller需要eclipselink類型,如果我嘗試重新投射,則會收到投射異常。有任何想法嗎? – 2010-08-30 16:16:35

+0

您需要使用以下條目將名爲jaxb.properties文件的文件添加到您的模型類中:javax.xml.bind.context.factory = org.eclipse.persistence.jaxb.JAXBContextFactory – 2010-08-30 16:40:12

+0

我可能會在另一次嘗試。儘管需要更多的代碼更改,但我仍能使Jorn的答案正常工作。正如你所說,它是最便攜的。非常感謝你!當我獲得15位代表時,我也可以投票回答你的答案。 – 2010-08-30 16:48:40

0

這裏有答案人另一種變體已準備好使用接口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); 
    } 
}