02.02.2013

ContainerRequestFilter und ContainerResponseFilter für JAX-RS 2.0

In diesem Teil des Tutorials wird die Filter-Funktionalität von JAX-RS vorgestellt. Bei JAX-RS 2.0 wurden die beiden Interfaces ContainerRequestFilter und ContainerResponseFilter eingeführt, durch die es möglich wird, alle Anfragen, die an einen REST-Service gestellt werden, abzufangen. Durch die beiden Interfaces ist es möglich den ContainerRequestContext oder ContainerResponseContext zu untersuchen und dann ggf. die Anfrage oder das Ergebnis zu beeinflussen.

Auf der Abbildung sieht man, wie der Client einen Request an den Service /itemService/itemstring des Servers absetzt. Bevor der Request beim REST-Service ankommt, geht er durch einen ContainerRequestFilter. Falls ein Request gefiltert wird, findet eine Umleitung zurück zum Client statt. Bei der Response verhält es sich ähnlich. Bevor die Response an den Client gesendet wird, läuft sich durch einen ContainerResponseFilter. Falls der Inhalt der Response herausgefiltert wird, liefert der REST-Server ein leeres Ergebnis zurück. Ansonsten kommt das Ergebnis Item 1 beim Client an.

Anmerkung: Die Abbildung bezieht sich auf das Beispiel weiter unten. Mit dem ContainerResponseFilter kann auch die Response verändert werden, bevor sie an den Client gesendet wird. Es ist also nicht nur möglich überhaupt kein Ergebnis zu liefern, sondern auch die Antwort zu bearbeiten.

In dem folgenden Beispiel wird bei einem Request anhand eines gesetzten Cookies entschieden, ob ein Ergebnis zurückgeliefert werden soll.

Als erstes wird deshalb eine Klasse TestFilter im Projekt JAXRSServer angelegt, die das Interface ContainerRequestFilter implementiert.

package org.hameister.itemservice;

import java.io.IOException;
import java.util.Map;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

@Provider
public class TestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext crc) throws IOException {
        Map<String, Cookie/gt; cookies = crc.getCookies();
        if (cookies.containsKey("Filter")) {
            Cookie filterCookie = cookies.get("Filter");
            String filter = (String) filterCookie.getValue();
            if ("f1".equals(filter)) {
                //Send "OK", but remove the result.
                crc.abortWith(Response.ok().build());
            }
        }
    }
}

In der Methode filter werden die Cookies des ContainerRequestContext abgefragt und überfrüft, ob ein Filter gesetzt wurde und er den Wert f1 hat. Falls dies so ist, wird der Request abgebrochen, liefert aber trotzdem ein ok als Ergebnis zurück. Es wäre aber auch möglich den Request auf eine andere URL umzuleiten. Beispielsweise mit folgender Anweisung:

crc.abortWith(Response.temporaryRedirect(new URI("/item")).build());

Damit die Klasse nach dem Deployment aufgerufen wird, muss die Annotation @Provider oberhalb der Klasse vorhanden sein.

Möchte man eine Response filtern, steht das Interface ContainerResponseFilter zur Verfügung. Hier besteht die Möglichkeit auch auf den ContainerResponseContext zuzugreifen, um die Ergebnismenge zu überprüfen und ggf. zu filtern.

Im folgenden Beispiel werden beispielsweise alle Objekte von Typ Item gefiltert, deren Name mit BigItem beginnt.

package org.hameister.itemservice;

import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;

 @Provider
public class TestResponseFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext crc, ContainerResponseContext crc1) throws IOException {
        if(crc1.getEntity() instanceof Item) {
            Item item = (Item)crc1.getEntity();
            if(item.getName().startsWith("BigItem")) {
                crc.abortWith(Response.serverError().build());
            }
        }           
    }
}

Zum Testen der Methode erweitern wir nun wieder die Klasse JAXRESTClientTest im Projekt JAXRSClient um ein paar JUnit-Tests:

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 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.", "Item 1", returnValue);
    }
}

Der erste Test setzt einen Filter f1 und erwartet daraufhin, dass kein Ergebnis zurückgeliefert wird. Beim zweiten Test wird ein Filter f2 gesetzt, der laut Filter-Implementierung, keine Wirkung hat. Deshalb liefert der REST-Service ein Ergebnis zurück, dessen Wert überprüft werden kann.

Die oben erstellten Filter greifen für jeden Request und jede Response. Oft ist es aber gewünscht, dass nur bei bestimmten Aufrufen ein Filter aktiv ist. Beispielsweise nur für eine einzelne Methode des REST-Service. Wie dies funktioniert, wird im folgenden Teil des Tutorials beschrieben.