02.02.2013
ReaderInterceptor und WriterInterceptor für JAX-RS 2.0
In diesem Teil des Tutorials werden Interceptoren vorgestellt. Seit JAX-RS 2.0 existieren diese auch für REST-Services und ermöglichen das Abfangen von Requests und Responses. Genau genommen, hat man die Möglichkeit sich an die Stellen "einzuhängen" bevor MessageBodyReader
bzw. MessageBodyWriter
aufgerufen werden. Um das zu erreichen, müssen die Interfaces ReaderInterceptor
und/oder WriterInterceptor
implementiert werden.
Auf der Abbildung sieht man, wie der Client einen Request an den Service /itemService/item des Servers absetzt. Bevor die Response mit dem Item an den Client gesendet wird, muss der ItemMessageBodyWriter durchlaufen werden, damit das Item als Stream übertragen wird. In der Abbildung oben wird der ItemMessageBodyWriter von einem WriterInterceptor umschlossen, der vor dem MessageBodyWriter und dem Versenden ausgeführt wird. Auf Seite des Client wird der Item-Stream von einem ItemMessageBodyReader geparsed, so dass der Client mit Objekten vom Typ Item arbeiten kann.
Anmerkung: In der Abbildung ist nur der ItemMessageBodyWriter abgebildet. Wenn Objekte von Typ String
oder Listen mit Item
s übertragen werden, dann kommt ein Standard-MessageBodyWriter bzw. der ItemListMessageBodyWriter zum Einsatz.
In dem folgenden Beispiel wird ein Interceptor bei jedem Aufruf des REST-Services angesprochen. D.h. bevor der MessageBodyWriter
aufgerufen wird, um das Resultat (Response) an den Client zu liefern.
Zu Demonstrationszwecken wird noch der Content-Type
aus dem Header ausgelesen und ein neuer Wert in den Header geschrieben. Hauptaufgabe des Beispiel-Interceptor ist es aber, die Ergebnismenge zu verändern. Es werden folgende Dinge durchgeführt:
- Bei einen
String
als Rückgabewert, wird der String verändert.
- Bei einem
Item
als Rückgabewert, wird der Name
des Item
verändert.
- Bei einer
List<Item>
als Rückgabewert, werden alles Name
s verändert und ein weiteres Item
in der Liste ergänzt.
Der Quellcode, der im Projekt JAXRSServer
abgelegt werden sollte, sieht folgendermaßen aus:
package org.hameister.itemservice;
import java.io.IOException;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
@Provider
public class TestInterceptor implements ReaderInterceptor, WriterInterceptor {
@Override
public Object aroundReadFrom(ReaderInterceptorContext ric) throws IOException, WebApplicationException {
System.out.println("aroundReadFrom");
return ric.proceed();
}
@Override
public void aroundWriteTo(WriterInterceptorContext wic) throws IOException, WebApplicationException {
System.out.println("Called before the MessageBodyWriters are executed.");
// Access the headers
MultivaluedMap<String, Object> headers = wic.getHeaders();
if(headers.containsKey("Content-Type")) {
System.out.println("Content-Type: "+headers.get("Content-Type"));
}
// Add header informations
wic.getHeaders().add("Manipulated", "true");
// Change the entities
Object entity = wic.getEntity();
if(entity instanceof String) {
wic.setEntity("Changed_string_"+entity); // Replace the entity
} else if(entity instanceof Item) {
Item item = (Item)entity;
item.setName("Changed_item_"+item.getName()); // Change an entity
} else if(entity instanceof List) {
List<Item> list = (List)entity;
for(Item item: list) {
item.setName("Changed_"+item.getName()); // Change an object
}
list.add(new Item("ID_JH", "Item Added", "This item was added by the Interceptor!")); //Add an object
}
wic.proceed();
}
}
Zu beachten ist die Annotation @Provider
, die dafür sorgt, dass der Interceptor registriert wird. Außerdem muss vor dem Verlassen der Methode immer proceed()
auf dem jeweiligen Context aufgerufen werden.
In dem Beispiel-Interceptor wird die Methode aroundReadFrom
vom Interface ReaderInterceptor
, muss dazu verwendet, den Aufruf mit System.out
zu protokollieren. In der Methode aroundWriteTo
vom Interface WriterInterceptor
werden die oben angesprochenen Veränderungen an der Ergebnismenge durchgeführt.
Wenn nun alle bisher erstellten JUnit-Tests ausgeführt werden, dann schlagen diese fehl. Der Grund dafür ist, dass durch den Interceptor die Rückgabewerte verändert wurden. Damit die Tests wieder fehlerfrei durchlaufen, müssen folgende Anpassungen durchgeführt werden:
package org.hameister.itemservice.test;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientFactory;
import javax.ws.rs.client.InvocationCallback;
import junit.framework.Assert;
import org.hameister.itemservice.Item;
import org.hameister.itemservice.ItemListMessageBodyReader;
import org.hameister.itemservice.ItemListMessageBodyWriter;
import org.hameister.itemservice.ItemMessageBodyReader;
import org.hameister.itemservice.ItemMessageBodyWriter;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class JAXRESTClientTest {
Client client;
private static final String SERVER = "http://localhost:8080/JAXRSServer/webresources";
public JAXRESTClientTest() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
client = ClientFactory.newClient();
}
@After
public void tearDown() {
client.close();
}
@Test
public void itemstringTextPlain() {
String returnValue = client.target(SERVER + "/itemservice/itemstring").request("text/plain").get(String.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value ''itemstring'':{0}", returnValue);
Assert.assertEquals("Changed_string_Item 1", returnValue);
}
@Test
public void itemstringJSON() {
String returnValue = client.target(SERVER + "/itemservice/itemstring").request("application/json").get(String.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value ''itemstring'':{0}", returnValue);
Assert.assertEquals("Changed_string_Item 1", returnValue);
}
@Test
public void itemstringXML() {
String returnValue = client.target(SERVER + "/itemservice/itemstring").request("application/xml").get(String.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value 'itemstring':{0}", returnValue);
Assert.assertEquals("Changed_string_Item 1", returnValue);
}
@Test
public void itemJSON() {
client.register(ItemMessageBodyWriter.class);
client.register(ItemMessageBodyReader.class);
Item returnValueItem = client.target(SERVER + "/itemservice/item").request("application/json").get(Item.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value 'itemstring':{0} {1}", new Object[]{returnValueItem.getName(), returnValueItem.getDescription()});
Assert.assertEquals("Changed_item_Item 1", returnValueItem.getName());
Assert.assertTrue("Wrong description text in Item: " + returnValueItem.getDescription(), returnValueItem.getDescription().startsWith("Item Description"));
}
@Test
public void itemXML() {
client.register(ItemMessageBodyWriter.class);
client.register(ItemMessageBodyReader.class);
Item returnValueItem = client.target(SERVER + "/itemservice/item").request("application/xml").get(Item.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value 'itemstring':{0} {1}", new Object[]{returnValueItem.getName(), returnValueItem.getDescription()});
Assert.assertEquals("Changed_item_Item 1", returnValueItem.getName());
Assert.assertTrue("Wrong description text in Item: " + returnValueItem.getDescription(), returnValueItem.getDescription().startsWith("Item Description"));
}
@Test
public void itemTextPlain() {
client.register(ItemMessageBodyWriter.class);
client.register(ItemMessageBodyReader.class);
Item returnValueItem = client.target(SERVER + "/itemservice/item").request("text/plain").get(Item.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value 'itemstring':{0} {1}", new Object[]{returnValueItem.getName(), returnValueItem.getDescription()});
Assert.assertEquals("Changed_item_Item 1", returnValueItem.getName());
Assert.assertTrue("Wrong description text in Item: " + returnValueItem.getDescription(), returnValueItem.getDescription().startsWith("Item Description"));
}
private void checkItemList(List<Item> returnValueItems) {
Assert.assertEquals("11 Items expected.", 11, returnValueItems.size());
System.out.println("Return value 'number of items':" + returnValueItems.size());
for (int i = 0; i < 10; i++) {
Item item = returnValueItems.get(i);
Assert.assertTrue("Item name is not correct: " + item.getName(), item.getName().startsWith("Changed_Item "));
Assert.assertTrue("Wrong description text in Item: " + item.getDescription(), item.getDescription().startsWith("Item Description"));
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value ''item'':{0} {1}", new Object[]{item.getName(), item.getDescription()});
}
//Check the additional Item
Item item = returnValueItems.get(10);
Assert.assertTrue("Item name is not correct: " + item.getName(), item.getName().startsWith("Item Added"));
Assert.assertTrue("Wrong description text in Item: " + item.getDescription(), item.getDescription().startsWith("This item was added by the Interceptor"));
}
@Test
public void itemListJSON() {
client.register(ItemListMessageBodyWriter.class);
client.register(ItemListMessageBodyReader.class);
List<Item> returnValueItems = client.target(SERVER + "/itemservice/items").request("application/json").get(List.class);
checkItemList(returnValueItems);
}
@Test
public void itemListXML() {
client.register(ItemListMessageBodyWriter.class);
client.register(ItemListMessageBodyReader.class);
List<Item> returnValueItems = client.target(SERVER + "/itemservice/items").request("application/xml").get(List.class);
checkItemList(returnValueItems);
}
@Test
public void itemListTextPlain() {
client.register(ItemListMessageBodyWriter.class);
client.register(ItemListMessageBodyReader.class);
List<Item> returnValueItems = client.target(SERVER + "/itemservice/items").request("text/plain").get(List.class);
checkItemList(returnValueItems);
}
@Test
public void itemFilter() {
String returnValue = client.target(SERVER + "/itemservice/itemstring").request("text/plain").cookie("Filter", "f1").get(String.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value ''itemstring'':{0}", returnValue);
Assert.assertEquals("Item was not filtered.", "", returnValue);
}
@Test
public void itemNoFilter() {
String returnValue = client.target(SERVER + "/itemservice/itemstring").request("text/plain").cookie("Filter", "f2").get(String.class);
Logger.getLogger(JAXRESTClientTest.class.getName()).log(Level.INFO, "Return value ''itemstring'':{0}", returnValue);
Assert.assertEquals("Item was not filtered.", "Changed_string_Item 1", returnValue);
}
}
Im nächsten Teil des Tutorials geht es um den asynchronen Aufruf von Methoden bei JAX-RS 2.0.