2010-11-19 6 views
5

Ich möchte den Ausgabestream für eine JAXB-Marshalling-Operation ändern, um einige beliebige XML einzuschließen. Hier ist ein Beispiel, um die Situation zu klären.Wie kann ich den JAXB-Marshalling-Ausgabestream ändern, um beliebiges Inline-XML einzuschließen?

Ich habe eine beliebige Product Domain-Objekt mit JAXB Annotationen, die derzeit wie folgt aussehen:

@XmlRootElement(name="Product") 
public class Product { 

    @XmlElement(name="CommonProperty") 
    private String commonProperty="Something"; 

    @XmlElement(name="ExtraXml") 
    private String extraXml="Something extra"; 

} 

die in der Regel in dieser Marschall würde:

<Product> 
    <CommonProperty>Something</CommonProperty> 
    <ExtraXml>Something else</ExtraXml> 
</Product> 

Nun, was ist, wenn das extraXml Feld einige enthalten zusätzliches XML (von beliebiger Komplexität), das inline mit dem endgültigen Marshall-Ergebnis enthalten sein sollte?

Say enthielt extraXml<abc><def>Something extra</def></abc>“, würde ich wirklich wie eine Lösung, die es mir ermöglicht, Product wie dieser Marschall (Formatierung optional):

<Product> 
    <CommonProperty>Something</CommonProperty> 
    <abc> 
    <def>Something extra</def> 
    </abc> 
</Product> 

Ich habe bei this related question aussehen, aber es hat nicht ganz ergibt das Ergebnis, das ich suche, da es eher auf eine Änderung des Gesamtformats als auf die DOM-Einfügung ausgerichtet ist.

Die Eigenschaft ist nur für die Illustration da, könnte es als @XmlTransient oder in eine einzige spezialisierte Klasse markiert werden. Das einzige Kriterium ist, dass es irgendwie einen String bekommen kann, der völlig willkürlichen XML-Inhalt zum Anhängen an die einzelne marshalled Ausgabe Product enthält.

Ich sollte auch erwähnen, dass die Verbraucher der Ausgabe von diesem in der Lage sind, den willkürlichen Inhalt in einer Weise zu analysieren, die ihnen entspricht. Der Zweck ist hier, die Verarbeitung auf der Serverseite zu vereinfachen.

Vielen Dank im Voraus für jede Hilfe, die Sie anbieten können.

Antwort

6

Sie können das zusätzliche XML als DOM-Knoten anstelle von String darstellen.

Ändern Sie die extraXML-Eigenschaft als org.w3c.dom.Node anstelle einer Zeichenfolge, und versehen Sie sie mit @XmlAnyElement.

import javax.xml.bind.annotation.XmlAccessType; 
import javax.xml.bind.annotation.XmlAccessorType; 
import javax.xml.bind.annotation.XmlAnyElement; 
import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlRootElement; 

import org.w3c.dom.Node; 

@XmlRootElement(name="Product") 
@XmlAccessorType(XmlAccessType.FIELD) 
public class Product { 

    @XmlElement(name="CommonProperty") 
    private String commonProperty="Something"; 

    @XmlAnyElement 
    private Node extraXml; 

} 

Dann, wenn Sie umarshal ein XML-Dokument wie:

<Product> 
    <CommonProperty>Something</CommonProperty> 
    <abc> 
    <def>Something extra</def> 
    </abc> 
</Product> 

Mit dem folgenden Code:

import java.io.File; 
import javax.xml.bind.JAXBContext; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.Unmarshaller; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     JAXBContext jc = JAXBContext.newInstance(Product.class); 

     Unmarshaller unmarshaller = jc.createUnmarshaller(); 
     File xml = new File("input.xml"); 
     Product product = (Product) unmarshaller.unmarshal(xml); 

     Marshaller marshaller = jc.createMarshaller(); 
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
     marshaller.marshal(product, System.out); 
    } 
} 

Die "extra XML" wird als DOM-Knoten gehalten werden.

+0

Hört sich vielversprechend an - könnten Sie etwas näher erläutern? –

+0

@Gary, Ich habe meine Antwort aktualisiert, um zu erklären, wie dies mit @XmlAnyElement durchgeführt werden kann. –

+1

+1 für Ihr großes Update - es ist eine große Hilfe (und herzlichen Glückwunsch zum 3K heute brechen). Es gibt ein letztes bisschen, wenn es Ihnen nichts ausmacht. Zu Beginn des Marshalling habe ich nur den 'extraXml' als String und eine Instanz von' Product', die von 'new Product()' erstellt wurde. Gibt es eine Möglichkeit, den Knoten in einer Weise zu spezifizieren, die JAXB gerne ausführt? –

