2010-06-07 6 views
28

È possibile utilizzare JAXB per unmarshall xml in una classe Java specifica basata su un attributo di xml?Java/JAXB: Unmarshall Xml a sottoclasse specifica basata su un attributo

<shapes> 
    <shape type="square" points="4" square-specific-attribute="foo" /> 
    <shape type="triangle" points="3" triangle-specific-attribute="bar" /> 
</shapes> 

desidero avere un elenco di oggetti Shape contenenti un triangolo e un quadrato, ciascuna con il proprio attributo specifico forma. IE:

abstract class Shape { 
    int points; 
    //...etc 
} 

class Square extends Shape { 
    String square-specific-attribute; 
    //...etc 
} 

class Triangle extends Shape { 
    String triangle-specific-attribute; 
    //...etc 
} 

Attualmente sto solo mettendo tutti gli attributi in un unico grande "Shape" di classe ed è tutt'altro che ideali.

Potrei farlo funzionare se le forme fossero denominate correttamente elementi xml, ma sfortunatamente non ho il controllo dell'xml che sto recuperando.

Grazie!

risposta

17

JAXB è una specifica, implementazioni specifiche forniranno punti di estensione per fare cose come questa. Se si utilizza EclipseLink JAXB (MOXy) si potrebbe modificare la classe Shape come segue:

import javax.xml.bind.annotation.XmlAttribute; 
import org.eclipse.persistence.oxm.annotations.XmlCustomizer; 

@XmlCustomizer(ShapeCustomizer.class) 
public abstract class Shape { 

    int points; 

    @XmlAttribute 
    public int getPoints() { 
     return points; 
    } 

    public void setPoints(int points) { 
     this.points = points; 
    } 

} 

Poi utilizzando il Moxy @XMLCustomizer si poteva accedere al InheritancePolicy e modificare il campo indica la classe da "@xsi: tipo" a "tipo" solo :

import org.eclipse.persistence.config.DescriptorCustomizer; 
import org.eclipse.persistence.descriptors.ClassDescriptor; 

public class ShapeCustomizer implements DescriptorCustomizer { 

    @Override 
    public void customize(ClassDescriptor descriptor) throws Exception { 
     descriptor.getInheritancePolicy().setClassIndicatorFieldName("@type"); 
    } 
} 

sarà necessario assicurarsi di avere un file di jaxb.properties con voi classi del modello (forma, Piazza, ecc) con la seguente voce specificando l'attuazione EclipseLink Moxy JAXB:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory 

Di seguito il resto delle classi del modello:

forme

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

import javax.xml.bind.annotation.XmlRootElement; 

@XmlRootElement 
public class Shapes { 

    private List<Shape> shape = new ArrayList<Shape>();; 

    public List<Shape> getShape() { 
     return shape; 
    } 

    public void setShape(List<Shape> shape) { 
     this.shape = shape; 
    } 

} 

Piazza

import javax.xml.bind.annotation.XmlAttribute; 

public class Square extends Shape { 
    private String squareSpecificAttribute; 

    @XmlAttribute(name="square-specific-attribute") 
    public String getSquareSpecificAttribute() { 
     return squareSpecificAttribute; 
    } 

    public void setSquareSpecificAttribute(String s) { 
     this.squareSpecificAttribute = s; 
    } 

} 

Triangolo

import javax.xml.bind.annotation.XmlAttribute; 

public class Triangle extends Shape { 
    private String triangleSpecificAttribute; 

    @XmlAttribute(name="triangle-specific-attribute") 
    public String getTriangleSpecificAttribute() { 
     return triangleSpecificAttribute; 
    } 

    public void setTriangleSpecificAttribute(String t) { 
     this.triangleSpecificAttribute = t; 
    } 

} 

Qui di seguito è un programma demo per verificare che tutto funzioni :

