Как мога да модифицирам JAXB маршалинг изходния поток, за да включва произволен вграден XML?

Бих искал да модифицирам изходния поток за JAXB маршалинг операция, за да включва някакъв произволен XML. Ето един пример за изясняване на ситуацията.

Имам произволен Product домейн обект с JAXB анотации, които в момента изглеждат така:

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

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

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

}

Което обикновено се включва в това:

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

Сега, какво ще стане, ако полето extraXml съдържа някакъв допълнителен XML (с произволна сложност), който трябва да бъде включен в линия с крайния сортиран резултат?

Да речем, extraXml съдържаше "<abc><def>Something extra</def></abc>", наистина бих искал решение, което да ми позволи да маршалирам Product по този начин (форматирането е по избор):

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

Разгледах този свързан въпрос, но не не дава напълно резултата, който преследвам, тъй като изглежда по-скоро насочен към цялостна промяна на формата, отколкото към вмъкване на DOM.

Свойството extraXml е само за илюстрация, може да бъде маркирано като @XmlTransient или в отделен специализиран клас. Единственият критерий е, че по някакъв начин може да получи String, съдържащо напълно произволно XML съдържание за добавяне към индивидуалния Product маршален изход.

Трябва също така да спомена, че потребителите на изхода от това могат да анализират произволното съдържание по начин, който им е подходящ. Целта тук е да се опрости обработката от страната на сървъра.

Благодаря предварително за всяка помощ, която можете да предложите.


person Gary Rowe    schedule 19.11.2010    source източник


Отговори (2)


Можете да представите допълнителния XML като DOM възел вместо низ.

Променете свойството extraXML да бъде org.w3c.dom.Node вместо String и го анотирайте с @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;

}

Тогава, когато umarshal XML документ като:

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

Със следния код:

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

„Допълнителният XML“ ще бъде запазен като DOM възел.

person bdoughan    schedule 19.11.2010
comment
Звучи обещаващо - бихте ли разяснили малко? - person Gary Rowe; 19.11.2010
comment
@Gary, актуализирах отговора си, за да обясня как това може да се направи с помощта на @XmlAnyElement. - person bdoughan; 19.11.2010
comment
+1 за основната ви актуализация - това е страхотна помощ (и поздравления за прекъсването на 3K днес). Има още една последна част, ако нямате нищо против. В началото на маршалинга имам само extraXml като низ и екземпляр на Product, създаден от new Product(). Има ли начин да се посочи възелът по начин, който JAXB е щастлив да маршалира? - person Gary Rowe; 19.11.2010
comment
Всъщност успях да поставя последното парче от пъзела на място. Пълно уважение към вас, че ме накарахте 99,9% от пътя. Добавих отговор за други. - person Gary Rowe; 19.11.2010

С помощта на Blaise Doughan тук е окончателното решение, на което се спрях. Това включва допълнителен демонстрационен код, който да помогне на други, които може да се окажат в тази ситуация.

Класовете

ProductList (нов клас за обвиване, за да покаже как работи това за множество записи на продукти)

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

}

Продукт (с модификациите на Blaise)

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;

}

Основна (някакъв демонстрационен код)

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

  }
}

Крайният резултат:

<?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>

Резултат!

Но не работи в JBoss...

Ако опитате горното в JBoss 4.2.3.GA или 4.3, може да откриете, че получавате

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

съобщава се изключение. Това (вероятно) се дължи на xercesImpl.jar в папките JBoss lib и /lib/endorsed, използващи възможността за отмяна на Java META-INF/Services, за да се предотврати маршалирането на JDK с помощта на вътрешни класове. Може да се наложи да посочите алтернатива DocumentBuilderFactory директно, като използвате следния подход:

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

Реализацията на Oracle изглежда облекчава тези проблеми, може би защото поддържа осведоменост за класовете на DOM Node в контекста на JAXB. Тези класове се намират в xdb.jar и xmlparserv2.jar, доставени с клиента на Oracle.

Дано помогне.

person Gary Rowe    schedule 19.11.2010