UP | HOME

JAXB - Les adaptateurs

Table of Contents

Par défaut, la norme JAXB n'autorise la traduction que vers une classe : il n'est pas possible de traduire vers une interface. La raison est simple : il n'est pas possible de construire un objet d'une interface sans connaître une classe d'implémentation. Il est cependant possible d'utiliser un adaptateur pour permettre la traduction d'un document vers un type interface. Autre limitation dans l'autre sens, il n'est pas possible de traduire une classe non annotée en un document. Une première solution est d'utiliser la classe enveloppe JAXBElement. Une seconde solution est d'utiliser ici aussi un adpatateur.

Avec JAX-RS, on utilise des adaptateurs pour les traductions non seulement entre les objets et les documents, mais aussi entre les objets et les chemins ou requêtes composant les URI des ressources. Le problème est analogue au précédent et requiert de recourir aux adaptateurs.

Utilisation des adpatateurs

Dans les cas décrits ci-dessus, en l'absence d'adaptateur, il se produit une erreur.

Jersey (implémentation de référence)

  • Pas d'adaptateur pour le contenu d'une requête
    • Erreur 415 : type de média non supporté
    • Erreur 500 : erreur interne
  • Pas d'adaptateur pour un chemin ou une requête composant une URI
    • Erreur 404 : pas de ressource

CXF (une autre implémentation de JAX-RS)

  • Pas d'adaptateur pour le contenu d'une requête
    • No message body writer/reader has been found for response/request class/type X
  • Pas d'adaptateur pour la requête d'un chemin
    • Parameter Class X has no constructor with single String parameter, static valueOf(String) or fromString(String) methods

Pour supprimer ces erreurs, il est nécessaire d'annoter X et de fournir un adaptateur.

  • @XmlRootElement : annotation nécessaire en Jersey, mais plutôt superfétatoire
  • @XmlJavaTypeAdapter(A.class), où A est un adaptateur d'une classe héritant de XmlAdapter (cf. paquet javax.xml.bind.annotation.adapters).

Distinguons les deux cas d'usage.

Liaison objet-document (data binding).

Hypothèses

Soient I une interface, A et B deux classes implémentant I telles que A n'est pas traduisible (marshallable) alors que B l'est. On suppose que l'adaptateur implémente deux méthodes :

  • B marshall(I x),
  • I unmarshall(B x),

B implémente I et est traduisible.

Problème 1 : marshalling (envoi)

Un problème survient lorsqu'un A doit être traduit en un document.

Solution 1

On annote I par un adaptateur paramétré par I et B. La méthode B marshall(I x) est appelée, ce qui permet de transformer le A en un B traduisible.

Problème 2 : unmarshalling (réception)

A la réception d'un document à utiliser pour construire un I, aucune classe d'implémentation n'est connue par le data binder.

Solution 2

On annote I par un adaptateur paramétré par I et B. La classe B utilisée par l'adaptateur sert à la traduction inverse (unmarshalling), ce qui produit un B, puis la méthode I unmarshall(B x) est appelée pour le convertir en un I : comme dans notre cas B est un sous-type de I, la méthode correspond à une simple conversion implicite.

Liaison objet-chemin ou requête d'une URI

Hypothèses

Soient I une interface, A et B deux classes implémentant I telles que A ne peut pas être construit à partir d'une String alors que B peut l'être. On suppose que l'adapteur implémente deux méthodes :

  • B marshall(I x),
  • I unmarshall(B x),

B implémente I et peut être construit à partir d'une String (B possède un constructeur B(String), ou une fonction B valueOf(String), ou une fonction B fromString(String)).

Remarque : si l'interface I possède une fonction I valueOf(String) ou I fromString(String), un adaptateur est inutile.

Problème 1 : traduction d'un objet en un chemin

Lors de la traduction d'un objet de type I en un chemin, c'est la méthode toString() qui est appelée. Il n'y a donc jamais de problème, qu'on utilise un A ou un B.

Solution 1

Rien à faire.

Problème 2 : traduction d'un chemin en un objet

Un problème survient lors de la traduction inverse, d'un chemin vers objet de type I : on ne sait pas construire un I à partir d'une String.

Solution 2

On annote I par un adaptateur paramétré par I et B. La classe B utilisée par l'adaptateur sert à la construction d'un objet B, en utilisant le constructeur ou les fonctions prenant une String en argument. La méthode I unmarshall(B x) est ensuite appelée pour produire un I (par conversion implicite dans notre cas).

Implémentation des adaptateurs

Pour implémenter un adaptateur, on hérite de la classe générique XmlAdapter<T, NT>, où T est le type traduisible et NT celui non traduisible. On implémente deux méthodes, marshall et unmarshall, suivant le modèle suivant.

public class Adaptateur extends
  javax.xml.bind.annotation.adapters.XmlAdapter<T, NT> {

    @Override
    public T marshal(NT a) throws Exception {
      // Construction d'un objet traduisible b à partir de a 
      ...
      return b;
    }
    @Override
    public NT unmarshal(T b) throws Exception {
      // Construction d'un objet non traduisible a à partir de b 
      ...
      return a;
    }
}

Très souvent, le type T correspond à une classe d'implémentation de l'interface NT. Dans ce cas, les deux méthodes se simplifient souvent ainsi.

public class Adaptateur extends
  javax.xml.bind.annotation.adapters.XmlAdapter<T, NT> {

    @Override
    public T marshal(NT a) throws Exception {
      T b = new T();
      b.setX(a.getX()); // pour chaque propriété X à traduire
      return b;
    }
    @Override
    public NT unmarshal(T b) throws Exception {
      return b; // conversion implicite
    }
}

Author: Hervé Grall
Version history: v1: 2015-10-30; v2: 2016-04-08[*text]; v3: 2016-10-25[update].
Comments or questions: Send a mail.
The webpage content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.