RESTful Web Services mit JAX-RS und Jersey

+ andere TechDocs
+ SOA
+ Jersey
+


Der Begriff "REST" (Representational State Transfer) wurde im Jahr 2000 von Roy Thomas Fielding geprägt und definiert allgemeine Grundlagen eines Architekturstils, der auf identifizierbaren Ressourcen, verschiedenen Repräsentationen, Hypermedia und einheitlichen Schnittstellen basiert. Dieser Architekturstil ist nicht auf HTTP beschränkt, aber das auf HTTP basierende Web ist die bekannteste Implementierung. In Konkurrenz zu SOA (Service Oriented Architecture) wird auch manchmal von "ROA" (Resource Oriented Architecture) gesprochen. Eine Einführung zu REST finden Sie im englischsprachigen Wikipedia.

Während REST sehr allgemeine Grundlagen definiert, steht der Begriff "RESTful Web Services" für konkretere Definitionen für auf REST basierender Kommunikation, zum Beispiel das im JSR 311 definierte JAX-RS: The Java API for RESTful Web Services.

Java EE 6 beinhaltet JAX-RS 1.1. Die Referenzimplementierung hierfür ist Jersey 1.1.5 von Sun.

RESTful Web Services konkurrieren mit SOAP Web Services.



Inhalt

  1. Architekturstile
  2. Vergleich zwischen REST und SOAP
  3. REST-konforme Verwendung von GET, PUT, POST und DELETE sowie Assoziationen zu CRUD
  4. HelloWorld-Programmierbeispiel eines RESTful-Webservices (mit Grizzly)
    Libs, HalloWeltService.java, HalloWeltTestServer.java, HalloWeltTestClient.java, Projektstruktur, Build und Test, WADL
  5. Kommandozeilen-Client-Tools
    cURL, Wget
  6. RESTful-HelloWorld-Programmierbeispiel mit Maven
  7. JAX-RS mit XML-Daten per JAXB
  8. HelloWorld-Programmierbeispiel eines RESTful-Webservices mit Tomcat und Eclipse
    Dynamic Web Project und Web Module Settings, Jersey, web.xml, HTML, RESTful-Webservice, Java-Client für den RESTful-Webservice
  9. Programmierbeispiel mit @GET, @PUT, @POST, @DELETE, @PathParam, @QueryParam, @FormParam, @XmlRootElement
    DO und CRUD-DAO für Bücherverwaltung, RESTful-Webservice, Test, GET, PUT, POST und DELETE mit einem Java-Client, GET, PUT, POST und DELETE mit cURL, POST mit PostToUrl
  10. Firefox-Plug-ins Poster und Firebug
    Poster, Firebug
  11. Links auf weiterführende Informationen



Architekturstile

Insbesondere im SOA-Umfeld wird oft zwischen folgenden Architekturstilen unterschieden, obwohl die Grenzen fließend sind:



Vergleich zwischen REST und SOAP

Vergleiche zwischen REST und anderen Architekturstilen oder Technologien sind nicht einfach und schwer objektivierbar. Es gibt sicherlich nicht eine pauschal "bessere" Technologie, sondern es kommt auf den Einsatzfall an. Auch der folgende Vergleich ist sicherlich diskussionswürdig:


 RESTful Web ServicesSOAP Web Services
Basis-StandardRESTW3C u.a.
Java-StandardJAX-RS (JSR 311)JAX-WS (JSR 224)
Beispiel für Java-ImplementierungJerseyMetro
Architekturstil"ROA" (Resource Oriented Architecture),
ressourcenorientiert mit generischer uniformer Schnittstelle (GET, PUT, POST, DELETE)
eher "SOA" (Service Oriented Architecture),
schnittstellen-/nachrichtenorientiert
Bevorzugtes Anwendungsgebietdatenorientierte synchrone kurz laufende Servicessowohl datenorientierte als auch lang laufende prozessorientierte Services, synchron und asynchron
Serverseitiger Zustand/Statuszustandsloseher zustandslos, kann aber auch zustandsbehaftet sein
Formale Schnittstellenbeschreibungnur zum Teil standardisiert: Nachrichtenformate können durch XML Schema Definition und WADL beschrieben werdenvollständig durch WSDL
NachrichtenformatText, HTML, XML, JSON, ...XML (plus Attachments)
NachrichtenprotokollRESTSOAP
TransportprotokollHTTPHTTP, SMTP, JMS, ...
Asynchrone Kommunikationnicht direkt (nur über Umwege simuliert)ja, per JMS und WS-Notification (WSN)
Transaktion, sichere Zustellungkeine Unterstützung, stattdessen kann GET, PUT und DELETE idempotent gestaltet werdenper WS Reliable Messaging (WSRM) und Web Services Transactions Framework (WSTF)
Routingmöglichper WS-Addressing
Operationsabhängiger Zugriffsschutzeinfach, per Webserver oder Firewallper WS-Security (WSS)
Bookmarks/Linksjanein
Cachingeinfachschwierig
Skalierbarkeitoptimalkann schwieriger sein
Performanceguteher schlechter
Lose Kopplung, Interoperabilität, Plattformunabhängigkeit, Internetfähigkeitjaja


REST-konforme Verwendung von GET, PUT, POST und DELETE sowie Assoziationen zu CRUD

Die vier am häufigsten benutzten HTTP-Verben sind GET, PUT, POST und DELETE. Seltener verwendet werden HEAD und OPTIONS. Die folgende Tabelle zeigt übliche Assoziationen zu CRUD (Create, Read, Update, Delete) und Beispiele für eine REST-konforme Verwendungen:


  HTTP    CRUD  Beispiel-URL und -BedeutungIdempotenz
GETRead http://xyz.de/Artikel/Buecher
--> Liste aller Bücher;
http://xyz.de/Artikel/Buecher/4711
--> Informationen zu dem per ID ausgewählten Buch;
http://xyz.de/Artikel/Buecher?isbn=1234567890
--> Informationen zu dem per Suchkriterium ausgewählten Buch
idempotent
POSTCreate http://xyz.de/Artikel/Buecher
--> Neuen Artikel hinzufügen (mit neuer ID)
(dabei wird üblicherweise die automatisch vergebene ID returniert)
nicht
idempotent
PUTUpdate,
Create
http://xyz.de/Artikel/Buecher/4711
--> Update (oder Create) des per ID identifizierten Artikels
idempotent
DELETE Delete http://xyz.de/Artikel/Buecher/4711
--> Diesen Artikel löschen
idempotent


HelloWorld-Programmierbeispiel eines RESTful-Webservices (mit Grizzly)

Das folgende Beispiel zeigt eine minimale Implementierung eines RESTful-Webservices mit JAX-RS inklusive Server und Client.

JAX-RS ist in Java EE 6 enthalten. Aber hier wollen wir uns auf Java SE 6 beschränken, wo JAX-RS nicht enthalten ist. Deshalb wird eine JAX-RS-Implementierung benötigt: Wir wählen die Sun-Referenzimplementierung Jersey.

Als besonders einfacher Webserver wird Grizzly verwendet.

