2010-06-07 9 views
28

¿Es posible usar JAXB para unmarshall xml a una clase Java específica basada en un atributo del xml?Java/JAXB: Unmarshall Xml a una subclase específica basada en un atributo

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

me gustaría tener una lista de objetos Shape contienen un triángulo y un cuadrado, cada uno con su propio atributo de forma específica. 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 
} 

estoy actualmente sólo poner todos los atributos de una gran clase "Shape" y es menos que ideal.

Podría hacer que esto funcione si las formas se llamaban correctamente elementos xml, pero desafortunadamente no tengo control del xml que estoy recuperando.

Gracias!

Respuesta

17

JAXB es una especificación, las implementaciones específicas proporcionarán puntos de extensión para hacer cosas como esta. Si está utilizando EclipseLink JAXB (MOXy) podría modificar la clase Shape de la siguiente manera:

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

} 

Luego, utilizando el moxy @XMLCustomizer se podía acceder al InheritancePolicy y cambie el campo Indicador de la clase de "@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"); 
    } 
} 

usted tendrá que asegurarse de que tiene un archivo de jaxb.properties contigo clases del modelo (forma, cuadrado, etc.) con la siguiente entrada especifica la aplicación EclipseLink moxy JAXB:

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

A continuación se muestra el resto de las clases del modelo:

Formas

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

} 

Plaza

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

} 

Triángulo

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

} 

A continuación se muestra un programa de demostración para comprobar que todo funciona :

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

Espero que esto ayude.

Para obtener más información sobre EclipseLink moxy ver:

EDITAR

En EclipseLink 2.2 estamos haciendo esto más fácil de configurar, revisa el siguiente artículo para Más información:

+0

Definitivamente estoy interesado en esto como una solución, pero parece que no puedo hacer que funcione. ¿Importa que Shape no sea en realidad mi elemento raíz? Si trato de deshacerme de la colección JAXB/MOXy no parece molestarme con el personalizador y arroja un montón de errores sobre intentar crear una instancia de una clase abstracta. ¿Hay alguna otra pieza que me falta? – Frothy

+0

Creo que le falta el archivo jaxb.properties que especifica EclipseLink MOXy como la implementación de JAXB. He actualizado el ejemplo anterior para que sea más completo. –

+0

¡Eres increíble! Esto funciona perfectamente Gracias por ir más allá para ayudarme con esto. – Frothy

0

No, me temo que no es una opción, JAXB no es tan flexible.

Lo mejor que puedo sugerir es que pongas un método en la clase Shape que ejemplifica el tipo "correcto" en función del atributo. El código del cliente invocaría ese método de fábrica para obtenerlo.

Mejor que se me ocurra, lo siento.

+0

no lo había pensado de esta realidad. Ciertamente no es ideal, pero probablemente funcione. Realmente estaba esperando una forma de colocar un adaptador personalizado o algo así. ¡Gracias por la rápida respuesta! – Frothy

+0

@Frothy: JAXB es bastante limitado. Hay alternativas que dan más flexibilidad, como JiBX. – skaffman

+0

Nunca he oído hablar de JiBX, gracias, lo investigaré esta noche. – Frothy

3

AFAIK, tendrá que escribir un XmlAdapter que sepa cómo manejar el mariscal/desemparejamiento de la forma.

+0

'XmlAdapter' es para traducir valores de cadena individuales en un tipo específico, no sirve para traducir tipos complejos. – skaffman

+0

@Skaffman: XmlAdapter se puede usar fácilmente con tipos complejos. Uno de los casos de uso más comunes es para asignar java.util.Map a XML. –

7

La anotación @XmlElements permite especificar qué etiqueta corresponde a cada subclase.

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

Véase también Javadoc para @XmlElements

+0

no funcionaría (no @attribute based decision logic) –

-2

Hay @XmlSeeAlso anotación para decirle a unirse subclases.

Por ejemplo, con las siguientes definiciones de clase:

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

sería necesario que el usuario cree JAXBContext como JAXBContext.newInstance (Dog.class, Cat.class) (Animal será recogido automáticamente desde Dog gato y se refiere a ella)

XmlSeeAlso anotación permitiría a escribir:.

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

¿Cómo soluciona esto el problema? (mapeo de diferentes clases en función del atributo de elemento) –

+0

Tuve un problema muy similar y esto es lo que me faltaba, gracias – liang

Cuestiones relacionadas