import java.io.StringReader; 
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 jaxbContext = JAXBContext.newInstance(Shapes.class, Triangle.class, Square.class); 

     StringReader xml = new StringReader("<shapes><shape square-specific-attribute='square stuff' type='square'><points>4</points></shape><shape triangle-specific-attribute='triangle stuff' type='triangle'><points>3</points></shape></shapes>"); 
     Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 
     Shapes root = (Shapes) unmarshaller.unmarshal(xml); 

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

Spero che questo aiuti.

Per ulteriori informazioni su EclipseLink Moxy vedere:

EDIT

In EclipseLink 2.2 stiamo rendendo questo più facile da configurare, controllare il seguente articolo per ulteriori informazioni:

+0

Sono decisamente interessato a questo come soluzione, ma non riesco a farlo funzionare. È importante che Shape non sia effettivamente il mio elemento principale? Se provo a rimuovere unmarshall, la raccolta JAXB/MOXy non sembra preoccuparsi di utilizzare il Customizer e genera un sacco di errori nel tentativo di creare un'istanza di una classe astratta. C'è un altro pezzo che mi manca? – Frothy

+0

Credo che manchi il file jaxb.properties che specifica EclipseLink MOXy come implementazione JAXB. Ho aggiornato l'esempio precedente per essere più completo. –

+0

Sei incredibile! Questo funziona perfettamente. Grazie per andare al di sopra e al di là di aiutarmi con questo. – Frothy

0

No, temo che non sia un'opzione, JAXB non è così flessibile.

Il meglio che posso suggerire è che si mette un metodo sulla classe Shape che istanzia il tipo "corretto" in base all'attributo. Il codice client invocerebbe quel metodo di fabbrica per ottenerlo.

Il meglio che posso venire, mi dispiace.

+0

non avevo pensato a questo in realtà. Sicuramente non è l'ideale, ma probabilmente funzionerà. Speravo davvero in qualche modo di lasciare un adattatore personalizzato o qualcosa del genere. Grazie per la risposta rapida! – Frothy

+0

@Frothy: JAXB è davvero piuttosto limitato. Ci sono alternative che danno più flessibilità, come JiBX. – skaffman

+0

Non ho mai sentito parlare di JiBX, grazie, lo esaminerò stasera. – Frothy

3

AFAIK, dovrete scrivere un XmlAdapter che sa come gestire il marshall/unmarshalling della forma.

+0

'XmlAdapter' serve per tradurre i singoli valori di stringa in un tipo specifico, non è utile per la traduzione di tipi complessi. – skaffman

+0

@Skaffman: XmlAdapter può essere facilmente utilizzato con tipi complessi. Uno dei casi di utilizzo più comuni è per la mappatura di java.util.Map in XML. –

7

L'annotazione @XmlElements consente di specificare quali tag corrisponde con la quale sottoclasse.

@XmlElements({ 
    @XmlElement(name="square", type=Square.class), 
    @XmlElement(name="triangle", type=Triangle.class) 
}) 
public List<Shape> getShape() { 
    return shape; 
} 

vedono anche javadoc per @XmlElements

+0

non funzionerebbe (nessuna logica di decisione basata su @attribute) –

-2

C'è @XmlSeeAlso annotazione per dire di legare sottoclassi.

Ad esempio, con le seguenti definizioni delle classi:

class Animal {} 
class Dog extends Animal {} 
class Cat extends Animal {} 

All'utente viene richiesto di creare JAXBContext come JAXBContext.newInstance (Dog.class, Cat.class) (Animale verrà automaticamente preso dal cane e Cat si riferisce ad essa)

XmlSeeAlso annotazione permetterebbe di scrivere:.

@XmlSeeAlso({Dog.class,Cat.class}) 
class Animal {} 
class Dog extends Animal {} 
class Cat extends Animal {} 
+1

In che modo questo risolve il problema? (mappando diverse classi in base all'attributo elemento) –

+0

Ho avuto un problema molto simile e questo è quello che mi mancava, grazie – liang

Problemi correlati