Android – Kommunikation mit der Außenwelt

Veröffentlicht in: Android | 0

Nachdem ich in einem vorangegangenen Artikel die Verwendung einer SQLite Datenbank in einer Android-Anwendung beschrieben habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/17/verwendung-einer-sqlite-datenbank-in-einer-android-app/), möchte ich mich in diesem Artikel der Kommunikation mit einem REST-Service widmen. Hierfür kann die im Android-SDK integrierte Apache HttpClient Library verwendet werden. Als REST-Schnittstelle soll das Beispiel aus meinem letzten Artikel (siehe http://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3/) verwendet werden. Hier wurde exemplarisch gezeigt wie man auf der REST-Ressource MyBean die Operationen GET, POST, PUT und DELETE ausführt. Neben XML- wurden auch JSON-Inhalte ausgetauscht. Letzteres Datenformat wird im folgenden Beispiel benötigt. Hier nun die Java-Repräsentation der MyBean-Ressource:

public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Im ersten Schritt soll die Android-Anwendung per GET-Request alle MyBean-Einträge erfragen und den erhaltenen JSON-Inhalt in MyBean-Objekte transformieren. Anschließend wird die Funktionalität sukzessive um die REST-Operationen POST, PUT und DELETE erweitert, wobei der POST- und PUT-Request entsprechenden JSON-Inhalt mitliefern muss.

Bevor es an die REST-Kommunikation geht, muss die Android-Anwendung die Erlaubnis zum Zugriff aufs Internet und damit folgenden Eintrag im Android-Manifest erhalten:

<uses-permission android:name=“android.permission.INTERNET“ />

Anschließend wird eine Hilfsklasse erstellt, welche die Verbindung zum REST-Service aufbaut, den erhaltenden InputStream in einen String umwandelt und für alle Arten von REST-Operationen genutzt werden kann:

public String connect(HttpRequestBase httpRequest) {
HttpClient httpclient = new DefaultHttpClient();
httpRequest.addHeader(„Accept“, „application/json“);
try {
HttpResponse response = httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
String result = convertStreamToString(inputStream);
inputStream.close();
return result;
}
} catch (Exception e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
return „“;
}

Die übergebene HttpRequestBase abstrahiert die jeweilige Http-Operation, die bereits die URI der Rest-Ressource und gegebenenfalls weitere Inhalte enthält. Die Kommunikation sieht bei allen Operationen gleich aus. Zunächst wird ein HttpClient benötigt – für dieses Beispiel reicht der Standard-HttpClient aus. Da der Http-Response vom Server JSON enthalten soll, wird die Header-Information Accept: application/json hinzugefügt. Sofern der Server außer dem Status – response.getStatusLine() – mit weiteren Daten antwortet, befinden sich diese in der HttpEntity in Form eines InputStreams. Da auf mobilen Endgeräten nur begrenzt Speicher vorhanden ist, können nicht beliebig Libraries verwendet werden. So kommt man nicht Drumherum, den InputStream selbstständig in einen String zu transformieren:

private String convertStreamToString(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder(50);
String line = null;
try {
while ((line = reader.readLine()) != null) {
stringBuilder.append(line + „n“);
}
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
} finally {
try {
inputStream.close();
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}
return stringBuilder.toString();
}

Der InputStreamReader dient dem InputStream als wrapper, der wiederrum von einem BufferedReader gewrappt wird. Letzterer kann zeilenweise ausgelesen und der Inhalt einem StringBuilder hinzugefügt werden. Sofern die Antwort des Servers voraussichtlich mehr als 16 Zeichen beträgt, empfiehlt es sich den StringBuilder mit einem höheren Wert zu initialisieren. Andernfalls muss der interne Buffer des StringBuilders sukzessive erhöht werden.

Nun soll die GET-Operation erstellt und die oben gezeigte connect-Methode aufgerufen werden:

HttpRequestBase httpGet = new HttpGet(REST_RESSOURCE_URI);
String result = connect(httpGet);

Anschließend enthält das result einen JSON-String folgender Art:

{„object“:{„myview“:[{„id“:“1″,“message“:“Some text“},{„id“:“2″,“message“:“Some more text“}]}}

Dieser String soll  nun geparst und der JSON-Inhalt in entsprechende MyBean-Objekte transformiert werden. Hierfür wird der komplette String zunächst einem JSONTokener übergeben. In unserem Fall liefert dessen nextValue-Methode ein JSONObject zurück, grundsätzlich wären aber auch ein JSONArray, String, NULL oder weitere Java-Typen möglich. Das so erhaltene JSONObject enthält wiederrum ein JSONObject mit dem Schlüsselwert „object“. Erst jetzt kommt man an das JSONArray „myview“, welches die gesammelten MyBean-Einträge enthält. Jeder dieser Einträge ist in einem JSONObject hinterlegt, welches die MyBean-Properties id und message enthält. Diese Werte können nun einem neu zu erzeugenden MyBean-Objekt übergeben werden:

.JSONTokener token = new JSONTokener(jsonContent);
while (token.more()) {
Object object = token.nextValue();
if (object instanceof JSONObject) {
JSONObject jsonRoot = (JSONObject) object;
try {
JSONObject jsonObject = jsonRoot.getJSONObject(„object“);
if (jsonObject != null) {
JSONArray jsonBeans = jsonObject.getJSONArray(„myview“);
if (jsonBeans != null) {
for (int i = 0; i < jsonBeans.length(); i++) {
JSONObject jsonBean = jsonBeans.getJSONObject(i);
int id = jsonBean.getInt(„id“);
String subject = jsonBean.getString(„message“);
MyBean myBean = new MyBean(id, message);
// adding myBean to a myBeans list
}
}
}
} catch (JSONException e) {
// do nothing, wrong JSON content
}
}

Soll nun eine POST- oder PUT-Operation getätigt werden, muss der umgekehrte Weg gegangen werden. Es wird nicht empfangener JSON-Inhalt deserialisiert, sondern ein MyBean-Objekt in JSON-Inhalt umgewandelt und an den Server gesendet. Hierfür wird eine Implementierung des HttpEntity-Interfaces benötigt:

public class MyHttpEntity implements HttpEntity {

private JSONObject jsonObject;

public MyHttpEntity(MyBean myBean) {
try {
jsonObject = new JSONObject();
jsonObject.put(„id“, myBean.getId());
jsonObject.put(„message“, myBean.getMessage());
} catch (JSONException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}

@Override
public InputStream getContent() throws IOException, IllegalStateException {
return new ByteArrayInputStream(jsonObject.toString().getBytes());
}

@Override
public long getContentLength() {
return jsonObject.toString().length();
}

@Override
public Header getContentType() {
return new BasicHeader(„Content-Type“, „application/json“);
}

@Override
public void writeTo(OutputStream outstream) throws IOException {
outstream.write(jsonObject.toString().getBytes());
}

// … further methods, which are not required
//    for this example …
}

Im Konstruktor wird das zu transformierende MyBean-Objekt übergeben und dessen Eigenschaften in einem zu erstellenden JSONObject hinterlegt. Anschließend sind folgende Methoden zu erstellen:

  • getContent – Liefert den JSON-Inhalt als InputStream
  • getContentLength – Enthält die Länge des JSON-Inhaltes
  • getContentType – Fügt die Header Information „Content-Type: application/json“ hinzu. Hierfür wird ein BasicHeader verwendet.
  • writeTo – Der JSON-Inhalt wird in einen OutputStream gespeichert.

Das Interface HttpEntity enthält noch weitere zu implementierende Methoden, deren Funktionalität spielt für dieses Beispiel aber keine Rolle. Nachdem der Serialisierung einer MyBean nichts mehr im Wege steht, kann jetzt die eigentliche POST-Operation ausgeführt werden:

HttpPost post = new HttpPost(REST_RESSOURCE_URI);
post.setEntity(new MyHttpEntity(myBean));
String result = connect(post);

Äquivalent hierzu ist der PUT-Request, nur dass dieser auf einer bestehenden MyBean getätigt wird. Daher wird in der URI noch eine id zur Identifikation der MyBean übergeben:

HttpPut put = new HttpPut(REST_RESSOURCE_URI + „/myBean/“ + myBean.getId());
put.setEntity(new MyHttpEntity(myBean));
connect(put);

Einfach gestaltet sich die DELETE-Operation, da der Request außer der zu löschenden Ressource keine weiteren Informationen benötigt. Die id der zum Löschen auserwählten MyBean wird im Pfad übergeben:

HttpDelete delete = new HttpDelete(REST_RESSOURCE_URL + „/myBean/“ + id);
connect(delete);

Ist die Hilfsklasse für die Kommunikation erst einmal geschrieben, sind die einzelnen REST-Operationen schnell umgesetzt. Lediglich bei der Serialisierung bzw. Deserialisierung von und nach JSON handelt es sich um lästige Fleißarbeit. Zumal hier jeder Fehler dank des recht langsamen Android-Emulators zusätzlich Zeit kostet.

REST Services mit Spring 3

Veröffentlicht in: Java, spring | 0

Ging vor ein paar Jahren der Trend bei der Übertragung von Informationen zwischen Softwaresystemen noch in Richtung Web-Services, so geht mittlerweile die Tendenz klar in Richtung REST.

REST selber basiert auf dem HTTP-Protokoll und ist zustandslos, da alle benötigten Informationen Bestandteil des jeweiligen HTTP-Requests sind. Die einzelnen Ressourcen die ein REST-Service zur Verfügung stellt, sind über eindeutige URIs erreichbar. Auf den Ressourcen können unter anderem folgende Operationen ausgeführt werden:

  • GET – Abfrage einer Ressource
  • POST- Zur aufgerufenen Ressource wird eine Sub-Ressource erzeugt
  • PUT – Verändern einer bestehende Ressource
  • DELETE – Löschen einer Ressource

Aufgrund der Entwicklung der vergangenen Jahren und der immer weiteren Verbreitung von REST wurde JAX-RS mit der Version 1.1 in den Java EE 6 Standard aufgenommen. Aktuell wird an der Version 2.0 gearbeitet, welche in den Java EE 7 Standard einfließen soll. Die gängigsten JAX-RS Implementierungen sind:

  • Jersey – Referenzimplementierung von Oracle
  • RESTEasy von JBoss
  • Restlet

Alle diese JAX-RS Implementierungen unterstützen Spring, was auch der Grund dafür ist, dass Spring selber keine eigene JAX-RS Implementierung anbietet. Man ist daher also auf eines der oben genannten Frameworks angewiesen.

Da ich mich bereits mit Restlet beschäftigt habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/01/restlet-rest-im-osgi-kontext/), habe ich mich im folgenden Beispiel für dieses REST-Framework entschieden. Restlet kann unter http://www.restlet.org/ in der aktuellen Version 2.0.11 heruntergeladen werden.

Natürlich gibt es auch die Möglichkeit Restlet als Maven Dependency zu laden. Hier kann man zusätzlich die Restlet-Spring-Extension verwenden, die Spring in der Version 3.0.1 verwendet. Will man eine neuere Spring Version einbinden – aktuell ist Spring 3.1.0 verfügbar – so muss man dies explizit in der pom.xml definieren.

Hier die Maven Dependencys für Restlet:

<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.restlet.jee</groupId>
<artifactId>org.restlet.ext.spring</artifactId>
<version>2.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-oxm-tiger</artifactId>
<version>1.5.9</version>
</dependency>

Die Library spring-oxm-tiger beinhaltet den Jaxb2Marshaller, der zum marshalling bzw. unmarshalling von Java Objekten in XML und umgekehrt benötigt wird.

Für die Ergebnis-Darstellung wird Spring MVC verwendet. Unter Spring 3.x wurden weitere Views, wie die MarshallingView zur Darstellung von XML-Inhalten und die MappingJacksonJsonView für JSON-Inhalte, hinzugefügt.

Im folgenden Beispiel soll ein REST-Service umgesetzt werden, der die bekannten GET, POST, PUT und DELETE Operationen auf der Ressource MyBean zur Verfügung stellt. Im ersten Schritt findet der Datentransfer ausschließlich mittels XML statt, im zweiten Schritt soll zusätzlich JSON unterstützt werden. D.h. der Service kann XML und JSON verarbeiten und abhängig vom unterstützten Datenformat des Clients findet die Antwort des Servers in XML oder JSON statt.

Starten wir mit der Spring-Konfiguration. Zunächst muss die MVC Java Konfiguration aktiviert werden:

<mvc:annotation-driven />

Weiterhin wird das Component-Scanning auf Package-Ebene aktiviert. Hiermit erspart man sich, jede Bean einzeln in der Konfigurationsdatei anzugeben:

<context:component-scan base-package=“de.mz.rest“ />

Nun wird das RequestMapping auf Type- und Methoden-Ebene aktiviert:

<bean class=“org.spring…DefaultAnnotationHandlerMapping“/>
<bean class=“org.spring…AnnotationMethodHandlerAdapter“ />

Anschließend wird der Jaxb2Marshaller definiert, der fürs marshalling bzw. unmarshalling von Objekten in XML und umgekehrt zuständig ist. Unter classesToBeBound sind alle Objekte aufgelistet, die der Marshaller in irgendeiner Form umwandeln soll:

<bean id=“jaxbMarshaller“ class=“org.springframework.oxm.jaxb.Jaxb2Marshaller“>
<property name=“classesToBeBound“>
<list>
<value>de.mz.rest.data.MyBean</value>
</list>
</property>
</bean>

Für die Darstellung der vom Jaxb2Marshaller aufbereiteten XML-Inhalte ist die MarshallingView zuständig:

<bean id=“viewName“ class=“org.springframework.web.servlet.view.xml.MarshallingView“>
<constructor-arg ref=“jaxbMarshaller“ />
</bean>

Zu guter Letzt wird noch ein ViewResolver benötigt, der die Requests auf die entsprechenden Controller weiterleitet. Hier wird der BeanNameViewResolver verwendet. Über das RequestMapping im Controller selber, wird  das Mapping später aber noch mal explizit gesetzt:

<bean  class=“org.spring…BeanNameViewResolver“ id=“viewResolver“ />

Nun wird die Klasse de.mz.rest.data.MyBean erstellt, bei der es sich um die Bean handelt, die vom Jaxb2Marshaller in XML umgewandelt werden soll. Hierfür werden JAXB (Java Architecture for XML Binding) Annotationen verwendet:

@XmlRootElement
@XmlType
public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Die Annotation @XmlRootElement definiert MyBean als ein Top Level Element. @XmlType hingegen kann dazu verwendet werden, um für die Attribute von MyBean eine bestimmte Reihenfolge zu definieren. Eigentlich ist diese Annotation laut JAXB-Spezifikation nicht zwingend nötig, allerdings benötigt der Jaxb2Marshaller diese Information. Andernfalls erachtet sich der Jaxb2Marshaller für die Klasse MyBean als nicht zuständig und beim Versuch diese Klasse in XML zu transformieren wird folgende, irreführende Fehlermeldung geworfen:

javax.servlet.ServletException: Unable to locate object to be marshalled in model

Wenden wir uns nun der REST-Ressource zu, die die gewohnte Spring MVC Annotation @Controller verwendet:

@Controller
@RequestMapping(„/myPath“)
public class MyResource extends ServerResource {

@RequestMapping(value=“/{id}“, method=RequestMethod.GET)
public ModelAndView get(@PathVariable String id)  {
return new ModelAndView(„viewName“, „object“, new MyBean());
}
}

Wie man in obigem Beispiel sieht, kommt die @RequestMapping Annotation zweimal zum Einsatz. Zum einen wird über die annotierte Klasse MyResource der Pfad definiert, unter dem die komplette REST-Ressource erreichbar ist, zum anderen werden die Methoden-Details definiert, wie Übergabe-Parameter und die jeweiligen Operationen (GET, POST, …) für die die Methode zuständig ist. Um innerhalb der Methode Zugriff auf die id aus dem Pfad zu erlangen, wird diese einfach als Übergabe-Parameter angegeben und mit der Annotation @PathVariable versehen. Nun muss dem ModelAndView-Element noch der ViewName der MarshallingView und die gewünschte MyBean übergeben werden. Die obige REST-Ressource ist über einen GET-Request auf die URI http://<server>:<port>/<webapp>/myPath/<id> aufrufbar.

Die letzten Einstellungen die noch fehlen, sind der ContextLoaderListener und das DispatcherServlet in der web.xml Datei:

<listener>
<listener-class>org.spring…ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>servletName</servlet-name>
<servlet-class>org.spring…DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Bislang liefert die REST-Ressource nur ein einziges MyBean-Element zurück, welches über die id spezifiziert wird. Will man die vollständige Liste an MyBeans erhalten, wird ein Wrapper für eine Liste von MyBeans benötigt. Auch dieser Wrapper, die MyBeanList, ist wie gehabt mit dem JAXB @XmlRootElement zu versehen:

@XmlRootElement
@XmlType
public class MyBeanList {

private List<MyBean> myBeans;

public List<MyBean> getMyBeans() {
return myBeans;
}
public void setMyBeans(List<MyBean> myBean) {
this.myBean = myBean;
}
}

Anschließend kann die MyBean-Ressource um folgende Methode ergänzt werden:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView getMyBeans() {
List<MyBean> myBeans = new ArrayList<MyBean>();
// add MyBeans to list
MyBeanList beanList = new MyBeanList();
beanList.setMyBeans(myBeans);
return new ModelAndView(„viewName“, „object“, beanList);
}

Es ist zu beachten, dass auch die MyBeanList in der Spring Konfiguration des Jaxb2Marshaller unter classesToBeBound hinzugefügt werden muss.

Diese Ressource ist mittels eines GET-Requests unter der URI http://<server>:<port>/<webapp>/myPath erreichbar.

Die GET-Requests aus den vorangegangenen Beispielen sind recht simpel zu testen, da diese Ressourcen über einen Web-Browser aufrufbar sind. Die vom Jaxb2Marshaller serialisierten Daten werden im Browser als XML dargestellt. Zusätzlich sollen aber noch die Operationen POST, PUT und DELETE umgesetzt werden. Der POST und PUT Request beinhaltet Daten, die von der REST-Ressource deserialisiert werden müssen, um das Objekt MyBean zu erhalten. Hier nun die Umsetzung der POST-Schnittstelle:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView addBean(@RequestBody MyBean bean) {
// add bean
return new ModelAndView(„viewName“, „object“, bean);
}

Die Methode des RequestMapping wird als POST-Operation definiert. Die Daten selber werden als Übergabeparameter in die Methoden-Signatur aufgenommen und müssen lediglich mit der Annotation @RequestBody versehen werden. Den Rest übernimmt der Jaxb2Marshaller.

Die Ressource ist mit einem POST-Request unter der URI http://<server>:<port>/webapp/myPath erreichbar.

Zum Testen der Ressource kann das Kommandozeilen-Tool curl http://curl.haxx.se/ verwendet werden. Hier der Befehl inklusive Parameter, mit denen sich die Ressource Testen lässt:

curl -v -H „Accept: application/xml“ -H „Content-type: application/xml“ -X POST -d ‚<myBean><id>1</id><message>some text</message></myBean>‘  http://<server>:<port>/<webapp>/myPath

Hier die Erläuterung zu den einzelnen Parametern:

  • H <header>: Hiermit wird dem Request eine Header-Information hinzugefügt.
    1. Die Antwort des Servers soll XML sein, da dies vom Client akzeptiert wird.
    2. Die vom Client gesendeten Daten bestehen aus XML.
  • X <command>: Der Request wird ausgewählt, z.B. GET, POST, PUT oder DELETE
  • d <data>: Hier wird der eigentliche Inhalt des Requests definiert.
  • v: Steht für verbose. Wird verwendet um mehr Informationen zu erhalten, kann ansonsten aber auch weggelassen werden.

Wenn man lieber mit einem Grafischen Tool arbeiten möchte, kann man statt curl das Firefox Plugin REST Client verwenden.

Erweitern wir nun die REST-Ressource um eine PUT-Methode, damit eine bestehende MyBean verändert werden kann. Hierbei wird zum einen die id benötigt, die im Pfad angegeben wird und als @PathVariable in der Methoden-Signatur annotiert ist. Zum anderen wird – wie beim POST-Request – die mit @RequestBody annotierte MyBean übergeben, die die aktualisierten Daten beinhaltet:

@RequestMapping(value = „/{id}“, method=RequestMethod.PUT)
public ModelAndView updateMyBean(@PathVariable String id, @RequestBody MyBean bean) {
// update bean
return new ModelAndView(„viewName“, „object“, bean);
}

Aufrufbar ist diese Ressource unter einem PUT-Request auf http://<server>:<port>/webapp/myPath/<id>.

Für die DELETE-Operation wird nur die id der zu löschenden MyBean im Pfad übergeben. Die REST-Ressource gibt dann eine Liste der verbleibenden MyBeans zurück, die wie gehabt in dem Wrapper MyBeanList enthalten sind.

@RequestMapping(value = „/{id}“, method=RequestMethod.DELETE)
public ModelAndView removeMyBean(@PathVariable String id) {
// delete mybean and return list of remaining beans
return new ModelAndView(„todos“, „object“, remainingBeans);
}

Die Ressource wird mittels eines DELETE-Requests auf http://<server>:<port>/webapp/myPath/<id> aufgerufen.

Im letzten Teil des Artikels soll der REST-Service erweitert werden, dass er nicht nur XML sondern auch JSON unterstützt. Hierfür bietet Spring den ContentNegotiatingViewResolver an, der für die korrekte Darstellung das View Konzept verwendet. Abhängig vom gewünschten Inhalt - diese gibt der Client im Accept-Header des Requests mit an (z.B.: Accept: application/json) - wird die entsprechende View angesprochen.

Für die JSON Unterstützung wird das Jackson-Framework http://xircles.codehaus.org/projects/jackson benötigt, welches zur Zeit in der Version 1.9.5 zu downloaden ist.

Das Jackson Framework kann natürlich auch als Maven Dependency verwendet werden:

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.5</version>
</dependency>

Nun muss der ContentNegotiatingViewResolver konfiguriert werden:

<bean class=“org.spring…ContentNegotiatingViewResolver“>
<property name=“mediaTypes“>
<map>
<entry key=“json“ value=“application/json“/>
<entry key=“xml“ value=“application/xml“/>
</map>
</property>
<property name=“viewResolvers“>
<list>
<ref bean=“viewResolver“ />
</list>
</property>
<property name=“defaultViews“>
<list>
<ref bean=“viewName“/>
<bean class=“org.spring…MappingJacksonJsonView“>
<property name=“prefixJson“ value=“true“/>
</bean>
</list>
</property>
</bean>

Zunächst werden die beiden MediaTypes definiert, die unterstützt werden sollen, also XML und JSON. Als ViewResolver kann der bereits definierte BeanNameViewResolver verwendet werden. Ansonsten müssen jetzt noch die Views definiert werden. Da weiterhin XML unterstützt werden soll, erhält die Property defaultViews die bereits definierte MarshallingView übergeben. Außerdem wird hier noch die MappingJacksonJsonView definiert.

Die JSON-Unterstützung der REST-Ressource kann ebenfalls mit curl überprüft werden, indem die Header-Informationen und die Daten angepasst werden:

curl -v -H „Accept: application/json“ -H „Content-type: application/json“ -X POST -d ‚{„id“:“1″,“message“:“Some Text“}‘  http://<server>:<port>/<webapp>/myPath

Insgesamt lässt sich sagen, dass man mit Spring 3.x recht komfortable REST-Services erstellen kann. Die dafür benötigte Konfigurations-Arbeit hält sich in Grenzen und mit der Kombination aus Spring MVC- und JAXB-Annotations kommt man schnell zum Ziel. Lediglich die Dokumentation ist mangelhaft und man muss sich die Informationen recht mühsam zusammen suchen. Auch das die vom Jaxb2Marshaller zu transformierenden Klassen zwangsläufig die @XmlType Annotation benötigen, ist nur schwer nachzuvollziehen.

Restlet – REST im OSGi Kontext

Veröffentlicht in: Eclipse, Java, RCP | 2

Der nachfolgende Artikel beschäftigt sich mit der Erstellung einer Client-Server Architektur, die mittels REST-Schnittstelle kommuniziert und auf dem OSGi-Framework aufbaut. Als REST-Framework wird http://www.restlet.org/ verwendet.

Zur Zeit liegt Restlet in der aktuellen Version 2.0.10 unter http://www.restlet.org/downloads/ zum Download bereit.

Zunächst wird für die Eclipse IDE eine neue Target-Platform erstellt, der als Location die Software Site http://download.eclipse.org/releases/indigo – die Indigo Update Site – hinzugefügt und hier die Equinox Target Components ausgewählt werden. Weiterhin wird als zweite Location auf das lib-Directory des entpackten Restlet-Archives verwiesen.

Kommen wir nun zur eigentlichen Programmierung. Insgesamt werden drei Plug-in Projects benötigt, jeweils ein Client/Server-Projekt und ein Projekt, welches die POJOs beinhaltet, die sowohl auf dem Client, als auch auf dem Server benötigt werden. In Letzteres wird eine User-Klasse erstellt, die zunächst nur aus Vor- und Nachname besteht.

Auf der Server-Seite wird eine eigene Implementierung der Klasse ServerResource benötigt, die eine Methode bereitstellt, um einen beliebigen User zu erhalten:

public class UserResource extends ServerResource {

@Get
public User getUser() {
return new User(„Max“, „Mustermann“);
}
}

Um beim Starten bzw. Stoppen des Server-Bundles den von Restlet bereitgestellten Jetty-Server hoch bzw. herunterzufahren, sind folgende zusätzliche Einträge im Server-Activator nötig:

    private Server server;
private int port = 8182;

public void start(BundleContext bC) throws Exception {
// … do what you have to do, while starting
server = new Server(Protocol.HTTP, port, UserResource.class);
server.start();
}

public void stop(BundleContext bC) throws Exception {
// … do what you have to do, while stopping …
server.stop();
}

Um den Server starten zu können, wird eine OSGI-Framework Run-Configuration benötigt, die zusätzlich zu den Equinox Target Components die Restlet-Bundles beinhaltet. Sollten einige der Restlet-Extensions Probleme bereiten, können diese getrost weg geklickt werden. Nun kann die soeben erstellte Run-Configuration ausgeführt und unter der URL http://localhost:8182/ die UserResource aufgerufen werden.

Natürlich sollte die UserResource nicht nur im Browser abrufbar sein, sondern von einem OSGi-Client konsumiert werden. Daher wird noch ein weiteres Bundle benötigt, wobei die Kommunikation mit dem Server denkbar einfach einfällt:

ClientResource clientResource = new ClientResource(„http://localhost:8182“);
User user = clientResource.get(User.class);

Wie man sieht, verwendet der Client ebenfalls das User-POJO, wobei die Serialisierung bzw. Deseriablisierung des User-Objekts vom Restlet-Framework umgesetzt wird. Dadurch lässt sich schnell und einfach eine REST-Kommunikation im OSGI Kontext realisieren. Zusätzlich zum OSGI Framework (Edition for Java SE) steht das Restlet-Framework noch für die Google AppEngine, GWT und die Android Entwicklung zur Verfügung. Schön das man nicht für jede Plattform ein unterschiedliches REST-Framework verwenden muss.