web-dev-qa-db-fra.com

Utilisation de JAXB pour disséminer/marshaler une liste <String>

J'essaie de créer un serveur très simple REST. Je viens d'avoir une méthode de test qui renverra une liste de chaînes. Voici le code:


@GET
@Path("/test2")
public List test2(){
    List list=new Vector();
    list.add("a");
    list.add("b");
    return list;
}

Cela donne l'erreur suivante:

 SEVERE: Un écrivain de corps de message pour le type Java, 
 Classe Java.util.Vector et le type de média MIME, 
 Application/octet-stream, est introuvable 

J'espérais que JAXB avait un paramètre par défaut pour les types simples tels que String, Integer, etc. Je suppose que non. Voici ce que j'ai imaginé:


<Strings>
  <String>a</String>
  <String>b</String>
</Strings>

Quelle est la façon la plus simple de faire fonctionner cette méthode?

45
User1

J'ai utilisé l'exemple de @LiorH et l'ai développé pour:


@XmlRootElement(name="List")
public class JaxbList<T>{
    protected List<T> list;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.list=list;
    }

    @XmlElement(name="Item")
    public List<T> getList(){
        return list;
    }
}

Notez qu'il utilise des génériques afin que vous puissiez l'utiliser avec d'autres classes que String. Maintenant, le code de l'application est simplement:


    @GET
    @Path("/test2")
    public JaxbList test2(){
        List list=new Vector();
        list.add("a");
        list.add("b");
        return new JaxbList(list);
    }

Pourquoi cette classe simple n'existe-t-elle pas dans le package JAXB? Quelqu'un voit quelque chose comme ça ailleurs?

46
User1
@GET
@Path("/test2")
public Response test2(){
   List<String> list=new Vector<String>();
   list.add("a");
   list.add("b");

   final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
   return Response.ok().entity(entity).build();
}
31
Sample Code

Si quelqu'un d'entre vous souhaite écrire un wrapper de liste pour des listes contenant des éléments de plusieurs classes et souhaite attribuer un nom XmlElement individuel en fonction du type de classe sans les classes Writing X Wrapper, vous pouvez utiliser l'annotation @XmlMixed . nomme les éléments de la liste en fonction de la valeur définie par le @XmlRootElement. Ce faisant, vous devez spécifier quelles classes pourraient éventuellement figurer dans la liste en utilisant @XmlSeeAlso

Exemple:

Classes possibles dans la liste

@XmlRootElement(name="user")
public class User {/*...*/}

@XmlRootElement(name="entry")
public class LogEntry {/*...*/}

Classe d'emballage

@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{

    protected List<T> records;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.records=list;
    }

    @XmlMixed 
    public List<T> getRecords(){
        return records;
    }
}

Exemple:

List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));


XStream xStream = new XStream();
String result = xStream.toXML(l);

Résultat:

<records>
    <user>...</user>
    <entry>...</entry>
</records>

Alternativement, vous pouvez spécifier les noms XmlElement directement dans la classe wrapper en utilisant l'annotation @XmlElementRef

@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{

    protected List<T> records;

    public JaxbList(){}

    public JaxbList(List<T> list){
        this.records=list;
    }

    @XmlElementRefs({
        @XmlElementRef(name="item", type=Object.class),
        @XmlElementRef(name="user", type=User.class),
        @XmlElementRef(name="entry", type=LogEntry.class)
    })
    public List<T> getRecords(){
        return records;
    }
}
12
Zounadire

Depuis un blog personnel post , il n'est pas nécessaire de créer un objet JaxbList < T > spécifique.

En supposant un objet avec une liste de chaînes:

@XmlRootElement
public class ObjectWithList {

    private List<String> list;

    @XmlElementWrapper(name="MyList")
    @XmlElement
    public List<String> getList() {
        return list;
    }

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

}

Un aller-retour JAXB:

