2010-08-27 3 views
8

Ich habe eine Klasse, die xml von einer Drittanbieter-Quelle entfernt (ich habe keine Kontrolle über den Inhalt). Hier ist der Code-Schnipsel, die entpackt:JAXB SAXParseException beim Entpacken von Dokumenten mit relativem Pfad zur 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 eine Klasse erzeugt wird, DTD-> xsd-> Klasse mit xjc. Das Paket com.optimumlightpath.it.aspenoss.xsd enthält alle diese Klassen.

Das XML, das ich erhalte, enthält einen relativen Pfad im DOCTYPE. Grundsätzlich str.value oben enthält:

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

Dies läuft erfolgreich als Java 1.5 Anwendung. Um den obigen Fehler zu vermeiden, musste ich ein ./ddd-Verzeichnis vom Projektstamm erstellen und alle dtd-Dateien einschließen (ich bin mir nicht sicher, warum ich das tun musste, aber wir kommen dazu).

Ich habe seit einem Web-Service auf Tomcat5.5 erstellt, die die oben genannte Klasse verwendet. Ich bekomme [org.xml.sax.SAXParseException: Relative URI "./dtd/Connections.dtd"; can not be resolved without a document URI.] auf der Unmarshal-Linie. Ich habe versucht, ./dtd in jedem relavant-Ordner (Projektstamm, WebContent, WEB-INF, Tomcat-Arbeitsverzeichnis, usw.) ohne Erfolg zu erstellen.

Frage # 1: Wo kann ich ./dtd finden, so dass die Klasse es finden kann, wenn sie als tomcat webservice ausgeführt wird? Gibt es irgendeine Tomcat- oder Service-Konfiguration, die ich tun muss, um das Verzeichnis zu erkennen?

Frage 2: Warum braucht die Klasse überhaupt die dtd-Datei? Hat es nicht alle Informationen, die es braucht, um in den Annotationen der Klasse dtd-> xsd-> zu entpacken? Ich habe viele Artikel gelesen, in denen ich die Validierung deaktivieren, EntityResource und andere Lösungen einstellen möchte, aber diese Klasse wird nicht immer als Web-Service bereitgestellt und ich möchte nicht zwei Code-Züge haben.

Antwort

8

Wenn aus einem Inputstream oder Reader unmarshalling der Parser nicht die systemId (uri/Lage) des Dokuments nicht kennt, so ist es nicht relative Pfade auflösen kann. Es scheint, dass der Parser versucht, Verweise unter Verwendung des aktuellen Arbeitsverzeichnisses aufzulösen, was nur funktioniert, wenn es von der IDE- oder Befehlszeile ausgeführt wird. Um dieses Verhalten außer Kraft zu setzen und die Auflösung selbst vorzunehmen, müssen Sie eine EntityResolver implementieren, wie Blaise Doughan erwähnte.

Nach ein paar Experimenten habe ich einen Standard Weg gefunden, dies zu tun. Sie müssen von einer SAXSource entpacken, die wiederum aus einer XMLReader und einer aufgebaut ist. In diesem Beispiel befindet sich die dtd neben der annotierten Klasse und kann daher im Klassenpfad gefunden werden.

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 für Ihre Antwort. Ich versuche Blaises Vorschlag zuerst, da es die geringsten Codeänderungen erfordert. Aber Sie haben beide sehr hilfreiche Antworten gegeben. Gibt es eine Möglichkeit, die ich beiden zuschreiben kann? –

+0

Mein Ansatz wird funktionieren, aber der Vorteil von Jorns Ansatz ist, dass Sie JAXB-Implementierung agnostisch bleiben, die die tragbarste Lösung ist. –

2

Frage # 2: Warum braucht die Klasse selbst die dtd-Datei an erster Stelle?

Es ist nicht die JAXB-Implementierung, die nach der DTD sucht, es ist der zugrunde liegende Parser.

Frage 1: Wo kann ich orten ./dtd so dass die Klasse kann es finden, wenn als tomcat Webservice laufen?

Ich bin nicht sicher, aber unter ich einen Weg zeigen, werden Sie diese Arbeit machen mit der MOXy JAXB Implementierung (Ich bin der Tech-Leitung), die in mehreren Umgebungen arbeiten.

Lösungsvorschlag

eine EntityResolver erstellen, die die DTD aus dem Classpath lädt. Auf diese Weise können Sie die DTD mit Ihrer Anwendung verpacken, und Sie werden unabhängig von der Implementierungsumgebung immer wissen, wo sie sich befindet.

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

} 

Dann mit der moxy JAXB Implementierung Sie bis auf die zugrunde liegende Implementierung werfen kann und die EntityResolver gesetzt.

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 für das Nehmen der Zeit, um zu antworten. Anfänglich beschwerte sich JAXBHelper, dass der Unmarshaller kein Eclipselink-Unmarshaller sei. Also habe ich javax.xml.bind.JAXBContext durch org.eclipse.persistence.jaxb.JAXBContext und javax.xml.bind.Unmarshaller durch org.eclipse.persistence.jaxb.JAXBUnmarshaller ersetzt. Der eclipselink JAXBContext gibt jedoch einen javax JAXBContext-Typ zurück. JAXBUnmarshaller benötigt einen eclipselink-Typ und ich bekomme Ausnahmen, wenn ich versuche, ihn neu zu schreiben. Irgendwelche Ideen? –

+0

Sie müssen eine Datei namens jaxb.properties-Datei mit Ihren Modellklassen mit dem folgenden Eintrag hinzufügen: javax.xml.bind.context.factory = org.eclipse.persistence.jaxb.JAXBContextFactory –

+0

Ich kann das zu einem anderen Zeitpunkt versuchen. Ich konnte Jorns Antwort funktionieren lassen, obwohl es mehr Codeänderungen erfordern würde. Wie du sagst, ist es das tragbarste. Danke euch beiden sehr !! Wenn ich 15 Wiederholungen bekomme, kann ich auch deine Antwort abstimmen. –

0

Hier ist eine andere Variation der Antworten al Bereit wurde mit der EntityResolver Schnittstelle gegeben. Meine Situation bestand darin, relative externe XML-Entitäten von einer XML-Datei zu einer anderen in einer Ordnerhierarchie aufzulösen. Der Parameter für den Konstruktor unten ist der XML-Arbeitsordner, nicht das Arbeitsverzeichnis des Prozesses.

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