In anderen Beispielen werden benötigte Bibliotheken automatisch über Maven hinzugefügt, aber in diesem ersten Beispiel soll alles manuell ohne Maven erfolgen.

  1. Sehen Sie sich den Jersey User Guide an.
  2. Installieren Sie ein aktuelles Java SE JDK.
  3. Legen Sie ein Projektverzeichnis an (z.B. D:\MeinWorkspace\JaxRsHelloWorld), darunter die Verzeichnisse bin, lib und src, und unter letzterem minirestwebservice:

    [\MeinWorkspace\JaxRsHelloWorld]
     |- [bin]
     |- [lib]
     '- [src]
         '- [minirestwebservice]
    
  4. Downloaden Sie jersey-archive-1.1.5.zip von https://jersey.dev.java.net oder von http://download.java.net/maven/2/com/sun/jersey/jersey-archive/1.1.5.

    Entzippen Sie das Jersey-Archive in ein temporäres Verzeichnis und kopieren Sie die .jar-Libraries asm-3.1.jar, jersey-client-1.1.5.jar, jersey-core-1.1.5.jar, jersey-server-1.1.5.jar und jsr311-api-1.1.1.jar (oder alle Libs) aus dem jersey-archive-1.1.5/lib-Verzeichnis in das JaxRsHelloWorld/lib-Verzeichnis.

  5. Downloaden Sie den Grizzly-Server von http://download.java.net/maven/2/com/sun/grizzly/grizzly-servlet-webserver (z.B. grizzly-servlet-webserver-1.9.18.jar).

    Kopieren Sie die Grizzly-jar-Lib in das JaxRsHelloWorld/lib-Verzeichnis.

  6. Legen Sie im src\minirestwebservice-Verzeichnis die folgenden drei Java-Dateien an.

    Dienstimplementierung: HalloWeltService.java

    package minirestwebservice;
    
    import javax.ws.rs.*;
    
    @Path( "/helloworld" )
    public class HalloWeltService
    {
       @GET @Produces( "text/plain" )
       public String halloText( @QueryParam("name") String name )
       {
          return "Hallo " + name;
       }
    
       @GET @Produces( "text/html" )
       public String halloHtml( @QueryParam("name") String name )
       {
          return "<html><title>HelloWorld</title><body><h2>Html: Hallo " + name + "</h2></body></html>";
       }
    }
    

    Wenn Sie nicht verschiedene Repräsentationen (Ausgabeformate, hier: text/plain und text/html) unterstützen wollen, genügt nur eine der beiden GET-Methoden.
    Falls Sie weitere Repräsentationen benötigen (z.B. application/xml), können Sie weitere Methoden hinzufügen.

    Sehen Sie sich die Bedeutung der @Path-, @GET-, @Produces- und @QueryParam-Annotationen an unter:
    https://jsr311.dev.java.net/nonav/releases/1.1/javax/ws/rs/package-summary.html

    RESTful-Webservice-Server: HalloWeltTestServer.java

    package minirestwebservice;
    
    import com.sun.grizzly.http.SelectorThread;
    import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
    
    public class HalloWeltTestServer
    {
       public static void main( String[] args ) throws Exception
       {
          String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434";
          String sec = ( args.length > 1 ) ? args[1] : "10";
    
          SelectorThread srv = GrizzlyServerFactory.create( url );
    
          System.out.println( "URL: " + url );
          Thread.sleep( 1000 * Integer.parseInt( sec ) );
          srv.stopEndpoint();
       }
    }
    

    Sehen Sie sich die GrizzlyServerFactory-Klasse an. Bei der hier verwendeten besonders simplen create()-Methode werden alle Klassen im Classpath (inkl. jars) nach JAX-RS-annotierten Klassen durchsucht. Sehen Sie sich auch die anderen create()-Methoden an, über die Sie genauer die zu verwendenden Klassen und Eigenschaften definieren können.

    RESTful-Webservice-Client: HalloWeltTestClient.java

    package minirestwebservice;
    
    import com.sun.jersey.api.client.*;
    
    public class HalloWeltTestClient
    {
       public static void main( String[] args )
       {
          String url = ( args.length > 0 ) ? args[0] : "http://localhost:4434";
          String nam = ( args.length > 1 ) ? args[1] : "ich";
          url = url + "/helloworld?name=" + nam;
          System.out.println( "URL: " + url );
    
          WebResource wrs = Client.create().resource( url );
    
          System.out.println( "\nTextausgabe:" );
          System.out.println( wrs.accept( "text/plain" ).get( String.class ) );
          System.out.println( "\nHTML-Ausgabe:" );
          System.out.println( wrs.accept( "text/html"  ).get( String.class ) );
       }
    }
    

    Sehen Sie sich die Client- und WebResource-Klassen an. Beachten Sie, dass auch asynchrone Clients erstellt werden können.

  7. Die Projektstruktur sieht jetzt so aus:

    cd \MeinWorkspace\JaxRsHelloWorld

    tree /F

    [\MeinWorkspace\JaxRsHelloWorld]
     |- [bin]
     |- [lib]
     |   |- asm-3.1.jar
     |   |- grizzly-servlet-webserver-1.9.18.jar
     |   |- jersey-client-1.1.5.jar
     |   |- jersey-core-1.1.5.jar
     |   |- jersey-server-1.1.5.jar
     |   '- jsr311-api-1.1.1.jar
     '- [src]
         '- [minirestwebservice]
             |- HalloWeltService.java
             |- HalloWeltTestClient.java
             '- HalloWeltTestServer.java
    
  8. Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), bauen Sie das Projekt und starten Sie den RESTful-Webservice-Server und den -Client in verschiedenen Kommandozeilenfenstern (ersetzen Sie ich durch Ihren Namen):

    cd \MeinWorkspace\JaxRsHelloWorld

    javac -cp bin;lib/* -d bin src/minirestwebservice/*.java

    start java -cp bin;lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 20

    java -cp bin;lib/* minirestwebservice.HalloWeltTestClient http://localhost:4434 ich

    start http://localhost:4434/helloworld?name=ich

    start http://localhost:4434/application.wadl

    Die letzten vier Kommandos müssen kurz hintereinander (z.B. in einer Batchdatei) innerhalb der angegebenen Zeit (im Beispiel: 20 Sekunden) ausgeführt werden, weil sich anschließend der TestServer beendet.

  9. Sie erhalten im Kommandozeilenfenster:

    URL: http://localhost:4434/helloworld?name=ich
    
    Textausgabe:
    Hallo ich
    
    HTML-Ausgabe:
    <html><title>HelloWorld</title><body><h2>Html: Hallo ich</h2></body></html>
    
  10. Im ersten Webbrowser-Fenster erscheint das Ergebnis der GET-Methode: Firefox 3.6 zeigt die HTML-Repräsentation "Html: Hallo ich", aber MS Internet Explorer 8 zeit die Text-Repräsentation "Hallo ich".

  11. Im zweiten Webbrowser-Fenster bietet MS Internet Explorer den Download der WADL-Datei an, während Firefox sie direkt anzeigt:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <application xmlns="http://research.sun.com/wadl/2006/10">
      <doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey: 1.1.5"/>
      <resources base="http://localhost:4434/">
        <resource path="/helloworld">
          <method name="GET" id="halloText">
            <request>
              <param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" style="query" name="name"/>
            </request>
            <response>
              <representation mediaType="text/plain"/>
            </response>
          </method>
          <method name="GET" id="halloHtml">
            <request>
              <param xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string" style="query" name="name"/>
            </request>
            <response>
              <representation mediaType="text/html"/>
            </response>
          </method>
        </resource>
      </resources>
    </application>
    

    WADL (Web Application Description Language) ist ein XML-basiertes Dateiformat zur Beschreibung von Schnittstellen von HTTP-basierten Anwendungen (besonders RESTful-Webservices) in maschinenlesbarer Form (teilweise vergleichbar mit WSDL).

    Sie können leicht die Beschreibung der zwei implementierten GET-Methoden halloText() und halloHtml() erkennen.

  12. Sie können auch andere URLs beim HalloWeltTestServer- und HalloWeltTestClient-Aufruf übergeben, auch inklusive eines Web-Root-ContextPath-Anteils, beispielsweise http://localhost:4711/xyz.

  13. Wenn Sie eine lange Laufzeit vorgegeben haben, können Sie den HalloWeltTestServer auch einfach vorzeitig mit "Strg + C" beenden.



Kommandozeilen-Client-Tools

cURL als Kommandozeilen-Client

cURL ist ein Kommandozeilentool zum Downloaden von Dateien. Doku finden Sie unter http://curl.haxx.se/docs/manual.html.

  1. Downloaden Sie für Windows die Datei "curl-7.19.4-win32-nossl.zip" von http://curl.haxx.se/download.html unter "Win32 - Generic, Win32 2000/XP, 7.19.4, binary, kein SSL" (durchgestrichenes SSL-Symbol).
  2. Entzippen Sie curl-7.19.4-win32-nossl.zip und sehen Sie sich die Anleitung in curl-7.19.4-win32-nossl\curl-7.19.4\docs\MANUAL an.
  3. Öffnen Sie im curl-7.19.4-win32-nossl\curl-7.19.4-Verzeichnis ein Kommandozeilenfenster und sehen Sie sich die Liste der vielfältigen Kommandozeilenoptionen an:

    curl --help

  4. Starten Sie obigen HalloWeltTestServer mit langer Laufzeit (z.B. 300 Sekunden) und lesen Sie folgendermaßen die Ausgabe des HelloWorld-Beispiels:

    cd \MeinWorkspace\JaxRsHelloWorld

    start java -cp bin;lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 300

    curl "http://localhost:4434/helloworld?name=ich"

    Sie erhalten:

    Hallo ich
    
  5. Mit der Option "-i" geben Sie auch die empfangenen Header-Informationen aus:

    curl -i "http://localhost:4434/helloworld?name=ich"

    Sie erhalten u.a.:

    HTTP/1.1 200 OK
    Content-Type: text/plain
    
    Hallo ich
    
  6. Mit der Option "-H" können Sie Header-Informationen zum Server senden und so den Content-Type vorgeben:

    curl -H "Accept:text/plain" "http://localhost:4434/helloworld?name=ich"

    -->

    Hallo ich
    

    und

    curl -i -H "Accept:text/html" "http://localhost:4434/helloworld?name=ich"

    -->

    HTTP/1.1 200 OK
    Content-Type: text/html
    
    <html><title>HelloWorld</title><body><h2>Html: Hallo ich</h2></body></html>
    
  7. Lassen Sie sich die WADL-Datei ausgeben:

    curl -i "http://localhost:4434/application.wadl"

    Beachten Sie den im Header ausgegebenen Content-Type: application/vnd.sun.wadl+xml: Das "vnd" steht für "Vendor-spezifisch" (also proprietär).

  8. Wie Sie mit cURL PUT-, POST- und DELETE-Kommandos absetzen können, finden Sie weiter unten erläutert.

Wget als Kommandozeilen-Client

Ein anderes Kommandozeilentool zum Downloaden von Dateien ist Wget.

  1. Doku zu Wget finden Sie unter: GNU Wget Manual.
  2. Lesen Sie folgendermaßen die Ausgabe des obigen HelloWorld-Beispiels (durch die Option "-O -" wird keine Datei angelegt, sondern nur auf StdOut ausgegeben):

    cd \MeinWorkspace\JaxRsHelloWorld

    start java -cp bin;lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 300

    wget -O - "http://localhost:4434/helloworld?name=ich"

    Sie erhalten:

    Hallo ich
    
  3. Mit der Option "--save-headers" geben Sie auch die empfangenen Header-Informationen aus:

    wget --save-headers -O - "http://localhost:4434/helloworld?name=ich"

    Sie erhalten u.a.:

    HTTP/1.1 200 OK
    Content-Type: text/plain
    
    Hallo ich
    

Grafische Tools

Falls Sie statt der Kommandozeilentools grafische Tools bevorzugen, sollten Sie sich die weiter unten gezeigten Firefox-Plug-ins Poster und Firebug ansehen.



RESTful-HelloWorld-Programmierbeispiel mit Maven

Das folgende Programmierbeispiel erweitert obiges HelloWorld-Programmierbeispiel eines RESTful-Webservices mit Grizzly um das Build-Tool Maven.

  1. Wechseln Sie in Ihr Workspace-Verzeichnis (z.B. D:\MeinWorkspace), in dem sich auch das JaxRsHelloWorld-Projekt befindet, und führen Sie folgende Kommandos aus

    mvn archetype:generate -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=minirestwebservice -DartifactId=JaxRsMitMaven

    cd JaxRsMitMaven

    tree /F

    rd src\main\resources

    md src\main\java\minirestwebservice

    md src\test\java\minirestwebservice

    xcopy ..\JaxRsHelloWorld\src\minirestwebservice\*.java src\main\java\minirestwebservice\

    tree /F

  2. Ersetzen Sie den Inhalt der pom.xml durch:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>minirestwebservice</groupId>
      <artifactId>JaxRsMitMaven</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
      <name>${artifactId}</name>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.6</source>
              <target>1.6</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>com.sun.jersey</groupId>
          <artifactId>jersey-server</artifactId>
          <version>1.1.5</version>
        </dependency>
        <dependency>
          <groupId>com.sun.jersey</groupId>
          <artifactId>jersey-client</artifactId>
          <version>1.1.5</version>
        </dependency>
        <dependency>
          <groupId>com.sun.grizzly</groupId>
          <artifactId>grizzly-servlet-webserver</artifactId>
          <version>1.9.18</version>
        </dependency>
        <dependency>
          <groupId>org.testng</groupId>
          <artifactId>testng</artifactId>
          <version>5.9</version>
          <classifier>jdk15</classifier>
          <scope>test</scope>
        </dependency>
      </dependencies>
      <!-- Falls Sie fehlende Libs direkt übers Internet nachladen, benötigen Sie folgende Repository-Angaben
           (bitte entfernen, falls Sie einen Repository-Manager wie Archiva konfiguriert haben): -->
      <repositories>
        <repository>
          <id>maven2-repository.dev.java.net</id>
          <name>Java.net Maven 2 Repository</name>
          <url>http://download.java.net/maven/2</url>
        </repository>
        <repository>
          <id>maven-repository.dev.java.net</id>
          <name>Java.net Maven 1 Repository (legacy)</name>
          <url>http://download.java.net/maven/1</url>
          <layout>legacy</layout>
        </repository>
      </repositories>
    </project>
    
  3. Ersetzen Sie im src\main\webapp\WEB-INF-Verzeichnis den Inhalt der web.xml durch:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             id="WebApp_ID" version="2.5">
      <display-name>REST</display-name>
      <servlet>
        <display-name>JAX-RS REST Servlet</display-name>
        <servlet-name>REST-Servlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>REST-Servlet</servlet-name>
        <url-pattern>/rs/*</url-pattern>
      </servlet-mapping>
      <welcome-file-list>
        <welcome-file>index.html</welcome-file>
      </welcome-file-list>
    </web-app>
    
  4. Fügen Sie im src\test\java\minirestwebservice-Verzeichnis die Unit-Testklasse HalloWeltServiceTest.java hinzu:

    package minirestwebservice;
    
    import junit.framework.TestCase;
    import com.sun.grizzly.http.SelectorThread;
    import com.sun.jersey.api.client.Client;
    import com.sun.jersey.api.client.WebResource;
    import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
    
    public class HalloWeltServiceTest extends TestCase
    {
       public void testRESTfulWebService() throws Exception
       {
          String urlSrvr = "http://localhost:4434";
          String urlClnt = urlSrvr + "/helloworld?name=MeinName";
    
          // Testserver:
          SelectorThread srv = GrizzlyServerFactory.create( urlSrvr );
    
          // Testclient:
          WebResource wrs = Client.create().resource( urlClnt );
          String      txt = wrs.accept( "text/plain" ).get( String.class );
          String      htm = wrs.accept( "text/html"  ).get( String.class );
    
          // Testserver beenden:
          srv.stopEndpoint();
    
          // Pruefungen:
          assertEquals( "Hallo MeinName", txt );
          assertEquals( "<html><title>HelloWorld</title><body><h2>Html: Hallo MeinName</h2></body></html>", htm );
       }
    }
    
  5. Bitte beachten Sie, dass Sie diesmal nicht manuell Libs zum Projekt hinzukopieren müssen, weil sich darum Maven kümmert.

  6. Die Projektstruktur sieht jetzt so aus (überprüfen Sie es mit tree /F):

    [\MeinWorkspace\JaxRsMitMaven]
     |- [src]
     |   |- [main]
     |   |   |- [java]
     |   |   |   '- [minirestwebservice]
     |   |   |       |- HalloWeltService.java
     |   |   |       |- HalloWeltTestClient.java
     |   |   |       '- HalloWeltTestServer.java
     |   |   |- [resources]
     |   |   '- [webapp]
     |   |       |- [WEB-INF]
     |   |       |   '- web.xml
     |   |       '- index.jsp
     |   '- [test]
     |       '- [java]
     |           '- [minirestwebservice]
     |               '- HalloWeltServiceTest.java
     '- pom.xml
    
  7. Führen Sie den Unittest aus:

    mvn test

  8. Falls Sie Eclipse einsetzen wollen, bereiten Sie Eclipse vor wie beschrieben unter maven.htm#Eclipse, führen Sie folgendes Kommando aus, laden Sie das JaxRsMitMaven-Projekt in Eclipse und führen Sie den Unittest innerhalb von Eclipse aus:

    mvn eclipse:eclipse

  9. Sie können auch weiterhin den HalloWeltTestServer und den HalloWeltTestClient über die Kommandozeile betreiben:

    cd \MeinWorkspace\JaxRsMitMaven

    mvn package

    start java -cp target/JaxRsMitMaven-1.0-SNAPSHOT/WEB-INF/classes;target/JaxRsMitMaven-1.0-SNAPSHOT/WEB-INF/lib/* minirestwebservice.HalloWeltTestServer http://localhost:4434 20

    java -cp target/JaxRsMitMaven-1.0-SNAPSHOT/WEB-INF/classes;target/JaxRsMitMaven-1.0-SNAPSHOT/WEB-INF/lib/* minirestwebservice.HalloWeltTestClient http://localhost:4434 ich

    start http://localhost:4434/helloworld?name=ich

    start http://localhost:4434/application.wadl

    Die letzten vier Kommandos müssen kurz hintereinander (z.B. in einer Batchdatei) innerhalb der angegebenen Zeit (im Beispiel: 20 Sekunden) ausgeführt werden, weil sich anschließend der TestServer beendet.

  10. Auch cURL können Sie weiter verwenden (bei laufendem HalloWeltTestServer):

    curl -i -H "Accept:text/plain" "http://localhost:4434/helloworld?name=ich"

    curl -i -H "Accept:text/html" "http://localhost:4434/helloworld?name=ich"

    curl -i "http://localhost:4434/application.wadl"

  11. Die WAR-Datei im target-Verzeichnis können Sie in beliebige Java EE Webserver oder Application Server deployen (z.B. durch Kopieren ins autodeploy-Verzeichnis). Im Falle von WebLogic erreichen Sie das Ergebnis über:

    http://localhost:7001/JaxRsMitMaven-1.0-SNAPSHOT/rs/helloworld?name=ich

    http://localhost:7001/JaxRsMitMaven-1.0-SNAPSHOT/rs/application.wadl

    Hinweise zum Testen mit Java EE Application Servern finden Sie unter Automatisierter Integrationstest mit Cargo für JBoss, WebLogic und Tomcat.



JAX-RS mit XML-Daten per JAXB

Mit RESTful Web Services können Java-Objekte zum Beispiel per XML übertragen werden, sowohl als Input-Argument, als auch als returniertes Ergebnis. Das Marshalling und Unmarshalling erfolgt vorzugsweise mit JAXB. Dies demonstriert das folgende Beispiel, welches der Einfachheit halber auf dem letzten aufbaut.

  1. Erstellen Sie folgende drei Java-Klassen im src\main\java\minirestwebservice-Verzeichnis.

    Input-Klasse: InputTO.java

    package minirestwebservice;
    
    import java.util.Date;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement
    public class InputTO
    {
       public int  i;
       public Date d;
    }
    
  2. Ergebnis-Klasse: ResultTO.java

    package minirestwebservice;
    
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement
    public class ResultTO
    {
       public int    i;
       public String s;
    }
    
  3. Service-Klasse: JaxbService.java

    package minirestwebservice;
    
    import java.text.SimpleDateFormat;
    import javax.ws.rs.*;
    
    @Path( "/hellojaxb" )
    public class JaxbService
    {
       @POST
       @Consumes( "application/xml" )
       @Produces( "application/xml" )
       public ResultTO getJaxbService( InputTO inp )
       {
          ResultTO res = new ResultTO();
          res.i = inp.i * 2;
          res.s = (new SimpleDateFormat( "yyyy-MM-dd HH:mm" )).format( inp.d );
          return res;
       }
    }
    
  4. Erstellen Sie im src\test\java\minirestwebservice-Verzeichnis die Unittest-Klasse: JaxbServiceTest.java

    package minirestwebservice;
    
    import java.util.Date;
    import junit.framework.TestCase;
    import com.sun.grizzly.http.SelectorThread;
    import com.sun.jersey.api.client.Client;
    import com.sun.jersey.api.client.WebResource;
    import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
    
    public class JaxbServiceTest extends TestCase
    {
       public void testJaxbService() throws Exception
       {
          String urlSrvr = "http://localhost:4434";
          String urlClnt = urlSrvr + "/hellojaxb";
    
          // Testserver:
          SelectorThread srv = GrizzlyServerFactory.create( urlSrvr );
    
          // Testclient:
          InputTO inp = new InputTO();
          inp.i = 42;
          inp.d = new Date();
          WebResource wrs = Client.create().resource( urlClnt );
          ResultTO    res = wrs.post( ResultTO.class, inp );
    
          // Testserver beenden:
          srv.stopEndpoint();
    
          // Pruefungen:
          assertEquals( 84, res.i );
          assertEquals( "20", res.s.substring( 0, 2 ) );
       }
    }
    
  5. Führen Sie die Unittests aus:

    mvn test

  6. Falls Sie nicht mit Java-Dateien beginnen wollen, sondern mit Schema-XSD-Dateien ("Contract-First", z.B. weil Server und Client in verschiedenen Programmiersprachen erstellt werden sollen), dann können Sie die Java-Datentransferobjektklassen auch mit xjc generieren lassen:

    xjc -d src/main/java -p mein.package MeinSchema.xsd

    Kontrollieren Sie, ob dabei die benötigten @XmlRootElement-Annotationen erzeugt wurden (fügen Sie sie gegebenenfalls hinzu). Kontrollieren Sie auch die Namespace-Definitionen.



HelloWorld-Programmierbeispiel eines RESTful-Webservices mit Tomcat und Eclipse

Das Beispiel beschreibt die Vorgehensweise unter Eclipse. Aber es ist natürlich auch ohne Eclipse lauffähig.

Dynamic Web Project und Web Module Settings

  1. Installieren Sie Java, Eclipse und Tomcat wie beschrieben unter: jee-tomcat-eclipse.htm#Installation.
  2. Starten Sie in Eclipse ein neues Web-Projekt über:
    'File' | 'New' | 'Project...' | '[+] Web' | 'Dynamic Web Project' | 'Next >'.

  3. Tragen Sie ein:

    Project name: REST
    Use default: Ja
    Target Runtime: Apache Tomcat v6.0
    Configurations: Default Configuration for Apache Tomcat v6.0
    Add project to an EAR: Nein

    'Next >'

  4. Falls der Project Facets Dialog erscheint: Bei 'Java' sollte mindestens '5.0' eingetragen sein.
    'Next >'

  5. Tragen Sie 'web module settings' ein:

    Context Root: REST
    Content Directory: WebContent
    Java Source Directory: src
    Generate Deployment Descriptor: Ja

    'Finish'

Jersey

  1. Sehen Sie sich das Community Wiki for Project Jersey und Overview of JAX-RS 1.0 Features an.

  2. Downloaden Sie Jersey von https://jersey.dev.java.net oder von http://download.java.net/maven/2/com/sun/jersey/jersey-archive (z.B. jersey-archive-1.1.0-ea.zip).

  3. Entzippen Sie das Jersey-Archive und kopieren Sie alle .jar-Libraries aus dem jersey-archive-1.1.0-ea\lib-Verzeichnis in das <MeinWorkspace>/REST/WebContent/WEB-INF/lib-Verzeichnis.

  4. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'REST'-Projektnamen und wählen Sie 'Refresh' (oder betätigen Sie 'F5'), damit Eclipse die .jar-Libraries registriert.

web.xml

  1. Öffnen Sie im Eclipse Project Explorer das 'REST'-Projekt und darunter den 'WebContent'-Zweig und das 'WEB-INF'-Verzeichnis. Öffnen Sie die web.xml, schalten Sie eventuell von der 'Design'-Ansicht auf die 'Source'-Ansicht um, und ersetzen Sie den Inhalt durch:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             id="WebApp_ID" version="2.5">
      <display-name>REST</display-name>
      <servlet>
        <display-name>JAX-RS REST Servlet</display-name>
        <servlet-name>REST-Servlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>REST-Servlet</servlet-name>
        <url-pattern>/rs/*</url-pattern>
      </servlet-mapping>
      <welcome-file-list>
        <welcome-file>index.html</welcome-file>
      </welcome-file-list>
    </web-app>
    

HTML

  1. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'REST'-Projektnamen und wählen Sie 'New' | 'HTML' | 'File name' = 'index.html' | 'Finish'.

  2. Ändern Sie den Inhalt von index.html zu:

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
      <title>Meine RESTful-Webservice-Startseite</title>
    </head>
    <body>
      <h3> Meine RESTful-Webservice-Startseite </h3>
      <p> <a href="/REST/rs/application.wadl">/REST/rs/application.wadl</a> </p>
      <p> <a href="/REST/rs/helloworld">/REST/rs/helloworld</a> </p>
    </body>
    </html>
    

RESTful-Webservice

  1. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'REST'-Projektnamen, wählen Sie 'New' | 'Package' und tragen Sie ein:

    Source folder: REST/src
    Name: meinpackage

    'Finish'

  2. Öffnen Sie im Eclipse Project Explorer das 'REST'-Projekt und darunter den 'Java Resources: src'-Zweig. Klicken Sie mit der rechten Maustaste auf den Package-Namen 'meinpackage', wählen Sie 'New' | 'Class' und tragen Sie ein:

    Source folder: REST/src
    Package: meinpackage
    Name: HelloWorld

    'Finish'

  3. Ändern Sie den Inhalt von HelloWorld.java zu:

    package meinpackage;
    
    import javax.ws.rs.*;
    
    @Path( "/helloworld" )
    public class HelloWorld
    {
       @GET @Produces( "text/plain" )
       public String halloText()
       {
          return "Hallo Text-Welt\n";
       }
    
       @GET @Produces( "text/html" )
       public String halloHtml()
       {
          return "<html><title>HelloWorld</title><body><h2>Hallo Html-Welt</h2></body></html>";
       }
    }
    
  4. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'REST'-Projektnamen und wählen Sie 'Run As' | 'Run on Server'.

  5. Falls der Dialog 'Define a New Server' oder 'Run On Server' erscheint:
    Wählen Sie 'Tomcat v6.0 Server at localhost' und aktivieren Sie die Checkbox 'Always use this server when running this project'. 'Finish'.

  6. Falls Sie danach gefragt werden: Aktivieren Sie 'Update context root ...'.

  7. Falls Tomcat nicht automatisch gestartet wurde:
    Klicken Sie unten auf den 'Servers'-Tabulatorreiter und starten Sie Tomcat (rechte Maustaste auf 'Tomcat v6.0 Server at localhost'-Eintrag oder über den grünen Kreis-Button).

  8. Lassen Sie sich über http://localhost:8080/REST/rs/application.wadl die WADL-Schnittstellenbeschreibung anzeigen. WADL (Web Application Description Language) ist ein XML-basiertes Dateiformat zur Beschreibung von Schnittstellen von HTTP-basierten Anwendungen (besonders RESTful-Webservices) in maschinenlesbarer Form (teilweise vergleichbar mit WSDL).

    Sie erhalten:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <application xmlns="http://research.sun.com/wadl/2006/10">
      <doc xmlns:jersey="http://jersey.dev.java.net/" jersey:generatedBy="Jersey: 1.1.0-ea 04/30/2009 04:46 PM"/>
      <resources base="http://localhost:8080/REST/rs/">
        <resource path="/helloworld">
          <method name="GET" id="halloText">
            <response>
              <representation mediaType="text/plain"/>
            </response>
          </method>
          <method name="GET" id="halloHtml">
            <response>
              <representation mediaType="text/html"/>
            </response>
          </method>
        </resource>
      </resources>
    </application>
    
  9. Lesen Sie die HelloWorld-Ausgabe:
    http://localhost:8080/REST/rs/helloworld

    Je nach Lese-Tool bzw. Browser erhalten Sie entweder die Plain-Text- oder die HTML-Ausgabe.

  10. Falls Sie die URL-Bestandteile ändern wollen:
    "REST" können Sie ändern über: Rechtsklick im Eclipse Project Explorer auf den 'REST'-Projektnamen, 'Properties', 'Web Project Settings', 'Context Root'.
    "rs" können Sie ändern über 'url-pattern' in: <MeinWorkspace>\REST\WebContent\WEB-INF\web.xml.
    "helloworld" können Sie ändern über '@Path( "/helloworld" )' in: <MeinWorkspace>\REST\src\meinpackage\HelloWorld.java.

Java-Client für den RESTful-Webservice

Mit Jersey lassen sich sehr einfach Java-Clients für RESTful-Webservices programmieren. Das folgende Beispiel liest sowohl die Text- als auch die HTML-Ausgabe des obigen HelloWorld-Beispiels.

  1. Erzeugen Sie im Package meinpackage die Java-Klasse HelloWorldClient.java:

    package meinpackage;
    
    import com.sun.jersey.api.client.*;
    
    public class HelloWorldClient
    {
       public static void main( String[] args )
       {
          Client      client      = Client.create();
          WebResource webResource = client.resource( "http://localhost:8080/REST/rs/helloworld" );
          System.out.println( "Textausgabe:" );
          System.out.println( webResource.accept( "text/plain" ).get( String.class ) );
          System.out.println( "HTML-Ausgabe:" );
          System.out.println( webResource.accept( "text/html"  ).get( String.class ) );
       }
    }
    
  2. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf die neu erstellte Java-Klasse HelloWorldClient.java und wählen Sie: 'Run As' | 'Java Application'.

  3. Die Ausgabe sieht folgendermaßen aus:

    Textausgabe:
    Hallo Text-Welt
    
    HTML-Ausgabe:
    <html><title>HelloWorld</title><body><h2>Hallo Html-Welt</h2></body></html>
    

Client-Tools

Natürlich können Sie auch für dieses Beispiel wieder cURL, Wget und die Firefox-Plug-ins Poster und Firebug einsetzen.



Bücherverwaltung-Programmierbeispiel mit @GET, @PUT, @POST, @DELETE, @PathParam, @QueryParam und @FormParam sowie XML-Rückgabe per @XmlRootElement

Das folgende Programmierbeispiel simuliert eine einfache Bücherverwaltung, um die verschiedenen GET-, PUT-, POST- und DELETE-Optionen, die verschiedenen Parameterarten und die automatische Erzeugung einer XML-Rückgabe per @XmlRootElement demonstrieren zu können.

Sie können wahlweise die Sourcedateien als Zip-Archiv REST.zip laden, oder, wie im Folgenden beschrieben, die Dateien selbst erstellen.

DO und CRUD-DAO für Bücherverwaltung

  1. Dieses Programmierbeispiel setzt die Durchführung des obigen HelloWorld-Programmierbeispiels voraus.

  2. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf den 'REST'-Projektnamen, wählen Sie 'New' | 'Package' und tragen Sie ein:

    Source folder: REST/src
    Name: buecher

    'Finish'

  3. Erzeugen Sie im Package buecher die Buch-Domain-Objekt-Klasse BuchDO.java:

    package buecher;
    
    // Buch-Domain-Objekt
    public class BuchDO
    {
       private Long   id;
       private String isbn;
       private String titel;
       private Double preis;
    
       public Long   getId()    { return id; }
       public String getIsbn()  { return isbn; }
       public String getTitel() { return titel; }
       public Double getPreis() { return preis; }
       public void setId(    Long   id    ) { this.id    = id; }
       public void setIsbn(  String isbn  ) { this.isbn  = isbn; }
       public void setTitel( String titel ) { this.titel = titel; }
       public void setPreis( Double preis ) { this.preis = preis; }
    
       public BuchDO()
       {
       }
    
       public BuchDO( Long id, String isbn, String titel, Double preis )
       {
          this.id    = id;
          this.isbn  = isbn;
          this.titel = titel;
          this.preis = preis;
       }
    
       @Override
       public String toString()
       {
          return "Buch (ID=" + id + ", ISBN=" + isbn + ", Titel=" + titel + ", Preis=" + preis + ")";
       }
    }
    
  4. Erzeugen Sie im Package buecher die CRUD-DAO-Klasse BuecherCrudDAO.java:

    package buecher;
    
    import java.util.*;
    
    /** DAO (Data Access Object) fuer CRUD-Operationen (Create, Read, Update, Delete) */
    public class BuecherCrudDAO
    {
       private List<BuchDO> buecherListeAlle = Collections.synchronizedList( new LinkedList<BuchDO>() );
       private static final BuecherCrudDAO INSTANCE = new BuecherCrudDAO();
    
       private BuecherCrudDAO()
       {
          buecherListeAlle.add( new BuchDO( new Long( 4711 ), "1234567891", "MeinTitel1", new Double( 12.34 ) ) );
          buecherListeAlle.add( new BuchDO( new Long( 4712 ), "1234567892", "MeinTitel2", new Double( 22.34 ) ) );
       }
    
       public static BuecherCrudDAO getInstance()
       {
          return INSTANCE;
       }
    
       // Neues Buch hinzufuegen:
       public BuchDO createBuch( String isbn, String titel, Double preis )
       {
          if( isEmpty( isbn ) || isEmpty( titel ) || preis == null )
             return null;
          BuchDO bu = null;
          long maxID = 1000;
          synchronized( buecherListeAlle ) {
             for( BuchDO buchDO : buecherListeAlle )
                if( maxID < buchDO.getId().longValue() )
                    maxID = buchDO.getId().longValue();
             bu = new BuchDO( new Long( ++maxID ), isbn, titel, preis );
             buecherListeAlle.add( bu );
          }
          return bu;
       }
    
       // Finde Buecher:
       public List<BuchDO> findeBuecher( Long buchID, String isbn, String titel )
       {
          if( buchID == null && isEmpty( isbn ) && isEmpty( titel ) )
             return new ArrayList<BuchDO>( Collections.unmodifiableCollection( buecherListeAlle ) );
    
          List<BuchDO> resultList = new LinkedList<BuchDO>();
          List<BuchDO> snapshotList;
          synchronized( buecherListeAlle ) {
             snapshotList = new ArrayList<BuchDO>( buecherListeAlle );
          }
          for( BuchDO buchDO : snapshotList )
             if( (buchID != null && buchDO.getId() != null && buchID.equals( buchDO.getId() )) ||
                 (!isEmpty( isbn )  && !isEmpty( buchDO.getIsbn() ) &&
                       buchDO.getIsbn().trim().toLowerCase().contains( isbn.trim().toLowerCase() )) ||
                 (!isEmpty( titel ) && !isEmpty( buchDO.getTitel() ) &&
                       buchDO.getTitel().trim().toLowerCase().contains( titel.trim().toLowerCase() )) )
                resultList.add( buchDO );
          return resultList;
       }
    
       // Daten eines per ID definierten Buches aendern:
       public BuchDO updateBuchFromID( Long buchID, String isbn, String titel, Double preis )
       {
          if( isEmpty( isbn ) || isEmpty( titel ) || preis == null )
             return null;
          BuchDO bu = new BuchDO( buchID, isbn, titel, preis );
          synchronized( buecherListeAlle ) {
             for( Iterator<BuchDO> itr = buecherListeAlle.iterator(); itr.hasNext(); )
                if( buchID.equals( itr.next().getId() ) ) {
                   itr.remove();
                   buecherListeAlle.add( bu );
                   return bu;
                }
          }
          return null;
       }
    
       // Per ID definiertes Buch loeschen:
       public List<BuchDO> deleteBuchFromID( Long buchID )
       {
          synchronized( buecherListeAlle ) {
             for( Iterator<BuchDO> itr = buecherListeAlle.iterator(); itr.hasNext(); )
                if( buchID.equals( itr.next().getId() ) ) {
                   itr.remove();
                   break;
                }
          }
          return findeBuecher( buchID, null, null );
       }
    
       private static boolean isEmpty( String s )
       {
          return s == null || s.trim().length() <= 0;
       }
    }
    

