Von Null auf REST mit JAX-RS

REST Services mit JAX-RS in OSGi

JAX-RS ist ein Standard in Java, mit dem sich relativ einfach RESTful Web Services erstellen lassen. Dazu werden Klassen („POJOs“) mit Annotationen versehen, um diese per HTTP aufrufbar zu machen. Weitere Details zu JAX-RS finden sich z. B. unter [6]. Es gibt auch jede Menge Literatur zu diesem Thema. Der Einsatz im Java-Enterprise-Umfeld ist relativ einfach, da es ein Standard ist. Die Referenzimplementierung dieses Standards ist Jersey [7]. Zwar sind die Jersey JARs mittlerweile mit OSGi-Bundle-Informationen ausgestattet, dennoch ist ein Einsatz out of the Box heraus nicht ohne Weiteres möglich.

Zum Glück gibt es aber auch hier Abhilfe. Für das Eclipse-Gyrex-Projekt ist eine Erweiterung verfügbar, die es ermöglicht, JAX-RS-annotierte Klassen relativ einfach in einem Bundle verfügbar zu machen. Dazu muss lediglich zusätzlich zu den JAX-RS-Klassen (welche auch Ressourcen genannt werden) eine OSGi-DS-Komponente registriert werden. Allerdings muss diesmal lediglich die Komponentendefinition (XML) erstellt werden. Die Komponentenimplementierung (Java-Klasse) wird durch die Erweiterung schon fertig bereitgestellt. Sie wird in der Komponentendefinition angegeben. Intern funktioniert das so, dass bei der Aktivierung der Komponente das Bundle nach den JAX-RS-annotierten Klassen durchsucht wird. Alle gefundenen JAX-RS-Klassen werden automatisch im System als Webanwendung registriert. Diese kann dann zur Laufzeit unter einem beliebigen URL dynamisch verfügbar gemacht werden. Weiterhin können in die JAX-RS-Klassen OSGi Services injiziert werden.

Zunächst wird ein neues OSGi-Bundle-Projekt angelegt. Wie im vorherigen Beispiel wird wieder ein Name vergeben, das OSGi-Framework ausgewählt und das Projekt ohne Aktivator erstellt. Anschließend werden zwei Beispiel JAX-RS-Klassen erstellt (Listing 3). Eine einfache Klasse demonstriert das „Hallo Welt“-Beispiel für JAX-RS. Die zweite Klasse nutzt die REST-Prinzipien, um Grußbotschaften entgegenzunehmen und diese an den OSGi Service aus dem vorherigen Beispiel zu übergeben. Genauso werden auch vorhandene Botschaften angezeigt. Dazu wird die Möglichkeit genutzt, den Service bequem per Injection zu erhalten. Allerdings muss beachtet werden, dass per Default ein Proxy injiziert wird, der eine IllegalStateException wirft, falls der Service nicht verfügbar ist. Der Vorteil ist, dass so die JAX-RS-Klasse entsprechend reagieren kann (bspw. durch eine „503 – Service nicht verfügbar“ -Antwort), wenn der Service nicht verfügbar ist.

Listing 3: JAX-RS-Beispielklassen
@Path("/")
public class HelloResource {
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String sayHello() {
    return sayHello("there. It's now " + new Date());
  }
  @GET
  @Path("/{name}")
  @Produces(MediaType.TEXT_PLAIN)
  public String sayHello(@PathParam("name") final String name) {
    return String.format("Hello %s!", name);
  }
}

@Path("/greetings")
public class GreetingsResource {
  // inject OSGi service using JAX-RS @Context
  @Context
  private GreetingService greetingService;
  // another JAX-RS injection
  @Context
  private UriInfo uriInfo;
  @POST
  public Response addGreeting(@FormParam("greeting") final String greeting) {
    // post to service
    try {
      greetingService.sayHello(greeting);
    } catch (final IllegalStateException e) {
      // no service is available
      return Response.status(Status.SERVICE_UNAVAILABLE).entity(e.getMessage()).build();
    } catch (final Exception e) {
      // issue deeper in some underlying code
      return Response.serverError().entity(ExceptionUtils.getRootCauseMessage(e)).build();
    }
    // redirect and show success message
    return Response.seeOther(uriInfo.getRequestUriBuilder().replaceQuery("added").build()).build();
  }
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public Response showGreetings() {
    // read greetings
    Collection greetings;
    try {
      greetings = greetingService.getGreetings();
    } catch (final IllegalStateException e) {
      // no service is available
      return Response.status(Status.SERVICE_UNAVAILABLE).type(MediaType.TEXT_PLAIN_TYPE).entity(e.getMessage()).build();
    } catch (final Exception e) {
      // issue deeper in some underlying code
      return Response.serverError().type(MediaType.TEXT_PLAIN_TYPE).entity(ExceptionUtils.getFullStackTrace(e)).build();
    }
    return Response.ok(StringUtils.join(greetings, SystemUtils.LINE_SEPARATOR), MediaType.TEXT_PLAIN).build();
  }
}

