2011-07-30 2 views
5

Je suis en train d'utiliser Moxy JAXB pour sérialiser une classe A qui ressemble à:XMLAdapter et XmlIDREF dans Moxy JAXB

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

Le point est que les objets Foo dans les champs « foos » sont plus nombreuses que les dans la carte fooBar. Par conséquent, je voudrais "lier" les éléments clés pour la carte "fooBar" aux éléments correspondants dans la liste "foos". Je l'ai essayé en utilisant les annotations XmlID et XmlIDREF:

@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... 
} 

Puis dans mon XMLAdapter j'ai essayé d'utiliser une annotation XmlIDREF sur les entrées de carte adaptées de l'objet foo:

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

Lorsque marshaling le code ci-dessus fonctionne comme prévu et produit le code XML suivant:

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

Pour les tests unmarshal, je suis en utilisant le test code suivant:

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

Ce code produit le (pour moi) résultat inattendu:

{[email protected]} 

C'est l'objet Foo utilisé comme clé dans la carte est nulle. Si je change l'adaptateur de carte et marsha l'objet Foo deux fois, au lieu d'utiliser une référence d'ID, je n'obtiens pas ce pointeur null.

J'ai été en mesure de trouver quelques messages à ce sujet sur google en utilisant le JAXB-RI, où le problème pourrait être résolu en écrivant un IDResolver comme décrit au http://weblogs.java.net/blog/2005/08/15/pluggable-ididref-handling-jaxb-20. Malheureusement, je n'ai pas été en mesure de trouver des informations sur une telle classe dans le MOXy JAXB JavaDoc.

Suggestion pour solution De réponse Blaise Doughan, je l'ai réalisé que ce bogue dans la mise en œuvre de Moxy JAXB. J'ai été capable de faire une solution de contournement (moche) pour ce bug. L'idée est que, au lieu d'utiliser un XMLAdapter, la carte est "convertie" dans sa classe de définition. La classe A ressemble maintenant:

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

Après la unmarshal, la méthode transferFromListToMap-doit maintenant être appelé. Ainsi, la ligne suivante doit être ajoutée immédiatement après la référence à nouveauA est obtenu:

newA.transferFromListToMap(); 

Toutes les suggestions pour une plus belle solution/correction d'un bug sera apprécié :).

+1

Je rencontre également un problème, peut-être lié, en marshaling une méthode \ @XmlIDREF, à un objet ayant une annotation \ @XmlJavaTypeAdapter sur sa classe. Ici, l'objet entier est marshalé au lieu de spécifier uniquement l'ID de l'objet. –

Répondre

1

Note: Je suis le EclipseLink JAXB (MOXy).

Je suis en mesure de confirmer la question que vous voyez:

Pourquoi la question est passe-t-

La question est due au traitement du XmlAdapter Moxy logique avant de traiter la logique @XmlIDREF. MOXy effectue un seul passage du document XML et les relations @XmlIDREF sont traitées à la fin pour s'assurer que tous les objets référencés ont été construits (car la référence peut précéder l'objet référencé, comme dans ce cas).

Je vais essayer de poster une solution de contournement à ce problème, et vous pouvez suivre nos progrès sur ce problème en utilisant le bogue ci-dessus.

+0

Merci pour la réponse. J'ai ajouté une suggestion pour une solution de contournement dans ma question. –

+2

J'ai aussi frappé ce bug. Y aura-t-il des progrès à l'avenir? Ou y a-t-il un autre moyen de résoudre ce problème? Sincèrement chrétien. –

+0

De même, l'instance configurée de mon XmlAdapter passée au marshaller est ignorée lors du marshaling des champs contenant une annotation @XmlIDREF. Cela fonctionnait parfaitement avec l'implémentation de Sun. – rochb

Questions connexes