@EJB
ne permet pas d'injecter un EJB dans un service REST Jersey. Il y a un peu de code technique à ajouter, ce que nous allons faire.
@Path
, ou, à défaut, au moins une de ses méthodes doit l'être.
@PathParam
est l'annotation que nous avons vue dans notre premier exemple. Elle permet d'associer un morceau de l'URL de requête à un champ ou un paramètre.
@QueryParam
et
@FormParam
permettent d'associer un paramètre de la requête à un champ ou un paramètre d'une méthode de notre classe.
@CookieParam
permet d'associer une valeur stockée dans un
cookie
ou l'instance de
Cookie
elle-même.
@HeaderParam
permet d'associer une valeur d'un champ HTTP à un champ ou un paramètre d'une méthode de notre classe.
@MatrixParam
permet d'associer un paramètre HTTP matriciel à un champ ou un paramètre de méthode de notre classe. Le problème est que les URL portant des paramètres matriciels ne sont pas clairement supportées par les navigateurs et les serveurs web. Cette annotation est donc à utiliser avec précautions.
valueOf(String)
ou
fromString(String)
List<T>
,
Set<T>
ou
SortedSet<T>
, où
T
est un type qui satisfait l'une des conditions énoncées.
@DefaultValue
, qui permet de fixer la valeur par défaut de l'élément annoté.
@GET
,
@POST
,
@PUT
,
@DELETE
, et
@HEAD
. Elles correspondent aux cinq méthodes HTTP qui portent le même nom. On ne peut poser chaque annotation qu'une seule fois sur une unique méthode dans une classe donnée, pour un chemin d'accès donné.
Une telle méthode doit être
public
. Lorsqu'une telle requête arrive, la bonne méthode est invoquée, avec les paramètres annotés correctement positionnés.
Enfin, une méthode annotée peut aussi porter un paramètre non annoté, que l'on appelle le paramètre d'entité. Ce paramètre portera l'intégralité de la requête. On peut définir par ailleurs une classe annotée
@Provider
dont la responsabilité sera de convertir cette requête dans le type Java désiré.
Exemple 31. Utilisation de
Path
sur une classe et des méthodes
@Path("marin") public class MarinService { @GET @Path("id/{id}") @Produces("application/xml") public Object getMarin(@PathParam("id") long id) { // contenu de la méthode } @GET @Path("list") @Produces("application/xml") public Object getMarinsList() { // contenu de la méthode } }
marin/list
, censée nous retourner la liste de nos marins ;
marin/id/15
, censée nous retourner le marin dont l'ID est 15.
@Path
sont toujours relatifs. Cela rend le
/
en début de valeur inutile. L'annotation posée sur la classe définit un chemin relatif au chemin de l'application.
@Consumes
) et pour la réponse (
@Produces
). Ces déclarations sont facultatives.
On peut poser ces annotations sur une classe ou sur des méthodes. Dans le cas d'une double déclaration, c'est celle de la méthode qui prévaut. On peut également poser ces annotations sur des classes annotées
@Provider
, comme nous le verrons dans la suite.
MessageBodyReader
, qui permettent de créer des objets Java à partir du contenu d'une requête ;
MessageBodyWriter
, qui permettent de créer des réponses HTTP à partir d'objets Java.
javax.xml.bind.JAXBElement
. C'est ce
provider
qui est responsable de la génération du XML lorsqu'un objet annoté par JAXB est retourné dans une méthode REST.
MessageBodyReader
définit une première méthode,
isReadable()
. Cette méthode reçoit en paramètre toutes les informations sur le contenu de la requête, et le type d'objet Java que le moteur de service REST attend. Si cette implémentation est capable de traiter cette requête, alors
true
doit être retourné.
MessageBodyWriter
possède une méthode analogue :
isWriteable()
.
Ainsi, le moteur de service REST peut déterminer, parmi tous les
entity providers
qu'il connait, lequel est capable de traiter la conversion qu'il doit effectuer.
Ensuite, chacune de ces implémentations définit deux autres méthodes :
readFrom()
et
writeTo()
, appelées par le moteur de service REST. Ces méthodes prennent en paramètres tous les éléments nécessaires à leur travail. Notons que
MessageBodyWriter
définit une méthode en plus :
getSize()
. Cette méthode est appelée par le moteur de service REST avant
writeTo()
, de façon à insérer dans le code HTTP la bonne valeur du champ
length
.
@EJB
, qui n'est pas reconnue, et laisse à
null
les champs qu'elle annote.
Il est possible de créer un
provider
propriétaire dans Jersey pour pallier cet inconvénient. Ce
provider
est annoté de façon standard par
@Provider
, mais implémente une interface qui fait partie de Jersey, et non pas de JAX-RS :
InjectableProvider<T, U>
Cette interface définit deux méthodes, dont la seconde est appelée pour injecter une référence de l'EJB dans le champ annoté :
getInjectable(ComponentContext context, T t, V v)
.
Le deuxième paramètre de cette méthode est une référence sur l'annotation rencontrée par Jersey, où
v
représente la classe du champ annoté. On prend en général
Type
comme type pour
V
, dans la mesure où cette classe est la super classe de tous les types en Java.
Cette méthode est donc appelée par Jersey avec l'annotation et le champ annoté. Elle doit retourner une instance de
Injectable
, qui n'est qu'une enveloppe sur l'objet injecté. Voyons un exemple d'une telle classe.
Exemple 32. Service REST avec injection d'un EJB
@Path("marin/{id}") public class MarinXMLService { // injection d'un EJB : ne fonctionne pas sans un Provider ad hoc @EJB(mappedName="MarinService") private MarinService marinService ; @GET @Produces("text/xml") public Object getMarin(@PathParam("id") long id) { Marin marin = marinService.findMarinById(id) ; return marin ; } }
Exemple 33. Provider pour l'injection de l'EJB
// Ce provider supporte l'annotation @EJB uniquement @Provider public class EJBProvider implements InjectableProvider<EJB, Type> { // méthode technique qui indique à Jersey comment créer les // instances de cet objet public ComponentScope getScope() { return ComponentScope.Singleton; } // méthode appelée pour déterminer la valeur à injecter public Injectable getInjectable(ComponentContext context, EJB ejb, Type t) { // un EJB ne peut pas être un type primitif // si t n'est pas une classe, alors l'annotation // n'est pas posée correctement if (!(t instanceof Class)) return null; try { Class clazz = (Class)t ; // nous sommes dans un contexte JEE, donc pas besoin // de fichier jndi.properties Context initialContext = new InitialContext(); // la valeur par défaut du nom de notre EJB est le nom de sa classe String componentName = clazz.getName() ; // si l'annotation mappedName est présente, alors elle porte // le nom de l'EJB if (ejb.mappedName() != null) { componentName = ejb.mappedName() ; } // requête sur l'annuaire avec le nom de l'EJB final Object ejbInstance = initialContext.lookup(componentName); // on retourne enfin une instance d'Injectable, conformément à // l'interface InjectableProvider return new Injectable() { public Object getValue() { return ejbInstance ; } }; } catch (Exception e) { return null; } } }
@EJB
, mais l'organisation du code reste la même.
On peut imaginer d'autres types de
providers
, construits sur le même principe, pour supporter les annotations
@Resource
et
@PersistenceContext
par exemple.
@Path
@Produces
et
@Consumes
@Provider
MessageBodyReader
et
MessageBodyWriter