RESTful-Webservice

  1. Erzeugen Sie im Package buecher die Rueckgabe-Transfer-Objekt-Klasse BuecherListeTO.java:

    package buecher;
    
    import java.util.List;
    import javax.xml.bind.annotation.XmlRootElement;
    
    // Rueckgabe-Transfer-Objekt
    @XmlRootElement
    public class BuecherListeTO
    {
       private String       message;
       private List<BuchDO> results;
       private Integer      returncode;
    
       public String       getMessage()    { return message;    }
       public List<BuchDO> getResults()    { return results;    }
       public Integer      getReturncode() { return returncode; }
       public void setMessage(    String       message    ) { this.message    = message;    }
       public void setResults(    List<BuchDO> results    ) { this.results    = results;    }
       public void setReturncode( Integer      returncode ) { this.returncode = returncode; }
    }
    
  2. Erzeugen Sie im Package buecher die Java-Klasse BuecherService.java, in welcher die vier HTTP-Verben GET, PUT, POST und DELETE als RESTful-Webservice REST-konform implementiert werden:

    package buecher;
    
    import java.util.*;
    import javax.ws.rs.*;
    
    // RESTful Web Service
    @Produces("application/xml")
    @Path("/Artikel")
    public class BuecherService
    {
       public  static final Integer RET_CODE_OK    = new Integer( 0 );
       public  static final Integer RET_CODE_ERROR = new Integer( 1 );
       private BuecherCrudDAO dao = BuecherCrudDAO.getInstance();
    
       // Per ID definiertes Buch ausgeben:
       @GET @Path("/Buecher/{id}")
       public BuecherListeTO getBuchFromID(
             @PathParam("id") String id )
       {
          return findeBuecher( id, null, null );
       }
    
       // Liste von ueber Suchkriterien gefundener Buecher ausgeben:
       @GET @Path("/Buecher")
       public BuecherListeTO getBuecherListe(
             @QueryParam("id")    String id,
             @QueryParam("isbn")  String isbn,
             @QueryParam("titel") String titel )
       {
          return findeBuecher( id, isbn, titel );
       }
    
       // Daten eines per ID definierten Buches aendern:
       @PUT @Path("/Buecher/{id}")
       public BuecherListeTO updateBuchFromID(
             @PathParam("id")    String id,
             @FormParam("isbn")  String isbn,
             @FormParam("titel") String titel,
             @FormParam("preis") String preis )
       {
          BuchDO buchDO = dao.updateBuchFromID( longFromString( id ), isbn, titel, doubleFromString( preis ) );
          return erzeugeBuecherListeTOMitBuchDO( "Buchdaten geaendert", buchDO );
       }
    
       // Neues Buch hinzufuegen:
       @POST @Path("/Buecher")
       public BuecherListeTO createBuch(
             @FormParam("isbn")  String isbn,
             @FormParam("titel") String titel,
             @FormParam("preis") String preis )
       {
          BuchDO buchDO = dao.createBuch( isbn, titel, doubleFromString( preis ) );
          return erzeugeBuecherListeTOMitBuchDO( "Buch hinzugefuegt", buchDO );
       }
    
       // Per ID definiertes Buch loeschen:
       @DELETE @Path("/Buecher/{id}")
       public BuecherListeTO deleteBuchFromID(
             @PathParam("id") String id )
       {
          dao.deleteBuchFromID( longFromString( id ) );
          return findeBuecher( id, null, null );
       }
    
       // Finde Buecher:
       private BuecherListeTO findeBuecher( String id, String isbn, String titel )
       {
          BuecherListeTO buecherListeTO = new BuecherListeTO();
          List<BuchDO>   buecherListe   = dao.findeBuecher( longFromString( id ), isbn, titel );
          if( buecherListe == null )
             return fehlerBuecherListeTO();
          if( isEmpty( id ) && isEmpty( isbn ) && isEmpty( titel ) ) {
             buecherListeTO.setMessage( buecherListe.size() + " Buecher" );
          } else {
             StringBuffer sb = new StringBuffer();
             sb.append( buecherListe.size() + " Ergebnis(se) fuer" );
             if( !isEmpty( id    ) ) sb.append( " ID = " + id );
             if( !isEmpty( isbn  ) ) sb.append( " ISBN = " + isbn );
             if( !isEmpty( titel ) ) sb.append( " Titel = " + titel );
             buecherListeTO.setMessage( sb.toString() );
          }
          buecherListeTO.setResults( buecherListe );
          buecherListeTO.setReturncode( RET_CODE_OK );
          return buecherListeTO;
       }
    
       private BuecherListeTO erzeugeBuecherListeTOMitBuchDO( String msg, BuchDO buchDO )
       {
          if( buchDO == null ) return fehlerBuecherListeTO();
    
          List<BuchDO> buecherListe = new LinkedList<BuchDO>();
          buecherListe.add( buchDO );
    
          BuecherListeTO buecherListeTO = new BuecherListeTO();
          buecherListeTO.setResults( buecherListe );
          buecherListeTO.setMessage( msg );
          buecherListeTO.setReturncode( RET_CODE_OK );
          return buecherListeTO;
       }
    
       private static BuecherListeTO fehlerBuecherListeTO()
       {
          BuecherListeTO buecherListeTO = new BuecherListeTO();
          buecherListeTO.setMessage( "Parameterfehler" );
          buecherListeTO.setReturncode( RET_CODE_ERROR );
          return buecherListeTO;
       }
    
       private static boolean isEmpty( String s )
       {
          return s == null || s.trim().length() <= 0;
       }
    
       private static Long longFromString( String s )
       {
          if( !isEmpty( s ) ) try { return new Long( s.trim() ); } catch( NumberFormatException ex ) {}
          return null;
       }
    
       private static Double doubleFromString( String s )
       {
          if( !isEmpty( s ) ) try { return new Double( s.trim() ); } catch( NumberFormatException ex ) {}
          return null;
       }
    }
    

