2009-05-03 7 views
31

J'ai plusieurs structures de données complexes commeJAXB: Comment rassembler des structures de données imbriquées complexes?

Map< A, Set<B> > 
Set< Map< A, B > > 
Set< Map< A, Set<B> > > 
Map< A, Map< B, Set<C> > > 
and so on (more complex data structures) 

Note: Dans mon cas, il n'a pas vraiment d'importance si j'utiliser ensemble ou une liste.

Maintenant, je sais que JAXB permettez-moi de définir « s XMLAdapter, c'est très bien, mais je ne veux pas définir un XMLAdapter pour toutes les structures de données fournies (il serait juste trop d'auteur et-coller le code).

J'ai essayé d'atteindre mon objectif en déclarant deux XmlAdapters généralisantes:

  • une pour Carte: MapAdapter<K,V>
  • un pour Set: SetAdapter<V>

Le problème:
se plaint JAXB comme suit:

javax.xml.bind.JAXBException: 
class java.util.Collections$UnmodifiableMap nor any of its 
    super class is known to this context. 

Voici ma classe d'adaptateur:

import java.util.*; 
import javax.xml.bind.annotation.*; 
import javax.xml.bind.annotation.adapters.*; 

public class Adapters { 

public final static class MapAdapter<K, V> 
     extends XmlAdapter<MapAdapter.Adapter<K, V>, Map<K, V>> { 

    @XmlType 
    @XmlRootElement 
    public final static class Adapter<K, V> { 

     @XmlElement 
     protected List<MyEntry<K, V>> key = new LinkedList<MyEntry<K, V>>(); 

     private Adapter() { 
     } 

     public Adapter(Map<K, V> original) { 
      for (Map.Entry<K, V> entry : original.entrySet()) { 
       key.add(new MyEntry<K, V>(entry)); 
      } 
     } 

    } 

    @XmlType 
    @XmlRootElement 
    public final static class MyEntry<K, V> { 

     @XmlElement 
     protected K key; 

     @XmlElement 
     protected V value; 

     private MyEntry() { 
     } 

     public MyEntry(Map.Entry<K, V> original) { 
      key = original.getKey(); 
      value = original.getValue(); 
     } 

    } 

    @Override 
    public Adapter<K, V> marshal(Map<K, V> obj) { 
     return new Adapter<K, V>(obj); 
    } 

    @Override 
    public Map<K, V> unmarshal(Adapter<K, V> obj) { 
     throw new UnsupportedOperationException("unmarshalling is never performed"); 
    } 

} 

} 

Voici mon cas de test JUnit:

import java.io.*; 
import java.util.*; 
import javax.xml.bind.*; 
import javax.xml.bind.annotation.*; 
import javax.xml.bind.annotation.adapters.*; 
import org.junit.*; 
import static java.lang.System.*; 

public class SomeTest { 

@Test 
public void _map2() 
     throws Exception { 

    Map<String, Map<String, String>> dataStructure = 
      new HashMap<String, Map<String, String>>(); 

    Map<String, String> inner1 = new HashMap<String, String>(); 
    Map<String, String> inner2 = new HashMap<String, String>(); 

    dataStructure.put("a", inner1); 
    dataStructure.put("b", inner1); 

    inner1.put("a1", "1"); 
    inner1.put("a2", "2"); 
    inner2.put("b1", "1"); 
    inner2.put("b2", "2"); 

    JAXBContext context = JAXBContext.newInstance(Adapters.XMap.class, 
      Adapters.XCount.class, Adapters.XEntry.class); 

    Marshaller marshaller = context.createMarshaller(); 
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); 
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 

    marshaller.setAdapter(new Adapters.MapAdapter()); 

    StringWriter sw = new StringWriter(); 

    marshaller.marshal(dataStructure, sw); 
    out.println(sw.toString()); 
} 

} 

Répondre

20

J'ai résolu le problème sans XMLAdapter de.

J'ai écrit objets JAXB-annotés pour Carte, Map.Entry et Collection.
L'idée principale est à l'intérieur de la méthode xmlizeNestedStructure (...):

Jetez un oeil sur le code:

public final class Adapters { 

private Adapters() { 
} 

public static Class<?>[] getXmlClasses() { 
    return new Class<?>[]{ 
       XMap.class, XEntry.class, XCollection.class, XCount.class 
      }; 
} 

public static Object xmlizeNestedStructure(Object input) { 
    if (input instanceof Map<?, ?>) { 
     return xmlizeNestedMap((Map<?, ?>) input); 
    } 
    if (input instanceof Collection<?>) { 
     return xmlizeNestedCollection((Collection<?>) input); 
    } 

    return input; // non-special object, return as is 
} 

public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) { 
    XMap<Object, Object> ret = new XMap<Object, Object>(); 

    for (Map.Entry<?, ?> e : input.entrySet()) { 
     ret.add(xmlizeNestedStructure(e.getKey()), 
       xmlizeNestedStructure(e.getValue())); 
    } 

    return ret; 
} 

public static XCollection<?> xmlizeNestedCollection(Collection<?> input) { 
    XCollection<Object> ret = new XCollection<Object>(); 

    for (Object entry : input) { 
     ret.add(xmlizeNestedStructure(entry)); 
    } 

    return ret; 
} 

@XmlType 
@XmlRootElement 
public final static class XMap<K, V> { 

    @XmlElementWrapper(name = "map") 
    @XmlElement(name = "entry") 
    private List<XEntry<K, V>> list = new LinkedList<XEntry<K, V>>(); 

    public XMap() { 
    } 

    public void add(K key, V value) { 
     list.add(new XEntry<K, V>(key, value)); 
    } 

} 

@XmlType 
@XmlRootElement 
public final static class XEntry<K, V> { 

    @XmlElement 
    private K key; 

    @XmlElement 
    private V value; 

    private XEntry() { 
    } 

    public XEntry(K key, V value) { 
     this.key = key; 
     this.value = value; 
    } 

} 

@XmlType 
@XmlRootElement 
public final static class XCollection<V> { 

    @XmlElementWrapper(name = "list") 
    @XmlElement(name = "entry") 
    private List<V> list = new LinkedList<V>(); 

    public XCollection() { 
    } 

    public void add(V obj) { 
     list.add(obj); 
    } 

} 

} 

Il fonctionne!

Regardons une sortie de démonstration:

<xMap> 
    <map> 
     <entry> 
      <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <count>1</count> 
       <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a</content> 
      </key> 
      <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <list> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a1</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a2</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">a3</entry> 
       </list> 
      </value> 
     </entry> 
     <entry> 
      <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <count>2</count> 
       <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b</content> 
      </key> 
      <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <list> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b1</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b3</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">b2</entry> 
       </list> 
      </value> 
     </entry> 
     <entry> 
      <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <count>3</count> 
       <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c</content> 
      </key> 
      <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
       <list> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c1</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c2</entry> 
        <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">c3</entry> 
       </list> 
      </value> 
     </entry> 
    </map> 
</xMap> 

Désolé, la sortie de démonstration utilise également une structure de données appelée « count » qui ne figure pas dans le code source de l'adaptateur.

BTW: ne sait comment supprimer tous ces ennuyeux et (dans mon cas) inutile xsi: type attributs?

+0

Cool! Il se peut que je doive le faire moi-même un jour. Pourriez-vous également publier un résumé de ce que vous deviez faire dans votre fichier de liaison .xjb? –

+0

Je n'ai jamais utilisé le fichier de liaison .xjb;) La solution proposée fonctionne juste;) –

+1

Je devais ajouter '@XmlSeeAlso ({Adapters.XMap.class, Adapters.XCollection.class, Adapters.XEntry.class}) 'sur ma classe annotée JAXB pour que cela fonctionne. – ajitomatix

1

On dirait que vous êtes sur la bonne voie avec XMLAdapter ... le message d'erreur peut être un indice:

classe java.util.Collections $ UnmodifiableMap ni aucun de ses super classe est connu pour ce contexte.

es-tu en train d'encapsuler une carte en utilisant Collections.unmodifiableMap() n'importe où? Où exactement se produit l'erreur?


(réponse précédente a laissé comme un record rassis pour les curieux)

