2012-01-10 12 views
8

L'obiettivo è quello di produrre il seguente codice XML con JAXBJAXB @XmlValue generico

<foo> 
    <bar>string data</bar> 
    <bar>binary data</bar> 
</foo> 

C'è una soluzione per consentire generici@XmlValue campi (ho bisogno di memorizzare e byte[]String dati)? Qui di seguito è ciò che desidero:

@XmlRootElement 
public class Foo { 
    private @XmlElement List<Bar> bars; 
} 

@XmlRootElement 
public class Bar<T> { 
    private @XmlValue T value; // (*) 
} 

Ma ottengo questa eccezione

(*) IllegalAnnotationException:
@ XmlAttribute/@ xmlvalue deve riferire ad un tipo Java che mappa al testo in XML.

risposta

8

Si potrebbe sfruttare un XmlAdapter per questo caso d'uso, invece di @XmlValue:

BarAdapter

package forum8807296; 

import javax.xml.bind.annotation.adapters.XmlAdapter; 

public class BarAdapter extends XmlAdapter<Object, Bar<?>> { 

    @Override 
    public Bar<?> unmarshal(Object v) throws Exception { 
     if(null == v) { 
      return null; 
     } 
     Bar<Object> bar = new Bar<Object>(); 
     bar.setValue(v); 
     return bar; 
    } 

    @Override 
    public Object marshal(Bar<?> v) throws Exception { 
     if(null == v) { 
      return null; 
     } 
     return v.getValue(); 
    } 

} 

Foo

Il XmlAdapter è associato con la proprietà bars utilizzando il @XmlJavaTypeAdapter della nota:

package forum8807296; 

import java.util.List; 
import javax.xml.bind.annotation.*; 
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; 

@XmlRootElement 
public class Foo { 
    private List<Bar> bars; 

    @XmlElement(name="bar") 
    @XmlJavaTypeAdapter(BarAdapter.class) 
    public List<Bar> getBars() { 
     return bars; 
    } 

    public void setBars(List<Bar> bars) { 
     this.bars = bars; 
    } 

} 

Bar

package forum8807296; 

public class Bar<T> { 
    private T value; 

    public T getValue() { 
     return value; 
    } 

    public void setValue(T value) { 
     this.value = value; 
    } 
} 

Demo

È possibile verificare questo esempio utilizzando il seguente demo cod e:

package forum8807296; 

import java.util.ArrayList; 
import java.util.List; 

import javax.xml.bind.JAXBContext; 
import javax.xml.bind.Marshaller; 

public class Demo { 

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

     Foo foo = new Foo(); 
     List<Bar> bars = new ArrayList<Bar>(); 
     foo.setBars(bars); 

     Bar<String> stringBar = new Bar<String>(); 
     stringBar.setValue("string data"); 
     bars.add(stringBar); 

     Bar<byte[]> binaryBar = new Bar<byte[]>(); 
     binaryBar.setValue("binary data".getBytes()); 
     bars.add(binaryBar); 

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

} 

uscita

Si noti come l'uscita comprende gli attributi xsi:type per preservare il tipo del valore. È possibile eliminare l'attributo della xsi:type avendo vostro XmlAdapter ritorno String invece di Object, se si esegue questa operazione è necessario gestire la conversione da String al tipo appropriato te per l'operazione unmarshal:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<foo> 
    <bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">string data</bars> 
    <bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:base64Binary">YmluYXJ5IGRhdGE=</bars> 
</foo> 
0

C'è un motivo per cui non si costruisce semplicemente una stringa con il byte []? Hai davvero bisogno di un generico?

+0

Ciò richiederebbe la conversione di dati opachi binari in una stringa, ad es., Devo codificarli manualmente in es. HexBinary o base64. Ma sì, questo è quello che sto attualmente usando come soluzione alternativa. –

+0

Stai utilizzando il tuo algoritmo di codifica? Dovrebbe essere abbastanza indolore se si utilizza il codificatore [apache commons] (http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html). –

+0

Sto usando [HexBinaryAdapter] (http://docs.oracle.com/javaee/5/api/javax/xml/bind/annotation/adapters/HexBinaryAdapter.html) che alt. commons classe base64, entrambe che sono belle one-liner. –

4

Non ho potuto ottenere @XmlValue lavorando come sempre ho ottenuto NullPointerException lungo la strada, non so perché. Invece, mi è venuto in mente qualcosa del genere.

cadere il vostro classe Bar del tutto, perché, come si vuole che sia in grado di contenere qualsiasi cosa si può semplicemente rappresentare con Object.

@XmlRootElement(name = "foo", namespace = "http://test.com") 
@XmlType(name = "Foo", namespace = "http://test.com") 
public class Foo { 

    @XmlElement(name = "bar") 
    public List<Object> bars = new ArrayList<>(); 

    public Foo() {} 
} 

Senza dirlo JAXB che i namespace tue tipi stanno usando ogni bar elemento all'interno di un foo conterrebbe dichiarazioni di namespace separati e roba-the package-info.java e tutta la roba namespace serve solo solo fancification scopi.

@XmlSchema(attributeFormDefault = XmlNsForm.QUALIFIED, 
      elementFormDefault = XmlNsForm.QUALIFIED, 
      namespace = "http://test.com", 
      xmlns = { 
       @XmlNs(namespaceURI = "http://test.com", prefix = ""), 
       @XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi"), 
       @XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema", prefix = "xs")}) 
package test; 

import javax.xml.bind.annotation.XmlNs; 
import javax.xml.bind.annotation.XmlNsForm; 
import javax.xml.bind.annotation.XmlSchema; 

L'esecuzione di questo semplice test genererebbe uno sdoganamento simile al proprio snippet XML.

public static void main(String[] args) throws JAXBException { 
    JAXBContext context = JAXBContext.newInstance(Foo.class); 

    Foo foo = new Foo(); 
    foo.bars.add("a"); 
    foo.bars.add("b".getBytes()); 

    Marshaller marshaller = context.createMarshaller(); 
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 
    marshaller.marshal(foo, System.out); 
} 

uscita:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<foo xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://test.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <bar xsi:type="xs:string">a</bar> 
    <bar xsi:type="xs:base64Binary">Yg==</bar> 
</foo> 
+1

+1 - Ecco un esempio di come potresti usare un 'XmlAdapter' per fare la stessa cosa, ma mantieni la classe' Bar': http://stackoverflow.com/a/8901997/383861 –

+0

@BlaiseDoughan Grazie per il una precisazione! –

0

Il trucco I' m in genere si usa per creare uno schema con i tipi che si desidera e quindi usare xjc per generare classi Java e vedere come vengono usate le annotazioni. :) Credo nel schema XML corretta mappatura tipo per byte [] è 'base64Binary', in modo da creare lo schema come questo:

<?xml version="1.0" encoding="UTF-8"?> 
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/NewXMLSchema" xmlns:tns="http://www.example.org/NewXMLSchema" elementFormDefault="qualified"> 
    <element name="aTest" type="base64Binary"></element> 
</schema> 

e funzionante xjc avremmo otteniamo seguente codice generato:

@XmlElementDecl(namespace = "http://www.example.org/NewXMLSchema", name = "aTest") 
public JAXBElement<byte[]> createATest(byte[] value) { 
    return new JAXBElement<byte[]>(_ATest_QNAME, byte[].class, null, ((byte[]) value)); 
}