Kategorie-Archiv: Java EE

Kommerzielle Unterstützung für GlassFish läuft aus

Quelle:

Bemerkungen dazu unter

Oracle handelt in meinen Augen (wieder einmal) aus rein ökonomischen Interessen; es geht nicht gut, zwei Produkte in einem Marktsegment zu positionieren, die Geschichte hatten wir schon mit Oracle Database und MySQL.

Möglicherweise wird GlassFish (und die assoziierten Java EE-Teile, wie Web-Services, OR-Mapper) eine Spielwiese für Technologien, die dann in WebLogic übertragen werden, wenn sie funktionieren, und dort dann exklusiv verbessert und optimiert.

Ob ich Kunden nun zu GlassFish raten würde? Eher nicht. Bis gestern war ich absoluter GF-Fan und habe den Server immer empfohlen, auch gegen den starken JBoss, doch wenn kein kommerzieller Support vorhanden ist, werden große Unternehmen ihn schlicht nicht einsetzen wollen.

Bye Bye GF

Session-Verwaltung in Servlets mit HttpSession

Die Servlet-API bietet die Klasse HttpSession an, eine Bibliothek auf hohem Niveau für die Verwaltung einer Sitzung. Sie basiert entweder auf Cookies oder URL-Rewriting, doch wird das von der API transparent gehalten. Als Programmierer bekommen wir so gut wie gar nichts davon mit. Falls der Client keine Kekse mag, wandeln wir alle Informationen in URLs um, die wir dann anbieten. Ein Sitzungsobjekt verwaltet die gesicherten Daten auch selbstständig in einer Datenstruktur. Hier fällt für uns keine Arbeit an.

Das mit einer Sitzung verbundene Objekt HttpSession

Jede Sitzung ist mit einem Sitzungsobjekt verbunden, das die Klasse HttpSession abbildet. Bei JSPs repräsentiert das implizite Objekt session die aktuelle Sitzung.

Werte mit einer Sitzung assoziieren und auslesen

Um Informationen mit der Sitzung zu verbinden, verwenden wir die Methode setAttribute(), die einen Schlüssel und einen Wert verbindet. Daten werden mit getAttribute() wieder aus der Datenstruktur gelockt, so wie es das folgende Beispiel zeigt:

List l = (List) session.getAttribute( "artikel" );

Hier verbinden wir mit einem Schlüssel eine Liste von Waren. Im Hintergrund werden die Informationen auf der Serverseite gesichert. Die Informationen selbst werden nicht in Cookies oder in der URL abgelegt, daher spielt die Größe der Daten auch keine Rolle. Ein HttpSession-Objekt verwaltet einen Assoziativspeicher, der die Wertepaare speichert. Es ist günstig, die Elemente serialisierbar zu gestalten, um die Daten dauerhaft zu speichern.

Werfen wir abschließend einen Blick auf das Programmstück, das eine neue Ware hinzufügt:

List l = (List) session.getAttribute( "artikel" );
if ( l == null )
{
  l = new ArrayList();
  session.setAttribute( "artikel", l );
}
l.add( w );

interface javax.servlet.http.HttpSession

  • Object getAttribute( String name )

    Liefert das mit name verbundene Objekt; null, wenn es keine Assoziation gab.
  • Enumeration getAttributeNames()

    Liefert eine Aufzählung aller mit der Sitzung verbundenen Objekte.
  • void setAttribute( String name, Object value )

    Bindet name mit dem Objekt value an die Sitzung. Existierte das Objekt, wird es ersetzt. Angemeldete HttpSessionBindingListener werden über die Methode value Bound() beziehungsweise valueUnbound() informiert.
  • void removeAttribute( String name )

    Entfernt das Attribut von der Sitzung. Ungültige Namen werden ignoriert. HttpSession BindingListener werden durch Aufruf von valueUnbound() informiert.

Alle Methoden liefern eine IllegalStateException, wenn die Sitzung ungültig ist. Die Methoden putValue() und setValue() sind veraltet und wurden durch setAttribute() und getAttribute() ersetzt.

URL-Rewriting

Das Session-Management sollte im Prinzip unabhängig von der technischen Umsetzung sein. Doch leider greift das Sitzungsmanagement beim URL-Rewriting schon sehr stark ein: Bei jedem Verweis auf eine neue Seite muss die URL entsprechend angepasst werden, weil die Sitzungs-ID mitgeschickt werden muss. Cookies verwalten die Sitzungs-ID völlig anders. Das bedeutet: Werden Cookies eingesetzt, ändert sich die URL nicht und jeder kann problemlos auf eine neue Seite verweisen. Nur beim URL-Rewriting muss an die URL eine Sitzungskennung angehängt werden.

Beispiel Eine URL für einen Cookie besitzt keine Sitzungskennung.

  • http://localhost/servlet/URLRewritingSession

Mit URL-Rewriting sieht das dann etwa so aus:

  • http://localhost/servlet/URLRewritingSession;jsessionid=abcde234

Wenn wir innerhalb eines Servlets auf eine andere generierte Seite verweisen wollen, haben wir eine URL vor uns, zu der wir verzweigen möchten. Die Servlet-API kümmert sich darum, an eine Benutzer-URL die Sitzungs-ID automatisch anzuhängen. Dazu dienen die HttpServletResponse-Methoden encodeURL() und encodeRedirectURL().

Beispiel: Aufgrund einer Formularbestätigung soll auf eine JSP-Seite mit dem Namen validate.jsp verwiesen werden:

<form action='<%= response.encodeURL("/validate.jsp") %>'>

Werden der Verweis und die Kodierung aus Versehen vergessen, ist dies das Ende der Sitzung. Ob eine Sitzung mit einem Cookie behandelt wird, lässt sich mit isRequestedSessionIdFromCookie() testen. Dann kann aufgrund einer Fallunterscheidung encodeURL() verwendet werden oder nicht. Allgemein ist es aber nicht schlecht, grundsätzlich alle Verweise innerhalb einer Webapplikation mit encodeURL() zu sichern. Im Fall von Cookies wird zwar keine Kennung angehängt, eine spätere Umstellung gestaltet sich aber einfacher, falls der Nutzer die Cookies einmal ausschaltet.

Zusätzliche Informationen

Ein Sitzungsobjekt verwaltet neben den assoziierten Daten noch weitere Informationen. Jede Sitzung bekommt eine eindeutige ID, die sich mit getId() erfragen lässt. Ist die Sitzung neu und hat der Client noch nie eine Verbindung gehabt, gibt isNew() den Wert true zurück. Existiert dann die Sitzung, gibt getCreationTime() ein long zurück – kodiert sind wie üblich die vergangenen Millisekunden seit dem 1.1.1970 –, in dem sich das Erstellungsdatum erfragen lässt. Dagegen erfragt getLastAccessedTime() die Zeit, die seit dem letzten Zugriff durch den Client vergangen ist. Falls der Server die Informationen dauerhaft speichert und der Cookie nicht abläuft, erlaubt dies Meldungen der Art: »Schön, Sie nach zwei Wochen zum fünften Mal bei unserer Partnervermittlung wiederzusehen. Hat’s wieder nicht geklappt?«

