RESTful Web Services mit JAX-RS

Zu klären ist noch, wie die Anfragedaten aus dem Request verarbeitet werden. Hierfür sind die so genannten Entity-Provider der JAX-RS-Runtime verantwortlich, die die textuell übermittelten Anfragedaten in ein passendes Java-Objekt umwandeln. Bestimmte Transformationen unterstützt jede JAX-RS-Implementierung von Hause aus [6]. Bei Bedarf lassen sich eigene Entity-Provider ergänzen. Solche basieren beim Unmarshalling auf einer mit @Provider annotierten Klasse, die das Interface MessageBodyReader implementiert. In Verbindung mit XML muss der Entwickler lediglich die Java-Klasse des zu transferierenden Objekts mit JAXB-Annotationen auszeichnen. Den Rest erledigt die Laufzeitumgebung dank JAXB (Java Architecture for XML Binding) vollkommen automatisch. Im Fall der Methode create()erwartet z. B. die Runtime, dass das Buch in den Anfragedaten in XML-Form übermittelt wird. Die textuelle Buchdarstellung wird dann intern in ein Book-Objekt umgewandelt und jenes beim Methodenaufruf injiziert. Damit alles wie beschrieben funktioniert, ist die Klasse Book mit JAXB-Annotationen zu versehen.

In Bezug auf die Rückgabewerte gibt es verschiedene Optionen. Der Programmierer kann einerseits ein Response-Objekt zurückliefern und darin den Statuscode und die zu übermittelnden Antwortdaten festlegen. Im Fall der Methode create()gehen wir z. B. folgendermaßen vor (Listing 2): Nach dem Speichern des Buches ermitteln wir mithilfe des injizierten UriInfo-Objekts den URI zum neu angelegten Buch und liefern diesen zusätzlich zum HTTP-Statuscode 201 (Created) als Antwortdaten zurück.

Listing 2
public Response create(UriInfo uriInfo, Book book) {
  book = bookMgr.save(book);

  UriBuilder builder = uriInfo.getAbsolutePathBuilder();
  URI bookURI = builder.path(String.valueOf(book.getBookID())).build();

  return Response.created(bookURI).build();
}

Andererseits kann man eine Methode auch als void deklarieren. In diesem Fall obliegt es der JAX-RS-Implementierung, einen passenden Statuscode an den Nutzer zurückzuliefern. Das muss jene auch machen, wenn die Methode direkt ein Objekt zurückliefert, wie das z. B. bei read()der Fall ist. JAX-RS hat dann ebenfalls dafür zu sorgen, dass das Java-Objekt in ein vom Aufrufer akzeptiertes Format transformiert wird. Hierzu greift die Applikation erneut auf die Entity-Provider zurück. In Verbindung mit XML reichen wiederum JAXB-Annotationen aus. Den Rest erledigt die JAX-RS Runtime. Im Fall der Methode read()reduziert sich der Code dadurch z. B. wie folgt:

public Book read(long bookID) {
  return bookMgr.get(bookID);
}

Selbstverständlich kann eine Methode die zu liefernden Daten auch selbst in das vom Aufrufer gewünschte Format bringen und in Form einer Zeichenkette zurückliefern. Das machen wir z. B. bei der Methode getAll(), die in Listing 3 dargestellt ist. Wir bedienen uns dabei der frei verfügbaren Bibliothek JSON.simple [7]. Ganz dem objektorientierten Stil folgend, wurde der Klasse Book eine Methode toJSONObject()hinzugefügt.

Listing 3
public String getAll(int maxNumber) {
  Collection books = bookMgr.get();
    
  JSONArray jsonArray = new JSONArray();
    
  for (Book book : books) {
    if (jsonArray.size() 

Der Nachteil dieser Vorgehensweise ist, dass Applikationslogik und Datenformatierung miteinander vermischt werden. Anhand der Methode search(),die dem Aufrufer die Büchersammlung wahlweise in JSON- oder XML-Darstellung zurückliefern kann, wollen wir uns deshalb ansehen, wie sich diese Herausforderung elegant meistern lässt. Wie der folgende Codeausschnitt zeigt, nimmt die Methode selbst keinerlei Datenformatierung vor:

public Collection search(String query) {
  return bookMgr.get(query);
}

Dank JAXB sind bei der Transformation der Collection<Book> in eine adäquate XML-Darstellung keine Probleme zu erwarten. Es bleibt nur zu klären, wie aus der Java-basierenden Büchersammlung die JSON-Darstellung gewonnen wird. Dazu müssen wir einen eigenen Entity-Provider implementieren. Beim Marshalling bedarf es hierzu einer mit @Provider annotierten Klasse, die das Interface MessageBodyWriter implementiert. Außerdem ist via @Produces anzugeben, welche Formate der Provider produzieren kann. Dessen Hauptmethoden stellen isWriteable()und writeTo()dar. Erstere wird von der JAX-RS-Implementierung aufgerufen, um zu erfahren, ob der Entity-Provider die übergebenen Daten transformieren kann. Liefert sie true, dann erfolgt die eigentliche Transformation mittels writeTo(). Die Klasse BookCollectionProvider, die die Transformation eines Collection<Book>-Objekts in eine passende JSON-Darstellung vornimmt, verdeutlicht den Sachverhalt (Listing 4).

Listing 4
...
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class BookCollectionProvider implements 
        MessageBodyWriter> {
  public long getSize(Collection books, Class> type,
        Type genericType, Annotation[] annotations, MediaType mediaType) {
    return -1;
  }

  public boolean isWriteable(Class> type, Type genericType, 
        Annotation[] annotations, MediaType mediaType) {
    boolean writeable = false;
    
    if (Collection.class.isAssignableFrom(type) && genericType 
                instanceof ParameterizedType) {
      ParameterizedType paramType = (ParameterizedType) genericType;
      Type[] typeArgs = paramType.getActualTypeArguments();
      
      writeable = (typeArgs.length == 1 && typeArgs[0].equals(Book.class));
    }

    return writeable;
  }

  public void writeTo(Collection books, Class> type, 
          Type genericType, Annotation[] annotations, MediaType mediaType, 
          MultivaluedMap httpHeaders, OutputStream entityStream) 
          throws IOException, WebApplicationException {
    JSONArray jsonArray = new JSONArray();

    for (Book book : books) {
      jsonArray.add(book.toJSONObject());
    }
    
    entityStream.write(jsonArray.toJSONString().getBytes());
  }
}
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.