Test

  1. Ersetzen Sie die index.html im Verzeichnis <MeinWorkspace>\REST\WebContent durch folgenden Inhalt:

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
      <title>Meine RESTful-Webservice-Startseite</title>
    </head>
    <body>
      <h3> Meine RESTful-Webservice-Startseite </h3>
    
      <h4><u> WADL </u></h4>
      <p> <a href="/REST/rs/application.wadl">/REST/rs/application.wadl</a> </p>
    
      <h4><u> Buecher-Verwaltung </u></h4>
      <p> <a href="/REST/rs/Artikel/Buecher">/REST/rs/Artikel/Buecher</a> (alle Bücher) </p>
      <p> <a href="/REST/rs/Artikel/Buecher/4711">/REST/rs/Artikel/Buecher/4711</a> (Buch mit ID 4711) </p>
      <p> <a href="/REST/rs/Artikel/Buecher?isbn=1234567892">/REST/rs/Artikel/Buecher?isbn=1234567892</a> (Buch mit ISBN 1234567892) </p>
      <p> <a href="/REST/rs/Artikel/Buecher/?titel=MeinTitel2">/REST/rs/Artikel/Buecher/?titel=MeinTitel2</a> (Buch mit Titel 'MeinTitel2') </p>
      <p> <a href="/REST/rs/Artikel/Buecher/?isbn=1234567891&titel=MeinTitel2">/REST/rs/Artikel/Buecher/?isbn=1234567891&titel=MeinTitel2</a> (Bücher mit ISBN 1234567892 oder Titel 'MeinTitel2') </p>
    
      <p> Neues Buch anlegen (alle Felder müssen ausgefüllt werden): </p>
      <form method="POST" action="/REST/rs/Artikel/Buecher" enctype="application/x-www-form-urlencoded">
         ISBN:&nbsp;<input type="text" name="isbn"  value="1472583693"    maxlength=20> &nbsp;
        Titel:&nbsp;<input type="text" name="titel" value="Neuer Titel X" maxlength=80> &nbsp;
        Preis:&nbsp;<input type="text" name="preis" value="123.45"        maxlength=20> &nbsp;
        <input type="submit" value="Neues Buch anlegen">
      </form>
    
      <p> Bücher finden (es genügt einzelne Felder zu füllen; als ISBN und Titel genügen Teilstrings zum Finden mehrerer passender Bücher): </p>
      <form method="GET" action="/REST/rs/Artikel/Buecher/" enctype="application/x-www-form-urlencoded">
         ISBN:&nbsp;<input type="text" name="isbn"  maxlength=20> &nbsp;
        Titel:&nbsp;<input type="text" name="titel" maxlength=80> &nbsp;
           ID:&nbsp;<input type="text" name="id"    maxlength=40> &nbsp;
        <input type="submit" value="Bücher finden">
      </form>
    </body>
    </html>
    
  2. Starten Sie Tomcat und das Deployment durch Rechtsklick im Eclipse Project Explorer auf den 'REST'-Projektnamen und 'Run As' | 'Run on Server'.

  3. Öffnen Sie die RESTful-Webservice-Startseite http://localhost:8080/REST/.
    Falls noch die Seite vom letzten Projekt angezeigt wird: Führen Sie einen Refresh aus ('Refresh the current page').
    Klicken Sie auf die verschiedenen Links und sehen Sie sich die jeweiligen XML-Ergebnisse an.
    Studieren Sie die WADL-Beschreibung.
    Fügen Sie über "Neues Buch anlegen" verschiedene neue Bücher hinzu.
    Finden Sie Bücher über "Bücher finden". Dabei genügen in den ISBN- und Titel-Feldern auch Teil-Strings (z.B. ISBN '123' oder Titel 'meintit'), so dass mit einer Anfrage mehrere passende Bücher gefunden werden können.