Das Ende der Sitzung

Eine Sitzung ist nicht automatisch unendlich lange gültig. Bei Cookies lässt sich der Gültigkeitszeitraum einstellen. Auch Sitzungsobjekte lassen sich in der Zeit anpassen. Die Methode setMaxInactiveInterval() setzt den Wert, wie lange eine Sitzung gültig ist. Ist der Wert negativ, zeigt er an, dass die Sitzung nicht automatisch beendet wird. Die entsprechende Methode getMaxInactiveInterval() liefert die Zeit in Sekunden, in der eine Sitzung gültig ist.

interface javax.servlet.http.HttpSession

  • long getCreationTime()

    Gibt in Millisekunden ab dem 1.1.1970 an, wann die Sitzung eröffnet wurde.
  • String getId()

    Liefert eine eindeutige Kennung, die die Sitzung identifiziert.
  • long getLastAccessedTime()

    Gibt in Millisekunden ab dem 1.1.1970 zurück, wann der Client zum letzten Mal auf den Server zugegriffen hat.
  • int getMaxInactiveInterval()
  • void setMaxInactiveInterval( int interval )

    Liefert und setzt die Zeit, für die der Servlet-Container die Sitzung aufrechterhalten soll, bis sie ungültig wird.
  • boolean isNew()

    Der Rückgabewert ist true, wenn die Sitzung neu ist.

Beispiel Zum Schluss wollen wir ein Programm formulieren, das alle diese Informationen auf einmal ausgibt.

<%@ page language="java" import="java.util.*" %>
<%
int cnt = 0;
if ( session.isNew() )
{
out.println( "Willkommen Neuling!\n" );
}
else
{
out.println( "Hallo, alter Freund!\n" );
String o = (String) session.getAttribute( "cnt" );
if ( o != null )
cnt = Integer.parseInt( o );
cnt++;
}
session.setAttribute( "cnt", ""+cnt );
%>
<p>
Session-ID: <%= session.getId() %> <p>
Erzeugt am: <%= new Date(session.getCreationTime()) %> <p>
Letzter Zugriff: <%= new Date(session.getLastAccessedTime()) %> <p>
Ungültig in Minuten: <%= session.getMaxInactiveInterval()/60 %> <p>
Anzahl Zugriffe: <%= cnt %>

Das Programm liefert beispielsweise folgende Ausgabe:

Hallo, alter Freund!
ID: 91410050092487D9B5D0D2A7A3D0F072
Erzeugt am: Fri Jan 18 20:16:49 CET 2002
Letzter Zugriff: Fri Jan 18 20:23:33 CET 2002
Ungültig in Minuten: 30
Anzahl Zugriffe: 4

Die ID sieht bei jedem Server anders aus. Der Webserver von Sun erzeugt beispielsweise ganz andere Kennungen.

Automatisches Neuladen von Servlet-Seiten