Nachdem die JAX-RS-Klassen angelegt sind, muss noch die OSGi-DS-Komponente erstellt werden. Wie bereits erwähnt, wird die Klasse zur Komponente von einem anderen Bundle genutzt, um das Bereitstellen von JAX-RS-Ressourcen zu vereinfachen. Damit die Klasse auch vom Beispiel-Bundle geladen werden kann, müssen die Importdeklarationen in dem Bundle Manifest auch angepasst werden. Das XML der Komponentendefinition ist in Listing 4 zu sehen. Die Komponente stellt einen OSGi Service vom Typ org.eclipse.gyrex.http.application.provider.ApplicationProvider zur Verfügung. Implementiert wird der Service von der Klasse org.eclipse.gyrex.http.jaxrs.JaxRsApplicationProviderComponent, die auch gleichzeitig die Klasse der Komponente darstellt. Damit das funktioniert, müssen die Pakete der Klassen im Import-Package Header im Bundle Manifest aufgelistet sein. Das fertige Projekt ist in Abbildung 5 zu sehen und kann auch online abgerufen werden [8].

Abb. 5: Hallo-JAX-RS-Beispielprojekt
Listing 4: JAX-RS-DS-Komponente
OSGI-INF/jaxrs-application.xml:


META-INF/MANIFEST.MF:
Manifest-Version: 1.0
Bundle-SymbolicName: hello.jaxrs
...
Service-Component: OSGI-INF/jaxrs-application.xml
Import-Package: hello.service;version="1.0.0",
 javax.inject;version="[1.0.0,2.0.0)",
 javax.ws.rs;version="[1.1.0,2.0.0)",
 javax.ws.rs.core;version="[1.1.0,2.0.0)",
 org.apache.commons.lang;version="[2.4.0,3.0.0)",
 org.apache.commons.lang.exception;version="[2.4.0,3.0.0)",
 org.eclipse.gyrex.http.application.provider;version="[1.0.0,2.0.0)",
 org.eclipse.gyrex.http.jaxrs;version="[1.0.0,2.0.0)" 
Bundle-ActivationPolicy: lazy

build.properties:
bin.includes = META-INF/,
               .,
               OSGI-INF/
...
OSGi-Webanwendungen

Der traditionelle Ansatz für die Implementierung von Webanwendungen in OSGi ist der dafür spezifizierte OSGi HttpService. Er wurde vor sehr langer Zeit spezifiziert, als die Servlet-Spezifikation noch die Version 2.1 trug. Er unterstützt daher auch nur Servlets und Ressourcen (statische Dateien). Daher besitzt ein Großteil der verfügbaren Implementierungen Erweiterungen, um bspw. JSPs und Filter unterstützen zu können. Dennoch ist eine Limitierung des HttpService nicht ohne Weiteres aufhebbar. Alle Servlets innerhalb einer Serviceinstanz teilen sich eine Session. Auch ist schwer kontrollierbar, welches Bundle in welchem Namespace Servlets und Ressourcen registrieren darf, sodass es bei fehlender Absprache zwischen Teams leicht zu Konflikten kommen kann. Eine Alternative biete die Web-Application-Spezifikation aus der OSGi Enterpreise Expert Group. Dort wird ein Web-Application-Bundle-(WAB-)Typ eingeführt, der als Web Application in einem Container (bswp. Tomcat oder Jetty) deployt wird. Der Container läuft dabei selbst eingebettet im OSGi-Framework. Eine typische JavaEE-Struktur innerhalb eines WAB wird unterstützt (u. a. auch WEB-INF/web.xml), allerdings verliert man dabei die Flexibilität, die der OSGi HttpService bietet.

Im Artikel wird allerdings weder das eine noch das andere genutzt sondern eine weitere Alternative aus dem Eclipse-Gyrex-Projekt. Dabei bleibt die Dynamik des OSGi HttpService erhalten, es wird aber das Gruppierungskonzept einer Webanwendung eingeführt. Dabei werden Servlets, Ressourcen, Filter etc. von einem ApplicationProvider bereitgestellt. Zur Laufzeit wird dann eine Instanz der Anwendung erzeugt und unter einem (oder mehreren) URL konfiguriert. Es können auch mehrere Instanzen unter verschiedenen URLs mit individuellen Parametern und Erweiterungen erzeugt werden, was in einer Multi-Mandanten-Umgebung von Vorteil ist. Dennoch ist jede Instanz so gekapselt, dass sie wiederum ihre eigene Session und eigene Instanzen von Servlets, Filters etc. nutzt.

Kommentare

Schreibe einen Kommentar

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