2011-05-24 16 views
11

entiendo todo sobre cómo utilizar XMLAdapters a convert unmappable types, o simplemente para cambiar cómo ciertos objetos son serializados/deserializado a XML. Todo funciona bien si utilizo anotaciones (ya sea a nivel de paquete o de otro modo). El problema es que estoy intentando cambiar las representaciones de objetos de terceros a los que no puedo cambiar el código fuente (es decir, para insertar anotaciones).JAXB adaptadores XML funcionan a través de anotaciones, pero no a través de setAdapter

Eso no debería ser un problema, teniendo en cuenta que el objeto Marshaller tiene un método para manually adding adapters. Desafortunadamente, no importa lo que haga, no puedo configurar los adaptadores de esta manera para que entren. Por ejemplo, tengo una clase que representa un punto en el espacio XYZ (coordenadas geocéntricas). En el XML que produzco, quiero que se convierta en latitud/longitud/altitud (coordenadas geodésicas). Aquí están mis clases:

Geocéntrica

package testJaxb; 

import javax.xml.bind.annotation.XmlAttribute; 
import javax.xml.bind.annotation.XmlRootElement; 

@XmlRootElement 
public class GeocentricCoordinate { 
    // Units are in meters; see http://en.wikipedia.org/wiki/Geocentric_coordinates 
    private double x; 
    private double y; 
    private double z; 

    @XmlAttribute 
    public double getX() { 
     return x; 
    } 
    public void setX(double x) { 
     this.x = x; 
    } 
    @XmlAttribute 
    public double getY() { 
     return y; 
    } 
    public void setY(double y) { 
     this.y = y; 
    } 
    @XmlAttribute 
    public double getZ() { 
     return z; 
    } 
    public void setZ(double z) { 
     this.z = z; 
    } 
} 

geodésicos

package testJaxb; 
import javax.xml.bind.annotation.XmlAttribute; 
import javax.xml.bind.annotation.XmlRootElement; 
/** 
* @see http://en.wikipedia.org/wiki/Geodetic_system 
*/ 
@XmlRootElement 
public class GeodeticCoordinate { 

    private double latitude; 
    private double longitude; 
    // Meters 
    private double altitude; 

    public GeodeticCoordinate() { 
     this(0,0,0); 
    } 

    public GeodeticCoordinate(double latitude, double longitude, double altitude) { 
     super(); 
     this.latitude = latitude; 
     this.longitude = longitude; 
     this.altitude = altitude; 
    } 

    @XmlAttribute 
    public double getLatitude() { 
     return latitude; 
    } 
    public void setLatitude(double latitude) { 
     this.latitude = latitude; 
    } 

    @XmlAttribute 
    public double getLongitude() { 
     return longitude; 
    } 

    public void setLongitude(double longitude) { 
     this.longitude = longitude; 
    } 

    @XmlAttribute 
    public double getAltitude() { 
     return altitude; 
    } 
    public void setAltitude(double altitude) { 
     this.altitude = altitude; 
    } 



} 

GeocentricToGeodeticLocationAdapter

package testJaxb; 
import javax.xml.bind.JAXBContext; 
import javax.xml.bind.JAXBException; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.annotation.adapters.XmlAdapter; 


/** 
* One of our systems uses xyz coordinates to represent locations. Consumers of our XML would much 
* prefer lat/lon/altitude. This handles converting between xyz and lat lon alt. 
* 
* @author ndunn 
* 
*/ 
public class GeocentricToGeodeticLocationAdapter extends XmlAdapter<GeodeticCoordinate,GeocentricCoordinate> { 

    @Override 
    public GeodeticCoordinate marshal(GeocentricCoordinate arg0) throws Exception { 
     // TODO: do a real coordinate transformation 
     GeodeticCoordinate coordinate = new GeodeticCoordinate(); 
     coordinate.setLatitude(45); 
     coordinate.setLongitude(45); 
     coordinate.setAltitude(1000); 
     return coordinate; 
    } 

    @Override 
    public GeocentricCoordinate unmarshal(GeodeticCoordinate arg0) throws Exception { 
     // TODO do a real coordinate transformation 
     GeocentricCoordinate gcc = new GeocentricCoordinate(); 
     gcc.setX(100); 
     gcc.setY(200); 
     gcc.setZ(300); 
     return gcc; 
    } 
} 

campo ObjectWithLocation

package testJaxb; 
import javax.xml.bind.JAXBContext; 
import javax.xml.bind.JAXBException; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.annotation.XmlRootElement; 

@XmlRootElement 
public class ObjectWithLocation { 

    private GeocentricCoordinate location = new GeocentricCoordinate(); 