Über das implizite Objekt response lassen sich Antworten von der JSP-Seite an den Client formulieren. Das Setzen von Content-Type ist für nahezu alle Servlets unabdingbar. Daneben gibt es noch weitere, die beispielsweise für Cookies interessant sind. Ein spezieller Header kann auch das Caching beeinflussen (mit dem Datum der letzten Modifizierung) oder die Seite nach einer bestimmten Zeit neu laden. Letzteres wollen wir verwenden, um eine einfache Ausgabe zu erzeugen, die jede Sekunde neu geladen wird. (Die Seite darf jedoch nicht im Cache liegen. Um das Caching explizit auszuschalten, sollte Pragma: no-cache gesetzt werden. Bei einer lokalen Installation spielt dies jedoch keine Rolle.

<%! private String result = "*"; %>
<% response.setHeader( "Refresh", "1" ); %>
<%= result += "*" %>

Dieses Servlet erzeugt eine Reihe von Sternchen, wobei es sich die Zeichenkette jede Sekunde neu vom Server holt. Das dargestellte Programm zeigt in einfacher Weise auf, was sich noch wesentlich komplexer mit Threads anstellen lässt. Im Hintergrund hätten wir einen Thread starten können, der ständig eine neue Berechnungen durchführt, die wir dann in der println()-Zeile hätten ausgeben können.

In Servlets Seiten über HTTP-Redirect umlenken

Ist eine Seite nicht mehr korrekt, kann sie umgelenkt werden. Hierfür wird ein spezieller Header gesetzt.

sendRedirect()

Dazu dient die Methode sendRedirect(String), die auf eine neue Seite verweist. Als Argument kann eine relative oder absolute URL aufgeführt werden, die auf eine temporäre neue Seite weist. Wir könnten auch mit setHeader() arbeiten, müssten dann aber von Hand den Statuscode ändern, der für Umleitungen auf 302 gesetzt sein muss. Die Arbeit können wir uns sparen. Nach dem Setzen der Umleitung sollte nicht mehr in die Ausgabe geschrieben werden.

Wozu kann nun diese Umleitung eingesetzt werden? Zum Beispiel, um über Formular-Parameter zu externen Seiten weiterzuleiten:

response.sendRedirect( url );

Nach der Umleitung steht der Ort der neuen Seite in der URL-Zeile des Browsers. Das folgende Programm verweist nun einfach auf ein anderes Servlet. Die Pfadangabe kann absolut oder relativ sein.

String url = "http://www.tutego.de/";
response.sendRedirect( url );

Was passiert beim Umlenken?

Technisch gesehen ist eine Umlenkseite eine ganz normale Webseite. Das wirkliche Umlenken ist eine Fähigkeit des Browsers und nicht des Servers. Dies ist wichtig anzumerken, weil eigene Programme, die URL-Verweise aufbauen, hier oft nicht korrekt vorgehen.

Das Servlet setzt beim sendRedirect() den Content-Type auf "text/html". Wichtig sind zwei weitere Informationen: die eine in der Statuszeile und die andere im Header. In der Statuszeile wird die Nummer 302 gesendet, die das Umlenken bezeichnet. Die Information darüber, wohin verwiesen wird, steht in einem weiteren Header namens »Location«. Somit können wir unser Redirect prinzipiell auch selbst ausformulieren, indem wir Folgendes schreiben:

response.setStatus( 302 );
response.setContentType( "text/html" );
response.setHeader( "Location", url );

Der String url ist dann eine Referenz auf die neue Seite. Der Verweis auf die externe Seite muss dann natürlich absolut sein. Dies regelt jedoch sendRedirect() automatisch.

Parametersammlungen im Servlet mit getParameterValues() auslesen

Da ein Parameter auch mehr als einen Wert haben kann, hilft getParameter() nicht weiter, da dieser nur jeweils einen Wert liefert. Hier führt die Methode getParameterValues() zum Ziel, die ein Feld von Strings zurückgibt. (Damit ist kein zusammengesetzter String etwa für Suchmaschinen gemeint.) Sind wir an einer vollständigen Aufzählung der Schlüssel interessiert, liefert getParameterNames() ein Objekt vom Typ Enumeration zurück, mit dem wir durch das Feld wandern und die Werte mit getParameter() erfragen können.

<%
java.util.Enumeration paramNames = request.getParameterNames();
while ( paramNames.hasMoreElements() )
{
 String param = (String) paramNames.nextElement();
 out.print( "<p>" + param + " = " );
 String[] paramValues = request.getParameterValues( param );
 if ( paramValues.length == 1 )
 {
  String paramValue = paramValues[0];
  if ( paramValue.length() == 0 )
   out.println( "unbestimmt" );
  else
   out.println( paramValue );
 }
 else
 {
 for ( int i = 0; i < paramValues.length; i++ )
  out.print( paramValues[i] + " " ) ;
  out.println( "<p>" );
 }
}
%>

Wenn wir das Programm mit der Zeile

http://localhost:8080/jt/parameterNames.jsp?a=1&b=2&c=&a=2

im Browser starten, erzeugt das Servlet folgende Ausgabe:

b = 2
a = 1 2
c = unbestimmt

Wir sehen, dass alle Parameter hier aufgeführt sind, doch in unbestimmter Reihenfolge. Dies ist aber egal. Das Programm erkennt, ob ein Wert gesetzt ist oder nicht beziehungsweise ob einem Schlüssel ein Wert zweimal zugewiesen wurde.

HttpServletRequest und HttpServletResponse und die Header

Sendet der HTTP-Client eine Anfrage an den Server, so sendet er gleichzeitig einige Informationen über sich mit. Sie nennen sich Header und bezeichnen Schlüssel-Werte-Paare, die durch einen Doppelpunkt getrennt sind. Ein Webbrowser kann zum Beispiel Folgendes formulieren:

GET /seminare/index.html HTTP/1.0
Accept-Language: de

Der Browser sendet hier den Header Accept-Language mit dem Wert de. So kann der Server unter Auswertung dieser Parameter optimal reagieren, zum Beispiel bei der Präferenz der Sprache eine lokalisierte Webseite liefern. Um an die Header zu gelangen, müssen wir das HttpServletRequest-Objekt lesen und die Header erfragen.

Header auslesen

Zum Lesen der Header in einem Servlet bieten sich zwei Lösungen an: Wenn wir einen speziellen Header erfragen wollen, dann holen wir mit getHeader() auf dem HttpServletRequest den passenden Wert zum Schlüssel. Sind wir an allen Schlüsseln interessiert, dann besorgt uns getHeaderNames() eine Enumeration. Die können wir dann durchlaufen und die Werte wiederum mit getHeader() auslesen. Falls ein Schlüssel nicht existiert, liefert die Methode null. Ähnlich wie bei getParameter() können hier auch mehrere Einträge existieren, die mit getHeaders() abgerufen werden können.

<%
java.util.Enumeration headerNames = request.getHeaderNames();
while ( headerNames.hasMoreElements() )
{
String headerNameKey = (String) headerNames.nextElement();
String headerNameValue = request.getHeader( headerNameKey );
%>
<%= headerNameKey %>: <%= headerNameValue %>
<p>
<%
}
%>

Das Servlet erzeugt für eine Anfrage etwa folgende Ausgabe:

accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
accept-language: de
accept-encoding: gzip, deflate
user-agent: Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 5.0)
host: localhost:8080
connection: Keep-Alive
cookie: JSESSIONID=EB9D8DFAB0D0AA1B38D292507983B6B1

Der Anfragetyp (GET, POST und so weiter) wird hier ebenso wenig angezeigt wie der Remote-Host. Dieser findet sich nicht im Header und muss mit anderen Funktionen erfragt werden.

Hilfsfunktion im Umgang mit Headern

Wieder gibt es für oft benutzte Header Abkürzungen.

  • getMethod() liefert eine Zeichenkette wie GET oder POST.
  • Die Methode getRequestURI() liefert die URI der Anfrageseite.
  • getProtocol() liefert das Protokoll von der Statuszeile, also heutzutage entweder HTTP/1.0 oder HTTP/1.1.
  • getCookies() liefert den Inhalt des Cookie-Headers (dazu später mehr).
  • getAuthType() und getRemoteUser() zerteilen die Information im Authorization-Feld in Komponenten.
  • getDateHeader() und getIntHeader() sind wieder Hilfsmethoden.

Übersicht der Browser-Header

Hier eine Übersicht über die üblichen Header, von denen wir manche schon aus dem Beispiel und auch vom Server kennen:

  • Accept. Der vom Browser bevorzugte MIME-Typ.
  • Accept-Charset. Der vom Browser bevorzugte Zeichensatz.
  • Accept-Encoding. Die Kodierung, die der Browser verarbeiten kann, wie etwa gzip oder compress. Unser Servlet-Programm sollte vor dem Komprimieren testen, ob der Browser überhaupt komprimierte Dateien verarbeiten kann.
  • Accept-Language. Die Sprache, die der Browser bevorzugt anzeigt. Mehr als ein Eintrag, wenn der Browser mehr als eine Sprache spricht.
  • Authorization. Information über Autorisierung, die normalerweise eine Antwort auf die WWW-Authenticate-Anfrage des Servers ist.
  • Connection. Informiert, ob persistente Verbindungen genutzt werden. Persistente Übertragungen übermitteln in einer TCP/IP-Verbindung mehrere Dateien, etwa eine HTML-Datei und mehrere Grafiken. Wenn der Wert von Connection »Keep-Alive« heißt, dann lassen sich mit einer Netzwerkverbindung mehrere Seitenteile übermitteln. Wenn die Request-Zeile die http-Version 1.1 anzeigt, sind Keep-Alive-Verbindungen Standard. Unsere Aufgabe bei diesen Verbindungen ist es, den Header ContentLength in die Antwort zu setzen. Server-abhängig wird hier teilweise schon automatisch in einen Puffer geschrieben und die Größe gesetzt. Dies muss aber nicht so sein, daher bietet es sich an, die Informationen in einen ByteArrayOutputStream zu schreiben, um später die Länge und den Inhalt abzufragen.
  • Content-Length. Die Länge des Bytestroms. Hier zählt der Browser die Bytes und informiert den Server, wie viele Daten noch kommen.
  • Cookie. Cookie-Information, die der Browser automatisch mitschickt.
  • From. Ein optionaler Header, der oft von Webrobotern gesetzt wird. Bei Browsern nicht üblich.
  • Host. Rechnername und Host, wie in der Original-URL angegeben.
  • If-Modified-Since. Liefert ein neues Server-Dokument, wenn die im Header angegebene Zeit auf ein neueres Dokument verweist. Ist das Browser-Dokument aktueller, gibt der Server den Antwortcode 304 mit der Nachricht »Not Modified« zurück.
  • Pragma. Gibt Informationen über das automatische Neuladen der Seiten. Der Wert no-cache zeigt an, dass der Server immer eine neue Seite liefern soll, auch wenn der Proxy eine Kopie hält.
  • Referer. Die URL mit dem Verweis, der auf die aktuelle Seite gezeigt hat.
  • User-Agent. Der Browsertyp. Praktisch, wenn unser Servlet JavaScript-Code einbettet, der vom Browser abhängig ist.
  • UA-Pixels, UA-Color, UA-OS, UA-CPU. Von Microsoft eingeführte proprietäre Header für den Internet Explorer, die Bildschirmgröße, Farbtiefe, Betriebssystem und CPU-Typ anzeigen.