public static void simpleExample() throws JAXBException {

    List<String> l = new ArrayList<String>();
    l.add("Somewhere");
    l.add("This and that");
    l.add("Something");

    // Object with list
    ObjectWithList owl = new ObjectWithList();
    owl.setList(l);

    JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
    ObjectWithList retr = marshallUnmarshall(owl, jc);

    for (String s : retr.getList()) {
        System.out.println(s);
    } System.out.println(" ");

}

Produit ce qui suit:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
    <MyList>
        <list>Somewhere</list>
        <list>This and that</list>
        <list>Something</list>
    </MyList>
</objectWithList>
10

J'ai rencontré ce modèle plusieurs fois. Le moyen le plus simple consiste à définir une classe interne avec des annotations JaxB. (de toute façon, vous voudrez probablement définir le nom de la balise racine)

afin que votre code ressemble à quelque chose comme ça

@GET
@Path("/test2")
public Object test2(){
   MyResourceWrapper wrapper = new MyResourceWrapper();
   wrapper .add("a");
   wrapper .add("b");
   return wrapper ;
}

@XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
       @XmlElement(name="Item")
       List<String> list=new ArrayList<String>();
       MyResourceWrapper (){}

       public void add(String s){ list.add(s);}
 }

si vous travaillez avec javax.rs (jax-rs), je retournerais l'objet Response avec le wrapper défini comme son entité

8
LiorH

Enfin, je l'ai résolu en utilisant JacksonJaxbJsonProvider Il nécessite peu de changements dans votre Spring context.xml et Maven pom.xml

Dans votre context.xml de printemps, ajoutez JacksonJaxbJsonProvider au <jaxrs:server>:

<jaxrs:server id="restService" address="/resource">
    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
    </jaxrs:providers>
</jaxrs:server>

Dans votre Maven pom.xml, ajoutez:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.0</version>
</dependency>
3
petrsyn

L'exemple de User1 a bien fonctionné pour moi. Mais, en guise d'avertissement, cela ne fonctionnera qu'avec des types simples String/Integer, à moins que vous ajoutiez une annotation @XmlSeeAlso

@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
    protected List<MovieTicket> list;

Cela fonctionne bien, même si cela m'empêche d'utiliser une seule classe de liste générique dans l'ensemble de l'application. Cela pourrait également expliquer pourquoi cette classe apparemment évidente n'existe pas dans le package JAXB.

2
piepera

J'aurais gagné du temps si je trouvais fournisseur Resteasy Jackson plus tôt.

Ajoutez simplement le JAR Resteasy Jackson Provider. Pas de wrappers d'entité. Pas d'annotations XML. Aucun auteur de corps de message personnalisé.

0
mstrthealias

Si vous utilisez maven dans le projet jersey, ajoutez ci-dessous le fichier pom.xml et mettez à jour les dépendances du projet afin que Jaxb puisse détecter la classe de modèle et convertir la liste en XML d'application de type média:

<dependency>
    <groupId>com.Sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.2.11</version>
</dependency>
0
user7455210

Veillez à ajouter la balise @XmlSeeAlso avec vos classes spécifiques utilisées dans JaxbList. HttpMessageNotWritableException est très important.

0
Maggy

Pour une solution plus générale, pour la sérialisation JAXB-XML de toute liste de niveau supérieur ne nécessitant qu'une seule classe à écrire, consultez la solution proposée dans cette question:

Est-il possible de configurer par programme JAXB?

public class Wrapper<T> {

private List<T> items = new ArrayList<T>();

@XmlAnyElement(lax=true)
public List<T> getItems() {
    return items;
}

}

//JAXBContext is thread safe and so create it in constructor or 
//setter or wherever:
... 
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
... 

public String marshal(List<T> things, Class clazz) {

  //configure JAXB and marshaller     
  Marshaller m = jc.createMarshaller();
  m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

  //Create wrapper based on generic list of objects
  Wrapper<T> wrapper = new Wrapper<T>(things);
  JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);

  StringWriter result = new StringWriter();
  //marshal!
  m.marshal(wrapperJAXBElement, result);

  return result.toString();

}
0
Gonen I