    public GeocentricCoordinate getLocation() { 
     return location; 
    } 

    public void setLocation(GeocentricCoordinate location) { 
     this.location = location; 
    } 


    public static void main(String[] args) { 

     ObjectWithLocation object = new ObjectWithLocation(); 

     try { 
      JAXBContext context = JAXBContext.newInstance(ObjectWithLocation.class, GeodeticCoordinate.class, GeocentricCoordinate.class); 
      Marshaller marshaller = context.createMarshaller(); 

      marshaller.setAdapter(new GeocentricToGeodeticLocationAdapter()); 
      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 

      marshaller.marshal(object, System.out); 

     } 
     catch (JAXBException jaxb) { 
      jaxb.printStackTrace(); 
     } 
    } 
} 

Salida:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<objectWithLocation> 
    <location z="0.0" y="0.0" x="0.0"/> 
</objectWithLocation> 

Mediante el uso de una anotación (en mi archivo package-info.java):

@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters 
({ 
@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value=GeocentricToGeodeticLocationAdapter.class,type=GeocentricCoordinate.class), 
}) 

package package testJaxb; 

consigo el código XML siguiente (deseada)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<objectWithLocation> 
    <location longitude="45.0" latitude="45.0" altitude="1000.0"/> 
</objectWithLocation> 

Así que mi pregunta es doble.

  1. ¿Por qué funciona el adaptador anotado, pero no cuando se establece explícitamente a través del método setAdapter?
  2. ¿Cómo puedo evitar este problema cuando tengo clases que no puedo anotar y cuyo package-info.java no puedo modificar para agregar las anotaciones?

Respuesta

8

El setAdapter(XmlAdapter) en Marshaller se utiliza para pasar en un XmlAdapter inicializado para una propiedad que ya está anotado con @XmlJavaTypeAdapter.El siguiente enlace es a una respuesta en la que aprovecho este comportamiento:

Si desea asignar clases de terceros que puede utilizar EclipseLink JAXB (MOXy) 's archivo de asignación XML (yo soy el plomo moxy):

+0

Ah bien. Eso es molesto. Ahora veo que no leí el contrato de addAdapter lo suficientemente bien. ¿Hay alguna razón para no exponer un método 'void addAdapter (Class classToAdapt, XMLAdapter )'? – I82Much

+3

@ I82Much - JAXB podría mejorarse para ofrecer ese comportamiento. La razón por la que actualmente no es compatible con esto se debe principalmente al rendimiento. JAXBContext puede inicializar sus metadatos en el momento de la creación y usar los mismos metadatos para cada operación de marshal/unmarshal. Si se permitiera la introducción de adaptadores en el nivel de Marshaller/Unmarshaller, una implementación de JAXB necesitaría tener en cuenta la posibilidad de tales cambios en los metadatos. –

+0

Aceptar respuesta muy razonable. Estoy molesto porque es muy fácil hacer este tipo de personalización en XStream y estoy encontrando muchos más obstáculos para pasar con JAXB. Voy a verificar MOXy. Gracias por la ayuda. – I82Much

2

siempre hay que anotar con @XmlJavaTypeAdapter(...).

marshaller.setAdapter(...) significa asignar una instancia inicializada personalizada de su adaptador de tipo en caso de que tenga una inicialización no predeterminada del constructor.

De lo contrario, si solo tiene un constructor predeterminado para su adaptador, entonces no necesita llamar explícitamente al método .setAdapter(...).

Aquí está una gran respuesta con una explicación más detallada: JAXB: Isn't it possible to use an XmlAdapter without @XmlJavaTypeAdapter?

JAXB tiempo de ejecución sólo puede aceptar adaptadores sin-args constructor .. (Obviamente JAXBContext no sabe nada de modelo específico de la aplicación)

Así que por suerte hay una opción: D

Puede decirle a su unmarshaller que use una instancia dada de UserAdapter en lugar de instalarla por sí mismo.

public class Test { 
public static void main(String... args) { 
    JAXBContext context = JAXBContext.getInstance(Event.class); 
    Unmarshaller unmarshaller = context.createUnmarshaller(); 

     UserContext userContext = null; // fetch it from some where 
     unmarshaller.setAdapter(UserAdapter.class, new UserAdapter(userContext)); 

     Event event = (Event) unmarshaller.unmarshal(..); 
    } 
} 

setAdapter método está disponible tanto en Marshaller & Unmarshaller

Nota: setAdapter en marshaller/unmarshaller no quiere decir que usted no tiene que utilizar @XmlJavaTypeAdapter.

Cuestiones relacionadas