Header, die der Server setzt

Bisher kennen wir von der Klasse HttpServletResponse die Methode setHeader() für beliebige Header.

Beispiel: Setze den Header pragma, damit vom Browser keine Daten im Cache gehalten werden:

response.setHeader( "pragma", "no-cache" );

Mit dieser Aufforderung soll der Browser die Seite jedes Mal neu laden. Das ist bei dynamischen Seiten besonders wichtig, da sie bei jedem Aufruf neu generiert werden und sich Werte ändern können, wie es zum Beispiel bei Warenkorbsystemen der Fall ist. Da wir uns als Applikationsentwickler nicht immer mit dem Namen der Header herumärgern wollen, bietet die Bibliothek einige Spezialfunktionen an.

Beispiel: Für den Header Content-Type gibt es die spezielle Methode setContentType():

response.setHeader( "Content-Type", "text/html");
response.setContentType( "text/html" );

Daneben gibt es setContentLength(), die den Header Content-Length setzt. Diese Länge muss nicht gesetzt werden und wird automatisch berechnet. Falsche Längen könnten zu Ausnahmesituationen führen. Der Gebrauch ist jedoch nützlich, wenn vorher die gesamte Webseite in einem StringBuffer sb gesammelt und in einem Rutsch übertragen wird. Dann können wir setContentLength(sb.length()) aufrufen.

Um einen Datums-Header zu setzen, existiert setDateHeader(String, long). Das Argument ist eine beliebige Zeichenkette, die mit einem Datumswert verbunden wird. Das long gibt die Millisekunden seit dem 1.1.1970 an. Die erzeugte Ausgabe schreibt einen UTC-String. Eine weitere Hilfsfunktion ist setIntHeader(), die Zahlenwerte mit Schlüsseln in den Header schreibt. Hier übernimmt die Methode die Konvertierung von String in eine Ganzzahl.

Neben diesen setXXX()-Methoden, die möglicherweise gesetzte Header überschreiben, lässt sich mit containsHeader(String) abfragen, ob Wertepaare schon gesetzt sind. Neben den setXXX()-Methoden gibt es auch entsprechende addXXX()-Methoden, die die Werte nicht überschreiben, sondern hinzufügen. Für Cookies existiert eine zusätzliche Methode namens addCookie(), die einen Cookie im Header setzt.

RESTful Web-Services mit JAX-RS und Jersey 2

Dieser Abschnitt stellt das Architekturprinzip REST vor und anschließend den Java-Standard JAX-RS. Es folgen Beispiele mit der JAX-RS-Referenzimplementierung Jersey.

1.1.1 Aus Prinzip REST

Bei RESTful Services liegt das Konzept zugrunde, dass eine Ressource über einen Web-Server verfügbar ist und eindeutig über eine URI identifiziert wird.

Da unterschieden werden muss, ob eine Ressource neu angelegt, gelesen, aktualisiert, gelöscht oder aufgelistet werden soll, werden dafür die bekannten HTTP-Methoden verwendet.

HTTP-Methode

Operation

GET

Listet Ressourcen auf oder holt eine konkrete Ressource.

PUT

Aktualisiert eine Ressource.

DELETE

Löscht eine Ressource oder eine Sammlung von Ressourcen.

POST

Semantik kann variieren, in der Regel aber geht es um das Anlegen einer neuen Ressource.

Tabelle 44.1: HTTP-Methoden und übliche assoziierte Operationen

Anders als SOAP ist REST kein entfernter Methodenaufruf, sondern eher vergleichbar mit den Kommandos INSERT, UPDATE, DELETE und SELECT in SQL.

Ursprung von REST

Die Idee, Web-Services mit dem Web-Standard HTTP aufzubauen, beschrieb Roy Thomas Fielding im Jahr 2000 in seiner Dissertation »Architectural Styles and the Design of Network-based Software Architectures«. Das Architekturprinzip nennt er Representational State Transfer (REST) – der Begriff ist neu, aber das Konzept ist alt. Aber so wie im richtigen Leben setzten sich manche Dinge erst spät durch. Das liegt auch daran, dass SOAP-basierte Web-Services immer komplizierter wurden und sich die Welt nach etwas anderem sehnte. (Daher beginnen wir im Buch auch mit REST, und dann wird SOAP folgen.)

Wie sieht eine REST URI aus?

Auf der einen Seite stehen die HTTP-Methoden GET, PUT, POST, DELETE und auf der anderen Seite die URIs, die die Ressource kennzeichnen. Ein gutes Beispiel einer REST-URL ist der Blog vom Java-Buch:

http://www.tutego.de/blog/javainsel/category/java-8/page/2/

Das Ergebnis ist ein HTML-Dokument mit ausschließlich den Beiträgen aus der Kategorie Java 8 und nur denen auf der zweiten Seite.

Fast daneben ist auch vorbei

Da auf den ersten Blick jede HTTP-Anfrage an einen Web-Server wie ein REST-Aufruf aussieht, sollten wir uns im Klaren darüber sein, was denn kein REST-Auruf ist. Eine URL wie http://www.bing.com/search?q=tutego ist erst einmal nicht der typische REST-Stil, da es keine Ressource gibt, die angesprochen wird. Da REST als leichtgewichtig und cool gilt, geben sich natürlich gerne Dienste ein bisschen RESTig. Ein Beispiel ist Flickr, ein Fotoservice von Yahoo. Das Unternehmen wirbt mit einer REST-API, aber es ist alles andere als REST und kein gutes Beispiel.[1] Das Gleiche gilt auch für Twitter, das lediglich einen rudimentären REST-Ansatz hat, aber in der Öffentlichkeit als REST pur wahrgenommen wird.

