61.6. エンティティーリーダーインターセプター

概要

本セクションでは、エンティティーリーダーインターセプター を実装し、登録する方法を説明します。これにより、クライアント側またはサーバー側でメッセージボディーを読み取る際に入力ストリームをインターセプトできます。これは通常、暗号化や復号などの要求ボディーの汎用的な変換や圧縮や圧縮解除に役立ちます。

ReaderInterceptor インターフェイス

javax.ws.rs.ext.ReaderInterceptor インターフェイスは以下のように定義されます。

// Java
...
package javax.ws.rs.ext;

public interface ReaderInterceptor {
    public Object aroundReadFrom(ReaderInterceptorContext context)
            throws java.io.IOException, javax.ws.rs.WebApplicationException;
}

ReaderInterceptor インターフェイスを実装することで、メッセージボディー (Entity オブジェクト) をサーバー側またはクライアント側で読み取る際にインターセプトできます。エンティティーリーダーインターセプターは、以下のいずれかのコンテキストで使用できます。

  • サーバー側: サーバー側のインターセプターとしてバインドされた場合に、エンティティーリーダーインターセプターは、(一致したリソース内の) アプリケーションコードによってアクセスされたタイミングで、要求メッセージのボディーをインターセプトします。REST 要求のセマンティクスによっては、一致したリソースがメッセージ本文にアクセスできない場合があり、そのような場合にはリーダーインターセプターは呼び出されません。
  • クライアント側: クライアント側のインターセプターとしてバインドされた場合に、エンティティーリーダーインターセプターは、クライアントコードによってアクセスされたタイミングで応答メッセージのボディーをインターセプトします。クライアントコードが明示的にレスポンスメッセージにアクセスしない場合 (Response.getEntity メソッドの呼び出しなど)、リーダーインターセプターは呼び出されません。

ReaderInterceptorContext インターフェイス

ReaderInterceptoraroundReadFrom メソッドは、メッセージボディー (Entity オブジェクト) とメッセージメタデータの両方にアクセスするために使用できる javax.ws.rs.ext.ReaderInterceptorContext 型の引数を 1 つ受け取ります。

ReaderInterceptorContext インターフェイスは以下のように定義されます。

// Java
...
package javax.ws.rs.ext;

import java.io.IOException;
import java.io.InputStream;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;

public interface ReaderInterceptorContext extends InterceptorContext {

    public Object proceed() throws IOException, WebApplicationException;

    public InputStream getInputStream();

    public void setInputStream(InputStream is);

    public MultivaluedMap<String, String> getHeaders();
}

InterceptorContext インターフェイス

ReaderInterceptorContext インターフェイスは、ベース InterceptorContext インターフェイスから継承されたメソッドもサポートします。

InterceptorContext インターフェイスは以下のように定義されます。

// Java
...
package javax.ws.rs.ext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection;

import javax.ws.rs.core.MediaType;

public interface InterceptorContext {

    public Object getProperty(String name);

    public Collection<String> getPropertyNames();

    public void setProperty(String name, Object object);

    public void removeProperty(String name);

    public Annotation[] getAnnotations();

    public void setAnnotations(Annotation[] annotations);

    Class<?> getType();

    public void setType(Class<?> type);

    Type getGenericType();

    public void setGenericType(Type genericType);

    public MediaType getMediaType();

    public void setMediaType(MediaType mediaType);
}

クライアント側での実装例

クライアント側にエンティティーリーダーインターセプターを実装するには、ReaderInterceptor インターフェイスを実装するクラスを定義します。

たとえば、以下のコードは、クライアント側のエンティティーリーダーインターセプター (優先度が 10) の例を示しています。これは、受信応答のメッセージボディーで COMPANY_NAME の全インスタンスを Red Hat に置き換えます。

// Java
package org.jboss.fuse.example;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.annotation.Priority;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;

@Priority(value = 10)
public class SampleClientReaderInterceptor implements ReaderInterceptor {

  @Override
  public Object aroundReadFrom(ReaderInterceptorContext interceptorContext)
          throws IOException, WebApplicationException
  {
    InputStream inputStream = interceptorContext.getInputStream();
    byte[] bytes = new byte[inputStream.available()];
    inputStream.read(bytes);
    String responseContent = new String(bytes);
    responseContent = responseContent.replaceAll("COMPANY_NAME", "Red Hat");
    interceptorContext.setInputStream(new ByteArrayInputStream(responseContent.getBytes()));

    return interceptorContext.proceed();
  }
}

