2011-07-30 18 views
5

Estoy tratando de utilizar moxy JAXB para serializar una clase A que se parece a:XmlAdapter y XmlIDREF en jaxb moxy

@XmlAccessorType(XmlAccessType.NONE) 
@XmlRootElement 
public class A { 
    private Map<Foo, Bar> fooBar = new HashMap<Foo, Bar>(); 
    private Set<Foo> foos = new HashSet<Foo>(); 

    @XmlJavaTypeAdapter(FooBarMapAdapter.class) 
    public Map<Foo, Bar> getFooBar() { 
     return fooBar; 
    } 

    public void setFooBar(Map<Foo, Bar> fooBar) { 
     this.fooBar = fooBar; 
    } 

    @XmlElement 
    public Set<Foo> getFoos() { 
     return foos; 
    } 

    public void setFoos(Set<Foo> foos) { 
     this.foos = foos; 
    } 
} 

El punto es que el Foo objetos en el "Foos" campos son un superconjunto de las en el mapa fooBar. Por lo tanto, me gustaría "vincular" los elementos clave del mapa "fooBar" con los elementos correspondientes de la lista "foos". He intentado esto utilizando las XmlID y XmlIDREF anotaciones:

@XmlAccessorType(XmlAccessType.NONE) 
public class Foo { 
    private String xmlId; 

    @XmlID 
    @XmlAttribute 
    public String getXmlId() { 
     return xmlId; 
    } 

    public void setXmlId(String xmlId) { 
     this.xmlId = xmlId; 
    } 
} 


@XmlAccessorType(XmlAccessType.NONE) 
public class Bar { 
    // Some code... 
} 

Luego, en mi XmlAdapter he tratado de usar una anotación XmlIDREF el objeto foo las entradas del mapa adaptados:

public class FooBarMapAdapter extends 
     XmlAdapter<FooBarMapAdapter.FooBarMapType, Map<Foo, Bar>> { 
    public static class FooBarMapType { 
     public List<FooBarMapEntry> entries = new ArrayList<FooBarMapEntry>(); 
    } 

    @XmlAccessorType(XmlAccessType.NONE) 
    public static class FooBarMapEntry { 
     private Foo foo; 
     private Bar bar; 

     @XmlIDREF 
     @XmlAttribute 
     public Foo getFoo() { 
      return foo; 
     } 

     public void setFoo(Foo foo) { 
      this.foo = foo; 
     } 

     @XmlElement 
     public Bar getBar() { 
      return bar; 
     } 

     public void setBar(Bar bar) { 
      this.bar = bar; 
     } 
    } 

    @Override 
    public FooBarMapType marshal(Map<Foo, Bar> map) throws Exception { 
     FooBarMapType fbmt = new FooBarMapType(); 
     for (Map.Entry<Foo, Bar> e : map.entrySet()) { 
      FooBarMapEntry entry = new FooBarMapEntry(); 
      entry.setFoo(e.getKey()); 
      entry.setBar(e.getValue()); 
      fbmt.entries.add(entry); 
     } 
     return fbmt; 
    } 

    @Override 
    public Map<Foo, Bar> unmarshal(FooBarMapType fbmt) throws Exception { 
     Map<Foo, Bar> map = new HashMap<Foo, Bar>(); 
     for (FooBarMapEntry entry : fbmt.entries) { 
      map.put(entry.getFoo(), entry.getBar()); 
     } 
     return map; 
    } 
} 

Cuando el cálculo de referencias del código anterior funciona como se espera y produce el siguiente código XML:

<?xml version="1.0" encoding="UTF-8"?> 
<a> 
    <fooBar> 
     <entries foo="nr1"> 
     <bar/> 
     </entries> 
    </fooBar> 
    <foos xmlId="nr1"/> 
</a> 

Para unmarshal pruebas, estoy usando el ensayo siguiente código:

public class Test { 
    public static void main(String[] args) throws Exception { 
     A a = new A(); 

     Map<Foo, Bar> map = new HashMap<Foo, Bar>(); 
     Foo foo = new Foo(); 
     foo.setXmlId("nr1"); 
     Bar bar = new Bar(); 
     map.put(foo, bar); 
     a.setFooBar(map); 
     a.setFoos(map.keySet()); 

     final File file = new File("test.xml"); 
     if (!file.exists()) 
      file.createNewFile(); 
     FileOutputStream fos = new FileOutputStream(file); 

     JAXBContext jc = JAXBContext.newInstance(A.class); 
     Marshaller m = jc.createMarshaller(); 
     m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
     m.marshal(a, fos); 

     FileInputStream fis = new FileInputStream(file); 
     Unmarshaller um = jc.createUnmarshaller(); 
     A newA = (A) um.unmarshal(fis); 

     System.out.println(newA.getFooBar()); 
    } 
} 