Vous pouvez créer placier personnalisée/logique unmarshaller qui fonctionne un peu plus straighforward que l'idée Adapters (je pense, je n » t utilisé celui-là avant). Fondamentalement, l'idée est que vous spécifiez une fonction statique pour faire le travail, et vous pouvez également créer une classe personnalisée. (Je place habituellement la fonction statique dans la classe en question, mais vous n'avez pas à le faire.) Ensuite, vous mettez une ligne dans votre fichier .XJB pour dire à JAXB d'utiliser votre fonction statique.

Maintenant que j'ai regardé mon code existant, je vois que tout ce que je faisais était de convertir une chaîne d'attributs en un objet Java personnalisé. Voici le code, pour référence, mais c'est juste pour les attributs.

fichier JAXB:

<?xml version="1.0" ?> 
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    jaxb:version="2.0"> 
    <jaxb:bindings schemaLocation={your schema} node="/xsd:schema"> 
     <jaxb:bindings node={some XPATH expression to select a node}> 
      <jaxb:bindings node={maybe another XPATH relative to the above}> 
       <jaxb:property> 
        <jaxb:baseType> 
         <jaxb:javaType name={your custom Java class} 
          parseMethod={your static method for unmarshaling} 
          printMethod={your static method for marshaling} 
          /> 
        </jaxb:baseType> 
       </jaxb:property> 
      </jaxb:bindings> 
     </jaxb:bindings> 
    </jaxb:bindings> 
</jaxb:bindings> 

(parseMethod et MéthodeImpression convertir à/de chaînes d'attributs)

+0

Merci pour votre réponse! Non, je n'emballe pas mes cartes partout, c'est étrange. Peut arriver à l'intérieur de JAXB. –

+0

avez-vous essayé le débogage, par ex. mettre des points d'arrêt sur votre code de marshaling/unmarshaling? –

+0

J'ai fini par écrire propres "wrappers", sans XmlAdapter, voir ma réponse. –

1

Voici mon marshaller/unmarshaller pour la liste des classes @XmlType.

par exemple

//Type to marshall 
@XmlType(name = "TimecardForm", propOrder = { 
"trackId", 
"formId" 
}) 
public class TimecardForm { 

    protected long trackId; 
    protected long formId; 
    ... 
} 

//a list holder 
@XmlRootElement 
public class ListHodler<T> { 
    @XmlElement 
    private List<T> value ; 

    public ListHodler() { 
    } 

    public ListHodler(List<T> value) { 
     this.value = value; 
    } 

    public List<T> getValue() { 
     if(value == null) 
      value = new ArrayList<T>(); 
     return this.value; 
    } 
} 

//marshall collection of T 
public static <T> void marshallXmlTypeCollection(List<T> value, 
     Class<T> clzz, OutputStream os) { 
    try { 
     ListHodler<T> holder = new ListHodler<T>(value); 
     JAXBContext context = JAXBContext.newInstance(clzz, 
       ListHodler.class); 
     Marshaller m = context.createMarshaller(); 
     m.setProperty("jaxb.formatted.output", true); 

     m.marshal(holder, os); 
    } catch (JAXBException e) { 
     e.printStackTrace(); 
    } 
} 

//unmarshall collection of T 
@SuppressWarnings("unchecked") 
public static <T> List<T> unmarshallXmlTypeCollection(Class<T> clzz, 
     InputStream input) { 
    try { 
     JAXBContext context = JAXBContext.newInstance(ListHodler.class, clzz); 
     Unmarshaller u = context.createUnmarshaller(); 

     ListHodler<T> holder = (ListHodler<T>) u.unmarshal(new StreamSource(input)); 

     return holder.getValue(); 
    } catch (JAXBException e) { 
     e.printStackTrace(); 
    } 

    return null; 
} 

2

Voici le code avec "dexmlize" capacité basée sur au-dessus du code d'Ivan. Utilisation:

Map<String, List> nameMapResult = (Map<String, List>) Adapters.dexmlizeNestedStructure(unmarshallResult); 

Afin de restaurer la classe de collection et de carte, un nouveau champ sera xmlized pour enregistrer les informations de classe. Code détaillé:

class Adapters { 
    private Adapters() { 
    } 
    public static Class<?>[] getXmlClasses() { 
      return new Class<?>[]{XMap.class, XEntry.class, XCollection.class}; 
    } 
    public static Object xmlizeNestedStructure(Object input) { 
      if (input instanceof Map<?, ?>) { 
        return xmlizeNestedMap((Map<?, ?>) input); 
      } 
      if (input instanceof Collection<?>) { 
        return xmlizeNestedCollection((Collection<?>) input); 
      } 
      return input; // non-special object, return as is 
    } 

    public static Object dexmlizeNestedStructure(Object input) { 
     if (input instanceof XMap<?, ?>) { 
       return dexmlizeNestedMap((XMap<?, ?>) input); 
     } 
     if (input instanceof XCollection<?>) { 
       return dexmlizeNestedCollection((XCollection<?>) input); 
     } 
     return input; // non-special object, return as is 
    } 

    private static Object dexmlizeNestedCollection(XCollection<?> input) 
    { 
     Class<? extends Collection> clazz = input.getClazz(); 
     Collection collection = null; 
     try 
     { 
      collection = clazz.newInstance(); 
      List dataList = input.getList(); 
      for (Object object : dataList) 
      { 
       collection.add(dexmlizeNestedStructure(object)); 
      } 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 
     return collection; 
    } 

    private static Object dexmlizeNestedMap(XMap<?, ?> input) 
    { 
     Class<? extends Map> clazz = input.getClazz(); 
     Map map = null; 
     try 
     { 
      map = clazz.newInstance(); 
      List<? extends XEntry> entryList = input.getList(); 
      for (XEntry xEntry : entryList) 
      { 
       Object key = dexmlizeNestedStructure(xEntry.getKey()); 
       Object value = dexmlizeNestedStructure(xEntry.getValue()); 
       map.put(key, value); 
      } 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 
     return map; 
    } 

    public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) { 
      XMap<Object, Object> ret = new XMap<Object, Object>(input.getClass()); 

      for (Map.Entry<?, ?> e : input.entrySet()) { 
        ret.add(xmlizeNestedStructure(e.getKey()), 
            xmlizeNestedStructure(e.getValue())); 
      } 
      return ret; 
    } 

    public static XCollection<?> xmlizeNestedCollection(Collection<?> input) { 
      XCollection<Object> ret = new XCollection<Object>(input.getClass()); 

      for (Object entry : input) { 
        ret.add(xmlizeNestedStructure(entry)); 
      } 
      return ret; 
    } 

    @XmlType 
    @XmlRootElement 
    public final static class XMap<K, V>{ 
      private List<XEntry<K, V>> list = new ArrayList<XEntry<K, V>>(); 
      private Class<? extends Map> clazz = null; 

      public XMap(Class mapClazz) { 
       this.clazz = (Class<? extends Map>)mapClazz; 
      } 

      public XMap() { 
      } 

      public void add(K key, V value) { 
        list.add(new XEntry<K, V>(key, value)); 
      } 

      @XmlElementWrapper(name = "map") 
      @XmlElement(name = "entry") 
      public List<XEntry<K, V>> getList() 
      { 
       return list; 
      } 

      public void setList(List<XEntry<K, V>> list) 
      { 
       this.list = list; 
      } 

      @XmlElement(name="clazz") 
      public Class<? extends Map> getClazz() 
      { 
       return clazz; 
      } 

      public void setClazz(Class<? extends Map> clazz) 
      { 
       this.clazz = clazz; 
      } 
    } 

    @XmlType 
    @XmlRootElement 
    public final static class XEntry<K, V> { 
      private K key; 
      private V value; 

      private XEntry() { 
      } 

      public XEntry(K key, V value) { 
        this.key = key; 
        this.value = value; 
      } 

      @XmlElement 
      public K getKey() 
      { 
       return key; 
      } 

      public void setKey(K key) 
      { 
       this.key = key; 
      } 

      @XmlElement 
      public V getValue() 
      { 
       return value; 
      } 

      public void setValue(V value) 
      { 
       this.value = value; 
      } 
    } 

    @XmlType 
    @XmlRootElement 
    public final static class XCollection<V> { 
      private List<V> list = new ArrayList<V>(); 
      private Class<? extends Collection> clazz = null; 

      public XCollection(Class collectionClazz) { 
       this.clazz = collectionClazz; 
      } 

      public XCollection() { 
      } 

      public void add(V obj) { 
        list.add(obj); 
      } 

      @XmlElementWrapper(name = "collection") 
      @XmlElement(name = "entry") 
      public List<V> getList() 
      { 
       return list; 
      } 

      public void setList(List<V> list) 
      { 
       this.list = list; 
      } 

      @XmlElement(name="clazz") 
      public Class<? extends Collection> getClazz() 
      { 
       return clazz; 
      } 


      public void setClazz(Class<? extends Collection> clazz) 
      { 
       this.clazz = clazz; 
      } 
    } 

} 
4

J'ai eu la même exigence d'utiliser une carte < String, Carte < chaîne, entier >>. J'ai utilisé le XMLAdapter et cela a bien fonctionné. Utiliser XMLAdaptor est la solution la plus propre je pense. Voici le code de l'adaptateur. Il s'agit de l'extrait de code de classe jaXb.

@XmlJavaTypeAdapter(MapAdapter.class) 
    Map<String, Map<String, Integer>> mapOfMap = new HashMap<String,Map<String, Integer>>(); 

MapType Classe:

public class MapType { 

public List<MapEntryType> host = new ArrayList<MapEntryType>(); 

} 

MapEntry Type de Classe:

public class MapEntryType { 

@XmlAttribute 
public String ip; 

@XmlElement 
public List<LinkCountMapType> request_limit = new ArrayList<LinkCountMapType>(); 

} 

LinkCountMapType Classe:

public class LinkCountMapType { 
@XmlAttribute 
public String service; 

@XmlValue 
public Integer count; 
} 

Enfin, la classe MapAdaptor:

public final class MapAdapter extends XmlAdapter<MapType, Map<String, Map<String, Integer>>> { 

@Override 
public Map<String, Map<String, Integer>> unmarshal(MapType v) throws Exception { 
    Map<String, Map<String, Integer>> mainMap = new HashMap<String, Map<String, Integer>>(); 

    List<MapEntryType> myMapEntryTypes = v.host; 
    for (MapEntryType myMapEntryType : myMapEntryTypes) { 
     Map<String, Integer> linkCountMap = new HashMap<String, Integer>(); 
     for (LinkCountMapType myLinkCountMapType : myMapEntryType.request_limit) { 
      linkCountMap.put(myLinkCountMapType.service, myLinkCountMapType.count); 
     } 
     mainMap.put(myMapEntryType.ip, linkCountMap); 
    } 
    return mainMap; 
} 

@Override 
public MapType marshal(Map<String, Map<String, Integer>> v) throws Exception { 
    MapType myMapType = new MapType(); 

    List<MapEntryType> entry = new ArrayList<MapEntryType>(); 

    for (String ip : v.keySet()) { 
     MapEntryType myMapEntryType = new MapEntryType(); 
     Map<String, Integer> linkCountMap = v.get(ip); 
     List<LinkCountMapType> linkCountList = new ArrayList<LinkCountMapType>(); 
     for (String link : linkCountMap.keySet()) { 
      LinkCountMapType myLinkCountMapType = new LinkCountMapType(); 
      Integer count = linkCountMap.get(link); 
      myLinkCountMapType.count = count; 
      myLinkCountMapType.service = link; 
      linkCountList.add(myLinkCountMapType); 
     } 
     myMapEntryType.ip = ip; 
     myMapEntryType.request_limit = linkCountList; 
     entry.add(myMapEntryType); 
    } 
    myMapType.host = entry; 
    return myMapType; 
} 

}

Rassemblant un objet JAXB donnera le code XML ci-dessous

 <mapOfmap> 
    <host ip="127.0.0.1"> 
     <request_limit service="service1">7</request_limit> 
     <request_limit service="service2">8</request_limit> 
    </host> 
</mapOfmap> 
1

Lorsque vous travaillez avec des structures de schéma complexes - la liaison JAXB peut être crucial dans la résolution des objets en conflit. L'outil de l'éditeur CAM sur Sourceforge vous permet de créer automatiquement des liaisons JAXB - consultez le guide rapide ici pour plus de détails - http://www.cameditor.org/#JAXB_Bindings

1

Pour résoudre ce problème pour JSON faire: jackson with jaxb

<init-param> 
     <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> 
     <param-value>true</param-value> 
    </init-param> 
+0

Veuillez être plus descriptif dans votre réponse. comment-répondre) – askmish

+0

Ce que vous écrivez n'a pas de sens, vous écrivez un mapping de resourse web.xml ..... C'est une question RMI et non une définition de servlet –

Questions connexes