5

Mit Blaise Doughan 's Hilfe hier ist die endgültige Lösung, die ich beschlossen habe. Dies beinhaltet zusätzlichen Demo-Code, um anderen zu helfen, die sich in dieser Situation befinden.

Die Klassen

Product (eine neue Verpackung Klasse zu zeigen, wie diese für mehrere Produkteinträge funktioniert)

import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlElementWrapper; 
import javax.xml.bind.annotation.XmlRootElement; 
import java.util.ArrayList; 
import java.util.List; 

@XmlRootElement(name="ProductList") 
public class ProductList { 

    @XmlElementWrapper(name="Products") 
    @XmlElement(name="Product") 
    public List<Product> products = new ArrayList<Product>(); 

} 

Produkt (mit Blaise Modifikationen)

import org.w3c.dom.Node; 

import javax.xml.bind.annotation.*; 

@XmlRootElement(name="Product") 
@XmlAccessorType(XmlAccessType.FIELD) 
public class Product { 

    @XmlElement(name="CommonProperty") 
    public String commonProperty="Something"; 

    @XmlAnyElement 
    public Node extraXml; 

} 

Haupt (einige Demonstration Code)

import org.w3c.dom.Document; 
import org.xml.sax.InputSource; 

import javax.xml.bind.JAXBContext; 
import javax.xml.bind.Marshaller; 
import javax.xml.parsers.DocumentBuilderFactory; 
import java.io.StringReader; 

public class Main { 

    public static void main(String[] args) throws Exception { 

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 

    // Build some arbitrary extra XML and prepare an InputStream 
    String fragment1 = "<abc><def>Some extra 1</def></abc>"; 
    String fragment2 = "<ghi><jkl>Some extra 2</jkl></ghi>"; 
    Document document1 = factory.newDocumentBuilder().parse(new InputSource(new StringReader(fragment1))); 
    Document document2 = factory.newDocumentBuilder().parse(new InputSource(new StringReader(fragment2))); 

    Product product1 = new Product(); 
    product1.commonProperty = "Hello 1"; 
    product1.extraXml=document1.getFirstChild(); 

    Product product2 = new Product(); 
    product2.commonProperty = "Hello 2"; 
    product2.extraXml=document2.getFirstChild(); 

    ProductList productList = new ProductList(); 
    productList.products.add(product1); 
    productList.products.add(product2); 

    JAXBContext jc = JAXBContext.newInstance(ProductList.class, Product.class); 
    Marshaller marshaller = jc.createMarshaller(); 
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
    marshaller.marshal(productList, System.out); 

    } 
} 

Die endgültige Ausgabe:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<ProductList> 
    <Products> 
     <Product> 
      <CommonProperty>Hello 1</CommonProperty> 
      <abc> 
       <def>Some extra 1</def> 
      </abc> 
     </Product> 
     <Product> 
      <CommonProperty>Hello 2</CommonProperty> 
      <ghi> 
       <jkl>Some extra 2</jkl> 
      </ghi> 
     </Product> 
    </Products> 
</ProductList> 

Ergebnis!

Aber es funktioniert nicht in JBoss ...

Wenn Sie versuchen, die oben in JBoss 4.2.3.GA oder 4.3 können Sie feststellen, dass Sie einen

class com.sun.org.apache.xerces.internal.dom.DocumentFragmentImpl nor any of its super class is known to this context. 

Ausnahme erhalten gemeldet werden. Dies liegt (wahrscheinlich) an den Ordnern xercesImpl.jar in den Ordnern JBoss lib und /lib/endorsed, die die Java-Überschreibungsfunktion Java META-INF/Services verwenden, um zu verhindern, dass das JDK mithilfe interner Klassen ein Marshalling durchführt. Sie können eine alternative DocumentBuilderFactory direkt mit dem folgenden Ansatz angeben müssen:

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance("oracle.xml.jaxp.JXDocumentBuilderFactory", this 
     .getClass().getClassLoader()); 

Die Oracle-Implementierung scheint diese Probleme zu lindern, vielleicht, weil es das Bewusstsein für den Zusammenhang DOM Knoten Klassen innerhalb des JAXB hält. Diese Klassen befinden sich in der mit dem Oracle-Client gelieferten Datei xdb.jar und xmlparserv2.jar.

Ich hoffe, es hilft.