JAX-RS のリファレンス実装 Jersey のフィルタを使う方法

JAX-RS を使っていると、リソースの処理が呼ばれる前か後に何らかの処理をはさみたい、ということがあると思います。Jersey ではそういったニーズにフィルタで対応できます。

Jersey のフィルタにはリクエスト/レスポンスを一律処理するフィルタ (ContainerRequestFilter, ContainerResponseFilter) と、各リソースで個別に設定できるフィルタ (ResourceFilter) の二種類があります。

ContainerRequestFilter

まず ContainerRequestFilter ではリクエストに対して一律、リソースの処理が実行される前の処理が記述できます。例として、実用性皆無ですが HTTPメソッド をすべて PUT に書き換えるフィルタを書いてみます。

package test.jersey.filter;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;

public class MethodReplaceFilter implements ContainerRequestFilter {
	
	public ContainerRequest filter(ContainerRequest request) {
		request.setMethod("PUT");
		return request;
	}
	
}

完成したフィルタを web.xml で登録します。フィルタは Jersey のサーブレットに init-param com.sun.jersey.spi.container.ContainerRequestFilters で渡します。ちなみに複数のフィルタを登録する場合はセミコロン ";" で区切ります。

<?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>Jersey Filter Test</display-name>
	<servlet>
		<servlet-name>Jersey Web Application</servlet-name>
		<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>javax.ws.rs.Application</param-name>
			<param-value>test.jersey.filter.MyWebApplication</param-value>
		</init-param>
		<init-param>
			<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
			<param-value>test.jersey.filter.MethodReplaceFilter</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>Jersey Web Application</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

動作の確認用のリソースは以下のように作りました。

package test.jersey.filter;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

import test.jersey.filter.TestResourceFilterFactory.FilterTarget;

@Path("/")
public class TestResource {

	@GET
	@Produces("text/plain")
	public Response get() {
		return Response.status(200).entity("GET").build();
	}
	
	@PUT
	@Produces("text/plain")
	public Response put() {
		return Response.status(200).entity("PUT").build();
	}
}

さきほどの Webアプリケーション をサーブレットコンテナにデプロイした状態で API を HTTP GET で叩いてみます。フィルタが有効に動作していれば HTTPメソッド が GET にもかかわらず内部的には PUTアノテーション で修飾した処理 (API) が呼ばれるはずです。

HTTP GET を呼びましたが、フィルタで HTTPメソッド が上書きされたためレスポンスには PUT が返って来ました。フィルタが上手く動作したようです。動作テストには Firefox plugin の REST client for Firefox を使っています。

ContainerResponseFilter

次に ContainerResponseFilter です。またもや実用性皆無ですが、エンティティの内容を String で上書きするフィルタを書いてみます。フィルタは ContainerRequestFilter と同様 init-param で com.sun.jersey.spi.container.ContainerResponseFilters で渡します。

package test.jersey.filter;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;

public class EntityReplaceFilter implements ContainerResponseFilter {

	public ContainerResponse filter(ContainerRequest request,
			ContainerResponse response) {
		response.setEntity("Overwrited!!");
		return response;
	}

}

動作テストするとレスポンスの内容が上書きされていることがわかります。

ResourceFilter

最後に ResourceFilter です。インタフェース ResourceFilter を実装したフィルタを ResourceFilterFactory で返して使います。
ResourceFilterFactory は何らかのルールに従ってフィルタを生成します。「何らかのルール」にはアノテーションの修飾を使うことが多いと思います。
例ではフィルタで処理する API のメソッドを FilterTarget アノテーションで修飾して識別することにします。フィルタの処理自体は ContainerResponseFilter と同様、レスポンスのエンティティを上書きするだけです。

package test.jersey.filter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.List;

import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;

public class TestResourceFilterFactory implements ResourceFilterFactory {

	public List<ResourceFilter> create(AbstractMethod am) {
		// メソッドが FilterTarget で修飾されていればフィルタする
		if (am.getAnnotation(FilterTarget.class) != null) {
			return Collections.<ResourceFilter>singletonList(new Filter());
		}
		// フィルタしない
		return null;
	}
	
	public static class Filter implements ContainerResponseFilter, ResourceFilter {

		@Override
		public ContainerRequestFilter getRequestFilter() {
			return null;
		}

		@Override
		public ContainerResponseFilter getResponseFilter() {
			return this;
		}
		
		@Override
		public ContainerResponse filter(ContainerRequest request,
				ContainerResponse response) {
			response.setEntity("Overwrited!!");
			return response;
		}

	}
	
	@Inherited
	@Target({ElementType.METHOD})
	@Retention(RetentionPolicy.RUNTIME)
	public @interface FilterTarget {

	}

}

リソースの修正は一箇所だけ、フィルタ処理の対象にしたい API のメソッドにアノテーションをつけるだけです。

	@FilterTarget // このメソッドがフィルタ対象
	@GET
	@Produces("text/plain")
	public Response get() {
		return Response.status(200).entity("GET").build();
	}

web.xml には init-param com.sun.jersey.spi.container.ResourceFilters に ResourceFilterFactory を実装したクラスを指定します。

		<init-param>
			<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
			<param-value>test.jersey.filter.TestResourceFilterFactory</param-value>
		</init-param>

動作テストすると GET だけがフィルタの対象になっていることがわかります。

ResourceFilterFactory と ResourceFilter の組み合わせは、フィルタの対象やフィルタの種類を細かく制御したいときに便利だと思います。

まとめ

Jersey では二種類のフィルタを自分でカスタマイズして使うことができます。一律処理したいときは ContainerRequestFilter, ContainerResponseFilter が便利です。細かく制御したいのであれば ResourceFilter, ResourceFilterFactory を使います。

また、現在 JAX-RS の次のバージョンとして 2.0 (JSR 339) の仕様の策定が進んでいます。Early Draft の内容を見るかぎり Jersey のフィルタによく似た機能として RequestFilter, ResponseFilter が規定されているようです。やはりフィルタは便利、というより必要なので仕様に盛り込んだ、ということでしょうか。