GET, PUT, POST und DELETE mit einem Java-Client

  1. Erzeugen Sie im Package buecher die RESTful-Webservice-Client-Klasse BuecherClient.java:

    package buecher;
    
    import javax.ws.rs.core.MultivaluedMap;
    import com.sun.jersey.api.client.*;
    import com.sun.jersey.core.util.MultivaluedMapImpl;
    
    // RESTful-Webservice-Client
    public class BuecherClient
    {
       public static void main(String[] args)
       {
          Client      client = Client.create();
          WebResource webResource = client.resource( "http://localhost:8080/REST/rs/Artikel/Buecher" );
    
          System.out.println( "\nBuch mit ID 4711:" );
          System.out.println( getBuchByID( webResource, "4711" ) );
    
          System.out.println( "\nFinde Buecher mit ISBN 1234567892 oder Titel 'MeinTitel2':" );
          System.out.println( findeBuecher( webResource, null, "1234567891", "MeinTitel2" ) );
    
          System.out.println( "\nAendere Daten zum Buch mit der ID 4712:" );
          System.out.println( putBuch( webResource, "4712", "isbn=1234567898&titel=Client-PUT-Titel&preis=111" ) );
    
          System.out.println( "\nNeues Buch anlegen:" );
          ClientResponse response = postBuch( webResource, "9876543210", "Client-POST-Titel", new Double( 222 ) );
          System.out.println( response.getStatus() + " " + response.getResponseStatus() );
          System.out.println( response.getEntity( String.class ) );
    
          System.out.println( "\nLoesche Buch mit ID 4711:" );
          System.out.println( deleteBuch( webResource, "4711" ) );
       }
    
       static String getBuchByID( WebResource webResource, String id )
       {
          return webResource.path( id ).get( String.class );
       }
    
       static String findeBuecher( WebResource webResource, String id, String isbn, String titel )
       {
          MultivaluedMap<String,String> queryParams = new MultivaluedMapImpl();
          if( id    != null ) queryParams.add( "id",    id );
          if( isbn  != null ) queryParams.add( "isbn",  isbn );
          if( titel != null ) queryParams.add( "titel", titel );
          return webResource.queryParams( queryParams ).get( String.class );
       }
    
       static ClientResponse putBuch( WebResource webResource, String id, String data )
       {
          return webResource.path( id ).put( ClientResponse.class, data );
       }
    
       static ClientResponse postBuch( WebResource webResource, String isbn, String titel, Double preis )
       {
          MultivaluedMap<String,String> formData = new MultivaluedMapImpl();
          formData.add( "isbn",  isbn );
          formData.add( "titel", titel );
          formData.add( "preis", "" + preis );
          return webResource.type( "application/x-www-form-urlencoded" ).post( ClientResponse.class, formData );
       }
    
       static ClientResponse deleteBuch( WebResource webResource, String id )
       {
          return webResource.path( id ).delete( ClientResponse.class );
       }
    }
    
  2. Klicken Sie im Eclipse Project Explorer mit der rechten Maustaste auf die neu erstellte Java-Klasse BuecherClient.java und wählen Sie: 'Run As' | 'Java Application'. Sehen Sie sich die Ausgabe an und überprüfen Sie das Ergebnis über http://localhost:8080/REST/rs/Artikel/Buecher.

  3. Das Programmierbeispiel ist etwas unsystematisch und inkonsistent: Die Übergabeparameter sind mal einzeln und mal als zusammengefügter String, der Rückgabewert ist mal der XML-String und mal das ClientResponse-Objekt, mal wird der Status ausgegeben und mal nicht etc. Der einzige Grund hierfür ist: Es sollen verschiedene Möglichkeiten demonstriert werden.