Jersey

Für Java gibt es mit JAX-RS einen Standard zum Deklarieren von REST-basierten Web-Services. Er wurde in der JSR-311, »JAX-RS: The Java API for RESTful Web Services«, definiert. Es fehlt noch eine Implementierung, damit wir Beispiele ausprobieren können.

  • Jeder Applikationsserver ab Java EE 6 enthält standardmäßig eine JAX-RS-Implementierung.
  • Da wir JAX-RS ohne einen Applikationsserver mit der normalen Java SE ausprobieren möchten, ist eine JAX-RS-Implementierung nötig, da das JDK die JAX-RS-API nicht mitbringt. Es ist naheliegend, die JAX-RS-Referenzimplementierung Jersey (http://jersey.java.net/) zu nutzen, die auch intern von Applikationsservern verwendet wird.
  • Mit Jersey lässt sich entweder ein Servlet-Endpunkt definieren, sodass der RESTful Web-Service in einem Servlet-Container wie Tomcat läuft, oder der eingebaute Mini-HTTP-Server nutzen. Wir werden im Folgenden mit dem eingebauten Server arbeiten.

    Download und Jar-Dateien

    Beginnen wir mit dem Download der nötigen Java-Archive. Die Jersey-Homepage http://jersey.java.net/ verweist für den Download auf http://jersey.java.net/download.html und klick wir dort auf auf

    Jersey JAX-RS 2.1 RI bundle bundle contains the JAX-RS 2.0 API jar, all the core Jersey module jars as well as all the required 3rd-party dependencies.

    dann startet der Download von jaxrs-ri-2.1.zip. (Die Datei liegt physikalisch auf den Serverm vom Maven Central Repository.) Das ca. 4,5 MiB großen Zip-Archivs können wir auspacken und dann folgende Archive in den Klassenpfad aufnehmen:

  • javax.ws.rs-api-2.0.jar (aus dem Ordner api): enthält die JAX-RS-API, aber keine Implementierung
  • jersey-client.jar, jersey-common.jar, jersey-server.jar (aus Order lib): Jersey-Implementierung
  • *.jar (aus dem Ordner ext): Auf was die Jersey-Implementierung selbst zurückreift, um es einfach zum machen einfach alles
  • Ein Jar müssen wir noch in den Klassenpfad dabei setzen, damit der Jersey-Server den im JDK eingebauten HTTP-Server nutzen kann:

  • http://repo1.maven.org/maven2/org/glassfish/jersey/containers/jersey-container-jdk-http/2.0/jersey-container-jdk-http-2.0.jar
  • 1.1.3 JAX-RS-Annotationen für den ersten REST-Service

    JAX-RS definierte einige Annotationen, die zentrale Konfigurationen bei RESTful Web-Services vornehmen, etwa Pfadangaben, Ausgabeformate oder Parameter. In den nächsten Kapiteln werden diese Annotationen an unterschiedlichen Beispielen vorgestellt.

    Die Annotationen @Path und @GET

    Beginnen wir mit einem REST-Service, der einen simplen Textstring liefert.

    Listing 44.1: com/tutego/insel/rest/MessageResource.java

    package com.tutego.insel.rest;
    import javax.ws.rs.*;
    @Path( "message" )
    public class MessageResource {

    @GET
    @Produces( MediaType.TEXT_PLAIN )
      public String message() {
        return "Yea! ";
      }
    }

    Die ersten beiden Annotationen sind zentral:

    • @Path: Die Pfadangabe, die auf den Basispfad gesetzt wird.
    • @GET: Die HTTP-Methode. Hier gibt es auch die Annotationen für die anderen HTTP-Methoden POST, PUT, …
    • @Produces: Die Annotation kann zwar grundsätzlich auch entfallen, aber besser ist es, deutlich einen MIME-Typ zu setzen. Es gibt String-Konstanten für die wichtigsten MIME-Typen, wie MediaType.TEXT_XML oder MediaType.TEXT_HTML, und auch Strings wie »application/pdf« können direkt gesetzt werden.

    1.1.4 Test-Server starten

    Ein Java EE-Application-Server würde die Klasse aufgrund der Annotationen gleich einlesen und als REST-Service anmelden. Da wir die Klasse jedoch in der Java SE ohne Application-Server verwenden, muss ein REST-Server von Hand aufgebaut werden. Das ist aber problemlos, denn dafür haben wir jersey-container-jdk-http-2.0.jar eingebunden, sodass Jersey sich in den im JDK eingebauten HTTP-Server integriert.

    Listing 44.2: com/tutego/insel/rest/StartRestServer.java, main()

    ResourceConfig rc = new ResourceConfig().packages( "com.tutego.insel.rest" );

    HttpServer server = JdkHttpServerFactory.createHttpServer( URI.create( "http://localhost:8080/rest" ), rc);

    JOptionPane.showMessageDialog( null, "Ende" );

    server.stop( 0 );

    Nach dem Start des Servers scannt Jersey die Klassen im genannten Paket daraufhin ab, ob sie passende JAX-RS-Annotationen tragen. Das Class-Scanning wurde aktiviert mit der package(…)-Methode beim ResourceConfig, es ist aber auch möglich im Konstruktor von ResourceConfig direkt die JAW-RS-Klassen aufzuzählen.

    Die statische Methode JdkHttpServerFactory.createHttpServer(…) liefert ein Objekt vom Typ com.sun.net. httpserver.HttpServer, das Teil der internen Java-API ist. Die JdkHttpServerFactory stammt aus dem internen Jersey-Paket.

    1.1.5 REST-Services konsumieren

    Bei createHttpServer(…) ist als Testpfad "http://localhost:8080/rest" eingetragen, und zusammen mit @Path("message") an der Klasse ergibt sich der Pfad http://localhost:8080/rest/message für die Ressource.

    REST-Client mit Jersey

    Sich im Browser das Ergebnis eines REST-Aufrufs anzuschauen ist nicht das übliche Szenario. Oft sind es JavaScript-Programme im Browser, die REST-Aufrufe starten und die konsumierten Daten auf einer Web-Seite integrieren.

    Ist es ein Java-Programm, was einen REST-Dienst anzapft, so kann dies mit einer einfachen URLConnection erledigt werden, was eine Standardklasse aus dem java.net-Paket ist, um auf HTTP-Ressourcen zuzugreifen. Doch Jersey definiert eine standardisierte API zum einfachen Zugriff. Es reicht ein Einzeiler:

    System.out.println(

    ClientBuilder.newClient().target( "http://localhost:8080/rest" )

    .path( "message" ).request().get( String.class )

    );

    Der API-Stil, den die Bibliotheksautoren hier verwenden, nennt sich Fluent-API; Methodenaufrufe werden verkettet, um alle Parameter wie URL, MIME-Typ für die Anfrage zu setzen. Das ist eine Alternative zu den bekannten Settern auf JavaBeans.

    Um die Typen bei der javax.ws.rs.client-API etwas besser zu verstehen, schreiben wir alles ganz vollständig aus:

    Listing 44.3: com/tutego/insel/rest/MessageJerseyClient.java, main()

    Client client = ClientBuilder.newClient();

    WebTarget target = client.target( "http://localhost:8080/rest" );

    WebTarget resourceTarget = target.path( "message" );

    Invocation.Builder request = resourceTarget.request( MediaType.TEXT_PLAIN );

    Response response = request.get();

    System.out.println( response.readEntity( String.class ) );

    Das Response-Objekt hat auch Methode getStatus() für den HTTP-Status-Code und hasEntry(), ob es ein Ergebnis gibt und kein Fehler. Apropos Fehler. Ist die URL falsch, gibt es eine RuntimeExcection – geprüfte Ausnahmen verwendet Jersey nicht.

    Exception in thread "main" javax.ws.rs.NotFoundException: HTTP 404 Not Found

    1.1.6 Content-Hander, Marshaller und verschiedene MIME-Typen

    JAX-RS erlaubt grundsätzlich alle MIME-Typen, und die Daten selbst können auf verschiedene Java-Datentypen übertragen werden. So ist es egal, ob bei Textdokumenten zum Beispiel der Rückgabetyp String oder OutputStream ist; selbst ein File-Objekt lässt sich zurückgeben. Für einen Parametertyp – Parameter werden gleich vorgestellt – gilt das Gleiche: JAX-RS ist hier recht flexibel und kann über einen InputStream oder Writer einen String entgegennehmen. (Reicht das nicht, können sogenannte Provider angemeldet werden.)

    Bei XML-Dokumenten kommt hinzu, dass JAX-RS wunderbar mit JAXB zusammenspielt.

    XML mit JAXB

    Dazu ein Beispiel für einen Dienst hinter der URL http://localhost:8080/rest/message/serverinfo, der eine Serverinformation im XML-Format liefert. Das XML wird automatisch von JAXB generiert.

    Listing 44.4: com/tutego/insel/rest/MessageResource.java, MessageResource Ausschnitt

    @Path( "message" )
    public class MessageResource {

    @GET
    @Path( "serverinfo" )
    @Produces( MediaType.TEXT_XML )
      public ServerInfo serverinfo() {
        ServerInfo info = new ServerInfo();
        info.server = System.getProperty( "os.name" )+" "+System.getProperty( "os.version" );
        return info;
      }
    }
    @XmlRootElement
    class ServerInfo {
      public String server;
    }

    Die Klasse ServerInfo ist eine JAXB-annotierte Klasse. In der eigenen JAX-RS-Methode serverinfo() wird dieses ServerInfo-Objekt aufgebaut, das Attribut gesetzt und dann zurückgegen; der Rückgabetyp ist also nicht String wie vorher, sondern explizit ServerInfo. Dass der MIME-Typ XML ist, sagt @Produces(MediaType.TEXT_XML). Und noch eine Annotation nutzt das Beispiel: @Path. Lokal an der Methode bedeutet es, dass der angegebene Pfad zusätzlich zur Pfadgabe an der Klasse gilt. Also ergibt sich der komplette Pfad aus:

    Basispfad + "message" + "/" + "serverinfo"

    JSON-Serialisierung *

    Ist der Client eines REST-Aufrufs ein JavaScript-Programm in einem Web-Browser, ist es in der Regel günstiger, statt XML das Datenformat JSON zu verwenden. JAX-RS bindet dabei die JABX-annotiereten Objekte nicht nur an XML, sondern auch an JSON. Dazu muss lediglich eine kleine Änderung bei den MIME-Typen vorgenommen werden:

    @Produces( MediaType.APPLICATION_JSON )

    Das reicht schon aus, und der Client empfängt ein JSON-serialisiertes Objekt.

    Damit die Erzeugung von JSON auch mit der Referenzimplementierung Jersey funktioniert, müssen weitere Klassen in den Klassenpfad aufgenommen werden – daher überspringen wir JSON als Format.

    Jersey-Client

    Die JAX-RS-API bietet mit dem MIME-Typ noch eine Besonderheit, dass der Server unterschiedliche Formate liefern kann, je nachdem, was der Client verarbeiten möchte oder kann. Der Server macht das mit @Produces klar, denn dort kann eine Liste von MIME-Typen stehen. Soll der Server XML und JSON generieren können, schreiben wir:

    Listing 44.5: com/tutego/insel/rest/MessageResource.java, Ausschnitt

    @GET
    @Path( "serverinfo" )
    @Produces( { MediaType.TEXT_XML, MediaType.APPLICATION_JSON } )
    public ServerInfo serverinfo()

    Kommt der Client mit dem Wunsch nach XML, bekommt er XML, möchte er JSON, bekommt er JSON. Die Jersey-Client-API teilt über request(String) bzw. request(MediaType… types) mit, was ihr Wunschtyp ist. (Dieser Typwunsch ist eine Eigenschaft von HTTP und nennt sich Content Negotiation.)

    Listing 44.6: com/tutego/insel/rest/XmlJsonMessageJerseyClient, main()

    /* Nur mit zusätzlichem JSON-Jar im Klassenpfad!

    WebTarget wr1 = ClientBuilder.newClient().target( "http://localhost:8080/rest" );

    Builder b1 = wr1.path( "message" ).path( "serverinfo" )

    .request( MediaType.APPLICATION_JSON );

    System.out.println( b1.get( ServerInfo.class ).server ); // Windows Vista 6.0

    */

    WebTarget wr2 = ClientBuilder.newClient().target( "http://localhost:8080/rest" );

    Builder b2 = wr2.path( "message" ).path( "serverinfo" ).request( MediaType.TEXT_XML );

    System.out.println( b2.get( ServerInfo.class ).server ); // Windows Vista 6.0

    WebTarget wr3 = ClientBuilder.newClient().target( "http://localhost:8080/rest" );

    Builder b3 = wr3.path( "message" ).path( "serverinfo" ).request( MediaType.TEXT_PLAIN );

    System.out.println( b3.get( ServerInfo.class ).server ); // NotAcceptableException

    Passt die Anfrage auf den Typ von @Produces, ist alles prima, ohne Übereinstimmung gibt es einen Fehler. Bei der letzten Zeile gibt es eine Ausnahme (javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable), da JSON und XML eben nicht purer Text sind.

    1.1.7 REST-Parameter

    Im Abschnitt über REST-URLs wurde ein Beispiel vorgestellt, wie Pfadangaben aussehen, wenn sie einen RESTful Service bilden:

    http://www.tutego.de/blog/javainsel/category/java-7/page/2/

    Als Schlüssel-Werte-Paar lassen sich festhalten: category=java-7 und page=2. Der Server wird die URL auseinanderpflücken und genau die Blog-Einträge liefern, die zur Kategorie »java-7« gehören und sich auf der zweiten Seite befinden.

    Bisher sah unser REST-Service auf dem Endpunkt /rest/message/ so aus, dass einfach ein String zurückgeben wird. Üblicherweise gibt es aber unterschiedliche URLs, die Operationen wie »finde alle« oder »finde alle mit der Einschränkung X« abbilden. Bei unseren Nachrichten wollen wir dem Client drei Varianten zur Abfrage anbieten (mit Beispiel):

  • /rest/message/: alle Nachrichten aller Nutzer
  • /rest/message/user/chris: alle Nachrichten von Benutzer »chris«
  • /rest/message/user/chris/search/kitesurfing: alle Nachrichten von Benutzer »chris« mit dem Betreff »kitesurfing«
  • Das erste Beispiel macht deutlich, dass hier ohne explizite Angabe weiterer Einschränkungskriterien alle Nachrichten erfragt werden sollen, während mit zunehmend längerer URL weitere Einschränkungen dazu kommen.

    Parameter in JAX-RS kennzeichnen

    Die JAX-RS-API erlaubt es, dass diese Parameter (wie Benutzername oder Suchstring) leicht eingefangen werden können. Für die drei möglichen URLs entstehen drei überladene Methoden:

    Listing 44.7: com/tutego/insel/rest/MessageResource.java, MessageResource Ausschnitt

    @GET @Produces( MediaType.TEXT_PLAIN )
    public String message() …
    @GET @Produces( MediaType.TEXT_PLAIN )
    @Path("user/{user}")
    public String message( @PathParam("user") String user ) {
      return String.format( "Benutzer = %s", user );
    }
    @GET
    @Produces( MediaType.TEXT_PLAIN )
    @Path("user/{user}/search/{search}")
    public String message( @PathParam("user") String user,
    @PathParam("search") String search ) {
      return String.format( "Benutzer = %s, Suchstring = %s", user, search );
    }

    Die bekannte @Path-Annotation enthält nicht einfach nur einen statischen Pfad, sondern beliebig viele Platzhalter in geschweiften Klammern. Der Name des Platzhalters taucht in der Methode wieder auf, nämlich dann, wenn er mit @PathParam an einen Parameter gebunden wird. Jersey parst für uns die URL und füllt die Parametervariablen passend auf bzw. ruft die richtige Methode auf. Da die JAX-RS-Implementierung den Wert füllt, nennt sich das auch JAX-RS-Injizierung.

    URL-Endung

    Aufgerufene Methode

    /rest/message/

    message()

    /rest/message/user/chris

    message( String user )

    /rest/message/user/chris/search/kitesurfing

    message( String user, String search )

    Tabelle 44.2: Welche URL zu welcher Methode führt

    Die Implementierungen der Methoden würden jetzt an einen Datenservice gehen und die selektierten Datensätze zurückgeben. Das zeigt das Beispiel nicht, da dies eine andere Baustelle ist.

    Tipp: Wenn Parameter falsch sind, kann eine Methode eine besondere Ausnahme vom Typ javax.ws.rs.WebApplicationException (dies ist eine RuntimeException) erzeugen. Im Konstruktor von javax.ws.rs.WebApplicationException lässt sich ein Statuscode als int oder als Aufzählung vom Typ Response.Status übergeben, etwa new WebApplicationException(Response.Status. EXPECTATION_FAILED).

    1.1.8 REST-Services mit Parametern über die Jersey-Client-API aufrufen

    Wenn die URLs in dem Format schlüssel1/wert1/schlüssel2/wert2 aufgebaut sind, dann ist ein Aufbau einfach durch Kaskadierung der path(…)-Methoden umzusetzen:

    Listing 44.8: com/tutego/insel/rest/ParameterizedMessageJerseyClient.java, main()

    System.out.println( ClientBuilder.newClient()

    .target( "http://localhost:8080/rest" )

    .path( "message" ).path( "user" ).path("chris")

    .request().get(String.class) ); // Benutzer = chris

    System.out.println( ClientBuilder.newClient()

    .target( "http://localhost:8080/rest" )

    .path( "message" ).path( "user" )

    .path("chris").path("search").path("kitesurfing")

    .request().get(String.class) ); // Benutzer = chris, Suchstring = kitesurfing

    Multiwerte

    Schlüssel-Wert-Paare lassen sich auch auf anderen Wegen übermitteln statt nur auf dem Weg über schlüssel1/wert1/schlüssel2/wert2. Besonders im Web und für Formularparameter ist die Kodierung über schlüssel1=wert1&schlüssel2=wert2 üblich. Auch das kann in JAX-RS und mit der Jersey-Client-API abgebildet werden.

  • Anstatt Parameter mit @PathParam zu annotieren, kommt bei Multiwerten @QueryParam zum Einsatz.
  • Anstatt mit path(String) zu arbeiten, wird bei dem Jersey-Client mit queryParam(String key, Object… value) gearbeitet.
  • Hinweis: Es gibt eine Reihe von Dingen, die in Methoden per Annotation übermittelt werden können, und nicht nur @PathParam und @QueryParam. Dazu kommen noch Dinge wie @HeaderParam für den HTTP-Request-Header, @CookieParam für Cookies, @Context für Informationsobjekte und weitere Objekte.

    1.1.9 PUT-Anforderungen und das Senden von Daten

    Zum Senden von Daten an einen REST-Service ist die HTTP-PUT-Methode gedacht. Die Implementierung einer Java-Methode kann so aussehen:

    Listing 44.9: com/tutego/insel/rest/MessageResource.java, MessageResource Ausschnitt

    @PUT

    @Path( "user/{user}" )

    @Consumes( MediaType.TEXT_PLAIN )

    public Response postMessage( @PathParam( "user" ) String user, String message ) {

    System.out.printf( "%s sendet ‘%s’%n", user, message );

    return Response.noContent().build();

    }

    Zunächst gilt, dass statt @GET ein @PUT die Methode annotiert. @Consumes hält den MIME-Typ dieser gesendeten Daten fest. Ein zusätzlicher @PathParam fängt die Benutzerkennung ein, die dann mit der gesendeten PUT-Nachricht auf der Konsole ausgegeben wird.

    Diese beiden Annotationen @PUT und @Consumes sind also nötig. Eine Rückgabe in dem Sinne hat die Methode nicht und es umstritten, ob ein REST-PUT überhaupt neben dem Status-Code etwas zurückgeben soll. Daher ist die Rückgabe ein spezielles JAX-RS-Objekt vom Typ Response, was hier für 204 steht.

    1.1.10 PUT/POST/DELETE-Sendungen mit der Jersey-Client-API absetzen

    Ein Invocation.Builder bietet neben get(…) auch die anderen Java-Methoden für HTTP-Methoden, also delete(…), post(…), options(…), … und eben auch put(…) zum Schreiben.

    Listing 44.10: com/tutego/insel/rest/ PostMessageJerseyClient.java, main()

    Client client = ClientBuilder.newClient();

    WebTarget target = client.target( "http://localhost:8080/rest" );

    Response response = target.path( "message" ).path( "user" ).path( "chris" )

    .request().put( Entity.text("Hey Chris") );

    System.out.println( response );

    Die put(…)-Methode erwartet als Argument den Typ Entitiy und zum Objektaufbau gibt es diverse statische Methoden in Enitity. Und Entity.text("Hey Chris") ist eine Abkürzung für Entity.entity("Hey Chris", MediaType.TEXT_PLAIN).


    [1] Roy Fielding meint dazu nur: »Flickr obviously don’t have a clue what REST means since they just use it as an alias for HTTP. Perhaps that is because the Wikipedia entry is also confused. I don’t know.« (Quelle: http://roy.gbiv.com/untangled/2008/no-rest-in-cmis)

    Expression Language 3.0 (JSR-341), wow, aber komplett an mir vorbeigezogen

    Schau http://www.youtube.com/watch?v=Uyx7fLXXSS4, http://www.youtube.com/watch?v=JEKpRjXL06w und http://rijkswatch.blogspot.de/2012/06/expression-language-30-is-in-public.html. Aus der Spezifikation http://download.oracle.com/otn-pub/jcp/el-3_0-fr-eval-spec/EL3.0.FR.pdf.

    • Lambdas: fact = n -> n==0? 1: n*fact(n-1); fact(5) oder employees.where(e->e.firstName == ‘Bob’)
    • Collection-Syntax: employees.where(e->e.firstName == ‘Bob’) oder {“one”:1, “two”:2, “three”:3}
    • Streaming wie in Java 8: books.stream().filter(b->b.category == ‘history’) .map(b->b.title) .toList() oder [1,2,3,4,5].stream().substream(2,4).toArray()

    Insgesamt:

    ■ Added Chapter 2 “Operations on Collection Objects”.
    ■ Added 1.8, String concatenation operator.
    ■ Added 1.13, Assignment operator.
    ■ Added 1.14, Semi-colon operator.
    ■ Added 1.20 Lambda Expression.
    ■ Added 1.22 Static Field and Methods.
    ■ Added T and cat to 1.17 Reserved words.
    ■ Modified 1.16 Operator precedence.
    ■ Modified coercion rule from nulls to non-primitive types.
    ■ Many changes to the javadoc API.

    Schön ist, dass man das auch eigenständig außerhalb vom Web-Container verwenden kann. Das macht die EL interessant für Template-Lösungen.

    Siehe auch YouTube Video: https://www.youtube.com/watch?v=JEKpRjXL06w

    Darf man in einer Java EE-Anwendung mit Class.forName(…) einen Klasse laden?

    Ja, das ist erlaubt. Siehe auch http://www.oracle.com/technetwork/java/restrictions-142267.html:

    Why is there a restriction against using the Java Reflection APIs to obtain declared member information that the Java language security rules would not allow? Doesn’t Java automatically enforce those rules?
    Contrary to common belief, most of the Java Reflection API can be used from EJB components. For example, loadClass() and invoke() can both be used by enterprise beans. Only certain reflection methods are forbidden.

    und

    Why all the restrictions on creating class loaders and redirection of input, output, and error streams?
    Class loading is allowed, but creating custom loaders is not, for security reasons. These restrictions exist because the EJB container has responsibility for class loading and I/O control. Allowing the EJB to perform these functions would interfere with proper operation of the Container, and are a security hazard.

    JPA-Beispiel in wenigen Minuten mit OpenJPA

    1. Beziehe unter http://mvnrepository.com/artifact/org.apache.openjpa/openjpa-all das openjpa-all-2.2.0.jar (etwas mehr als 6 MiB) und setzte es in den Klassenpfad.
    2. Habe einen Datentreiber im Klassenpfad (bei mir den der HSQLDB).
    3. Lege im Projekt einen Ordner META-INF an, platziere dort eine Datei persistence.xml:
    4. <?xml version="1.0" encoding="UTF-8"?>
      <persistence xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_2_0.xsd"
        version="2.0">
        <persistence-unit name="traida" transaction-type="RESOURCE_LOCAL">
          <class>traida.shared.domain.Contact</class>
          <properties>
            <property name="openjpa.jdbc.DBDictionary" value="hsql"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:file:hsqldbtest;user=sa" />
            <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver" />
            <property name="openjpa.Log" value="DefaultLevel=ERROR, Tool=ERROR" />
            <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/>
          </properties>
        </persistence-unit>
      </persistence>

      Mehr zu den Properties unter http://openjpa.apache.org/builds/apache-openjpa-2.2.1-SNAPSHOT/docs/docbook/manual/main.html.

    5. Lege eine Klasse traida.shared.domain.Contact an:
    6. @Entity
      public class Contact {
        @Id
        @GeneratedValue( strategy = GenerationType.IDENTITY )
        public Long id;
        public String name;
        // Setter/Getter ausgelassen
      }

    7. Schreibe eine main(String[])-Methode mit:
    8. EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( "traida" );
      EntityManager entityManager = entityManagerFactory.createEntityManager();
      entityManager.getTransaction().begin();
      Contact c = new Contact();
      c.name = "Hallo Willi";
      entityManager.persist( c );
      entityManager.getTransaction().commit();
      System.out.println( entityManager.createQuery( "select c from Contact c" ).getResultList() );
      entityManager.close();

    9. Fertig, jetzt freuen.

    Erster Milestone von Jersey 2, implementiert JAX-RS 2

    Weitere Details im Blog http://marek.potociar.net/2012/02/22/first-milestone-build-of-jersey-2-0/. Für Clients gibt es vielleicht die größte Änderung: Die Einführung der JAX-RS Client API. Die API ist allerdings noch nicht verabschiedet, kann sich daher also ändern. Die bisherige Spezi von JAX-RS liegt unter http://jcp.org/aboutJava/communityprocess/edr/jsr339/index.html.