Как я могу изменить выходной поток сортировки 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>

Я просмотрел этот связанный вопрос, но он не t вполне дает результат, который мне нужен, поскольку он кажется более ориентированным на общее изменение формата, а не на вставку 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;

}

Затем, когда вы умаршалируете 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

С помощью Блейза Догана я нашел окончательное решение. Это включает в себя дополнительный демонстрационный код, чтобы помочь другим, которые могут оказаться в такой ситуации.

Классы

ProductList (новый класс-оболочка, показывающий, как это работает для нескольких записей Product)

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

}

Продукт (с изменениями Блейза)

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 в контексте JAXB. Эти классы находятся в файлах xdb.jar и xmlparserv2.jar, поставляемых с клиентом Oracle.

Надеюсь, поможет.

person Gary Rowe    schedule 19.11.2010