GET, PUT, POST und DELETE mit cURL

  1. Installieren Sie cURL wie oben beschrieben. Sie können sich mit cURL die XML-Ausgaben der GET-Kommandos ansehen. Allerdings ist die Ausgabe leider kaum verständlich, weil Zeilenumbrüche fehlen. Folgendermaßen erhalten Sie zum Beispiel das Buch mit der ID 4711:

    curl -i "http://localhost:8080/REST/rs/Artikel/Buecher/4711"
    

    Sie erhalten:

    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Content-Type: application/xml
    Content-Length: 266
    
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherListeTO>
       <message>1 Ergebnis(se) fuer BuchID = 4711</message>
       <results><id>4711</id><isbn>1234567891</isbn><preis>12.34</preis><titel>MeinTitel1</titel></results>
       <returncode>0</returncode></buecherListeTO>
    

    Interessanter für cURL sind die anderen Optionen PUT, POST und DELETE. Die REST-konforme Verwendungen dieser Optionen sind oben unter GET, PUT, POST und DELETE erläutert.
    Lassen Sie sich nach jeder Änderung die Liste aller Bücher anzeigen über: http://localhost:8080/REST/rs/Artikel/Buecher.

  2. Ändern Sie mit cURL über PUT die Informationen eines vorhandenen Buches:

    curl --request PUT -d "isbn=1234567899&titel=PUT-Titel&preis=111" "http://localhost:8080/REST/rs/Artikel/Buecher/4712"
    

    Das geänderte Buch wird returniert:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherListeTO>
       <message>Buchdaten geaendert</message>
       <results><id>4712</id><isbn>1234567899</isbn><preis>111.0</preis><titel>PUT-Titel</titel></results>
       <returncode>0</returncode></buecherListeTO>
    
  3. Fügen Sie mit cURL über POST ein neues Buch hinzu:

    curl --request POST -d "isbn=9876543210&titel=POST-Titel&preis=222" "http://localhost:8080/REST/rs/Artikel/Buecher"
    

    Das neu angelegte Buch wird mit der neu erzeugten Buch-ID returniert:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?><buecherListeTO>
       <message>Buch hinzugefuegt</message>
       <results><id>4714</id><isbn>9876543210</isbn><preis>222.0</preis><titel>POST-Titel</titel></results>
       <returncode>0</returncode></buecherListeTO>
    
  4. Löschen Sie mit cURL über DELETE ein vorhandenes Buch:

    curl --request DELETE "http://localhost:8080/REST/rs/Artikel/Buecher/4711"
    
  5. Falls Sie bei anderen REST-Diensten den Fehler "415 Unsupported Media Type" erhalten, probieren Sie verschiedene Mediatypen. Falls Sie Daten (z.B. per XML über POST) übergeben, sollte auch "-H Content-type:..." korrekt gesetzt werden. "-H Accept:..." definiert nur den Mediatyp des erwarteten Ergebnisses. Bitte beachten Sie auch, dass bei XML manchmal text/xml und manchmal application/xml erwartet wird (siehe hierzu auch text/xml vs application/xml).