サーバー側での実装例

サーバー側のエンティティーリーダーインターセプターを実装するには、ReaderInterceptor インターフェイスを実装し、@Provider アノテーションを付けるクラスを定義します。

たとえば、以下のコードは、サーバー側のエンティティーリーダーインターセプター (優先度が 10) の例を示しています。これは、受信リクエストのメッセージボディーで COMPANY_NAME の全インスタンスを Red Hat に置き換えます。

// Java
package org.jboss.fuse.example;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.annotation.Priority;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;

@Priority(value = 10)
@Provider
public class SampleServerReaderInterceptor implements ReaderInterceptor {

  @Override
  public Object aroundReadFrom(ReaderInterceptorContext interceptorContext)
          throws IOException, WebApplicationException {
    InputStream inputStream = interceptorContext.getInputStream();
    byte[] bytes = new byte[inputStream.available()];
    inputStream.read(bytes);
    String requestContent = new String(bytes);
    requestContent = requestContent.replaceAll("COMPANY_NAME", "Red Hat");
    interceptorContext.setInputStream(new ByteArrayInputStream(requestContent.getBytes()));

    return interceptorContext.proceed();
  }
}

クライアント側でのリーダーインターセプターのバインド

JAX-RS 2.0 クライアント API を使用すると、エンティティーリーダーインターセプターを javax.ws.rs.client.Client オブジェクトまたは javax.ws.rs.client.WebTarget オブジェクトに直接登録できます。実質的に、リーダーインターセプターはオプションで異なるスコープに適用できるため、インターセプターの影響を受けるのは、特定の URI パスのみです。

たとえば、以下のコードは、client オブジェクトを使用して実行されたすべての呼び出しに適用されるように SampleClientReaderInterceptor インターセプターを登録する方法を示しています。

// Java
...
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
...
Client client = ClientBuilder.newClient();
client.register(SampleClientReaderInterceptor.class);

JAX-RS 2.0 クライアントでインターセプターを登録する方法は、「クライアントエンドポイントの設定」 を参照してください。

サーバー側でのリーダーインターセプターのバインド

サーバー側でリーダーインターセプターを バインド する (つまり Apache CXF ランタイムにインストールする) には、以下の手順を実行します。

  1. 以下のコードフラグメントで示されるように、@Provider アノテーションをリーダーインターセプタークラスに追加します。

    // Java
    package org.jboss.fuse.example;
    ...
    import javax.annotation.Priority;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.ext.Provider;
    import javax.ws.rs.ext.ReaderInterceptor;
    import javax.ws.rs.ext.ReaderInterceptorContext;
    
    @Priority(value = 10)
    @Provider
    public class SampleServerReaderInterceptor implements ReaderInterceptor {
      ...
    }

    リーダーインターセプター実装が Apache CXF ランタイムにロードされると、REST 実装はロードされたクラスを自動的にスキャンし、@Provider アノテーション (スキャンフェーズ) の付いたクラスを検索します。

  2. XML で JAX-RS サーバーエンドポイントを定義する場合 (例: 「JAX-RS サーバーエンドポイントの設定」)、jaxrs:providers 要素のプロバイダーリストにリーダーインターセプターを追加します。

    <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
        xmlns:cxf="http://cxf.apache.org/blueprint/core"
        ...
    >
        ...
        <jaxrs:server id="customerService" address="/customers">
          ...
          <jaxrs:providers>
            <ref bean="interceptorProvider" />
          </jaxrs:providers>
          <bean id="interceptorProvider" class="org.jboss.fuse.example.SampleServerReaderInterceptor"/>
    
        </jaxrs:server>
    
    </blueprint>
    注記

    この手順は、Apache CXF の非標準要件です。厳密に言うと、JAX-RS 標準によれば、インターセプターをバインドするために必要なのは @Provider アノテーションのみです。しかし実際には、標準的なアプローチはやや柔軟性がなく、大規模なプロジェクトに多くのライブラリーが含まれている場合は、プロバイダーの衝突につながる可能性があります。