Este código produce la (para mí) resultado inesperado:

{[email protected]} 

Es decir, el objeto de Foo utilizado como clave de la correlación es nula. Si cambio el adaptador de mapa y clasifico el objeto Foo dos veces, en lugar de usar una referencia de ID, no obtengo este puntero nulo.

He podido encontrar algunas publicaciones sobre esto en google utilizando el JAXB-RI, donde el problema podría resolverse escribiendo un IDResolver como se describe en http://weblogs.java.net/blog/2005/08/15/pluggable-ididref-handling-jaxb-20. Lamentablemente, no he podido encontrar ninguna información sobre dicha clase en MOXy JAXB JavaDoc.

Sugerencia para solución De respuesta Blaise Doughan, me he dado cuenta que este es un error en la implementación de moxy JAXB. He podido hacer una (fea) solución para este error. La idea es que, en lugar de usar un XMLAdapter, el mapa se "convierta" dentro de su clase de definición. La clase A ahora se ve así:

@XmlAccessorType(XmlAccessType.NONE) 
@XmlRootElement 
public class A { 
    private Map<Foo, Bar> fooBar = new HashMap<Foo, Bar>(); 
    private Set<Foo> foos = new HashSet<Foo>(); 

    // Due to a bug a XMLAdapter approch is not possible when using XmlIDREF. 
    // The map is mapped by the wrapper method getXmlableFooBarMap. 
    // @XmlJavaTypeAdapter(FooBarMapAdapter.class) 
    public Map<Foo, Bar> getFooBar() { 
     return fooBar; 
    } 

    public void setFooBar(Map<Foo, Bar> fooBar) { 
     this.fooBar = fooBar; 
    } 

    @XmlElement 
    public Set<Foo> getFoos() { 
     return foos; 
    } 

    public void setFoos(Set<Foo> foos) { 
     this.foos = foos; 
    } 

    // // WORKAROUND FOR JAXB BUG ///// 
    private List<FooBarMapEntry> mapEntries; 

    @XmlElement(name = "entry") 
    public List<FooBarMapEntry> getXmlableFooBarMap() { 
     this.mapEntries = new LinkedList<FooBarMapEntry>(); 
     if (getFooBar() == null) 
      return mapEntries; 

     for (Map.Entry<Foo, Bar> e : getFooBar().entrySet()) { 
      FooBarMapEntry entry = new FooBarMapEntry(); 
      entry.setFoo(e.getKey()); 
      entry.setBar(e.getValue()); 
      mapEntries.add(entry); 
     } 

     return mapEntries; 
    } 

    public void setXmlableFooBarMap(List<FooBarMapEntry> entries) { 
     this.mapEntries = entries; 
    } 

    public void transferFromListToMap() { 
     fooBar = new HashMap<Foo, Bar>(); 
     for (FooBarMapEntry entry : mapEntries) { 
      fooBar.put(entry.getFoo(), entry.getBar()); 
     } 
    } 
} 

Después de la unmarshal, la transferFromListToMap-método que ahora tiene que ser llamado. Así que la siguiente línea se debe añadir inmediatamente después de obtener la referencia a NEWA: serán apreciados

newA.transferFromListToMap(); 

Alguna sugerencia para una solución Solución/bug más agradable :).

+1

También estoy experimentando un problema, quizás relacionado, al ordenar un método \ @XmlIDREF, a un objeto que tenga una anotación \ @XmlJavaTypeAdapter en su clase. Aquí el objeto completo se clasifica en lugar de solo especificar el ID del objeto. –

Respuesta

1

Nota: Yo soy el EclipseLink JAXB (MOXy) principal.

he podido confirmar el problema que está viendo:

por qué el asunto se Pasando

El problema se debe a moxy procesar la XmlAdapter lógica antes de procesar la lógica @XmlIDREF. MOXy realiza una única pasada del documento XML y las relaciones @XmlIDREF se procesan al final para garantizar que todos los objetos a los que se hace referencia se hayan generado (ya que la referencia puede preceder al objeto al que se hace referencia, como en este caso).

Voy a tratar de publicar una solución a este problema, y ​​puede seguir nuestro progreso en este tema utilizando el error anterior.

+0

Gracias por la respuesta. He agregado una sugerencia para una solución en mi pregunta. –

+2

Pegué este error, también. ¿Habrá algún progreso en el futuro? ¿O hay alguna otra manera de cómo resolver esto? Sinceramente cristiano. –

+0

Lo mismo, la instancia configurada de mi XmlAdapter que se pasa al marshaller se ignora cuando se calculan los campos que contienen una anotación @XmlIDREF. Funcionó perfectamente con la implementación de Sun. – rochb

Cuestiones relacionadas