POST mit PostToUrl

Falls Sie einen POST-Request per Java, aber ohne Jersey, verschicken wollen, sehen Sie sich das Programmierbeispiel PostToUrl an. Zum Beispiel folgendermaßen können Sie damit ein neues Buch anlegen:

java PostToUrl http://localhost:8080/REST/rs/Artikel/Buecher "isbn=9876543213&titel=PostToUrl-Titel&preis=333"

Das Programmierbeispiel ist auch auf GET, PUT und DELETE erweiterbar.



Firefox-Plug-ins Poster und Firebug

Poster

Falls Sie Mozilla Firefox als Webbrowser verwenden, können Sie Firefox um das Poster-Plug-in erweitern. Klicken Sie dazu unter https://addons.mozilla.org/de/firefox/addon/2691 auf die Schaltfläche "Zu Firefox hinzufügen" bzw. "Add to Firefox". Nach der Installation und dem Neustart sehen Sie unten rechts ein gelb hinterlegtes P.

Starten Sie Tomcat, klicken Sie auf das P, tragen Sie bei URL "http://localhost:8080/REST/rs/Artikel/Buecher", bei Content Type "text/plain" und bei Content to Send "isbn=9876543217&titel=Poster-Titel&preis=777" ein, wählen Sie bei Actions "POST" und klicken Sie auf das "GO" neben "POST".

Sie erhalten in etwa Folgendes:

Poster Poster

Firebug

Falls Sie Mozilla Firefox als Webbrowser verwenden, können Sie Firefox um das Firebug-Plug-in erweitern. Klicken Sie dazu unter https://addons.mozilla.org/de/firefox/addon/1843 auf die Schaltfläche "Zu Firefox hinzufügen" bzw. "Add to Firefox". Nach der Installation und dem Neustart sehen Sie unten rechts einen kleinen Käfer.

Starten Sie Tomcat, öffnen Sie http://localhost:8080/REST, klicken Sie auf den Firebug-Käfer, aktivieren Sie unter 'Netzwerk' die 'Netzwerk'-Checkbox, und klicken Sie auf der Bücherverwaltungs-Webseite auf den Button 'Neues Buch anlegen'.

Klicken Sie auf die drei Tabulatorreiter 'Header', 'Post' und 'Antwort', um in etwa Folgendes angezeigt zu bekommen:

Firebug



Links auf weiterführende Informationen




Weitere Themen: andere TechDocs | SOA
© 2009 Torsten Horn, Aachen