ぺーぺーSEのブログ

備忘録・メモ用サイト。

Jersey、JSONでボディマッピングの実装

MessageBodyReaderとMessageBodyWriterを用いてボディをオブジェクトに変換するエンティティプロバイダを作成する。
MessageBodyReaderはリクエストを受信した際ボディをJavaオブジェクトへ変換するロジックを、
MessageBodyWriterはレスポンスを返却する際Javaオブジェクトをボディ電文へ変換するロジックを実装する。
「@Provider」を付与するクラスをプロバイダクラスという。

以下はリクエストボディをオブジェクトへ変換して標準出力し、
そのオブジェクトをそのままレスポンスボディ電文へ変換してレスポンスを返却するサンプル。

Mavenでサンプルプロジェクトを作成する。

mvn archetype:generate
 -DgroupId=org.sample
 -DartifactId=JerseyHelloWorld
 -Dversion=1.0.0
 -DarchetypeArtifactId=maven-archetype-webapp

POMを下記のように作成。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sample</groupId>
  <artifactId>JerseyHelloWorld</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>

  <properties>

    <!-- Generic properties -->
    <jdk.version>1.7</jdk.version>
    <encoding>UTF-8</encoding>

    <!-- Jersey -->
    <jersey.version>1.17.1</jersey.version>

  </properties>

  <build>
    <finalName>JerseyHelloWorld</finalName>
    <plugins>
      <!-- compiler -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>${jdk.version}</source>
          <target>${jdk.version}</target>
          <encoding>${encoding}</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>

    <!-- Jersey -->
    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-core</artifactId>
      <version>${jersey.version}</version>
    </dependency>
    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-servlet</artifactId>
      <version>${jersey.version}</version>
    </dependency>
    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-json</artifactId>
      <version>${jersey.version}</version>
    </dependency>

  </dependencies>
</project>

JSONを扱うリソースクラスを作成。

■リソースクラス(org.sample.resource.SampleResource)

package org.sample.resource;

import java.util.ArrayList;
import java.util.LinkedHashMap;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/sample")
public class SampleResource {

	@POST
	@Path("map")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	public LinkedHashMap<String, String> postMap(LinkedHashMap<String, String> input) {
		System.out.println(input);
		return input;
	}

	@POST
	@Path("list")
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	public ArrayList<LinkedHashMap<String, String>> postList(ArrayList<LinkedHashMap<String, String>> input) {
		System.out.println(input);
		return input;
	}
}

プロバイダクラスを作成する。
「@Provider」を付与する。

リクエストボディを「SampleResource # postMap」の引数「LinkedHashMap input」オブジェクトへ変換するロジックを実装する。
「SampleResource # postMap」のアノテーション「@Consumes({ MediaType.APPLICATION_JSON })」と下記クラスのアノテーション「@Consumes(MediaType.APPLICATION_JSON)」が対応している。

■プロバイダクラス1(org.sample.providere.JsonMapMessageBodyReader)

package org.sample.provider;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;

import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class JsonMapMessageBodyReader implements MessageBodyReader<LinkedHashMap<String, String>> {
	
	private static ObjectMapper mapper = new ObjectMapper();

	@Override
	public boolean isReadable(Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return type == LinkedHashMap.class;
	}

	@Override
	public LinkedHashMap<String, String> readFrom(Class<LinkedHashMap<String, String>> type, Type genericType,
			Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
			throws IOException, WebApplicationException {
		try {
			LinkedHashMap<String,String> map = mapper.readValue(entityStream, new TypeReference<LinkedHashMap<String,String>>(){});
			return map;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

「SampleResource # postMap」の返り値「LinkedHashMap」オブジェクトをレスポンスボディ電文へ変換するロジックを実装する。
「SampleResource # postMap」のアノテーション「@Produces({ MediaType.APPLICATION_JSON })」と下記クラスのアノテーション「@Produces(MediaType.APPLICATION_JSON)」が対応している。

■プロバイダクラス2(org.sample.providere.JsonMapMessageBodyWriter)

package org.sample.provider;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.ObjectMapper;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JsonMapMessageBodyWriter implements MessageBodyWriter<LinkedHashMap<String, String>> {
	
	private static ObjectMapper mapper = new ObjectMapper();

	@Override
	public boolean isWriteable(Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return type == LinkedHashMap.class;
	}

	@Override
	public long getSize(LinkedHashMap<String, String> t, Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return -1;
	}

	@Override
	public void writeTo(LinkedHashMap<String, String> t, Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, Object> httpHeaders,
			OutputStream entityStream) throws IOException,
			WebApplicationException {
		String json = createJson(t);
        try(PrintWriter out = new PrintWriter(entityStream)){
        	out.print(json);
        } catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private String createJson(LinkedHashMap<String, String> map) {
		try {
			String json = mapper.writeValueAsString(map);
			return json;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

リクエストボディを「SampleResource # postList」の引数「ArrayList> input」オブジェクトへ変換するロジックを実装する。
「SampleResource # postList」のアノテーション「@Consumes({ MediaType.APPLICATION_JSON })」と下記クラスのアノテーション「@Consumes(MediaType.APPLICATION_JSON)」が対応している。

■プロバイダクラス3(org.sample.providere.JsonListMapMessageBodyReader)

package org.sample.provider;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;

import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class JsonListMapMessageBodyReader implements MessageBodyReader<ArrayList<LinkedHashMap<String, String>>> {
	
	private static ObjectMapper mapper = new ObjectMapper();

	@Override
	public boolean isReadable(Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return type == ArrayList.class;
	}

	@Override
	public ArrayList<LinkedHashMap<String, String>> readFrom(
			Class<ArrayList<LinkedHashMap<String, String>>> type, Type genericType,
			Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
			throws IOException, WebApplicationException {
		try {
			ArrayList<LinkedHashMap<String, String>> ArrayList = mapper.readValue(entityStream, new TypeReference<ArrayList<LinkedHashMap<String, String>>>(){});
			return ArrayList;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

「SampleResource # postList」の返り値「ArrayList>」オブジェクトをレスポンスボディ電文へ変換するロジックを実装する。
「SampleResource # postList」のアノテーション「@Produces({ MediaType.APPLICATION_JSON })」と下記クラスのアノテーション「@Produces(MediaType.APPLICATION_JSON)」が対応している。

■プロバイダクラス4(org.sample.providere.JsonListMapMessageBodyWriter)

package org.sample.provider;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.ObjectMapper;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JsonListMapMessageBodyWriter implements MessageBodyWriter<ArrayList<LinkedHashMap<String, String>>> {
	
	private static ObjectMapper mapper = new ObjectMapper();

	@Override
	public boolean isWriteable(Class<?> type, Type genericType,
			Annotation[] annotations, MediaType mediaType) {
		return type == ArrayList.class;
	}

	@Override
	public long getSize(ArrayList<LinkedHashMap<String, String>> t, Class<?> type,
			Type genericType, Annotation[] annotations, MediaType mediaType) {
		return -1;
	}

	@Override
	public void writeTo(ArrayList<LinkedHashMap<String, String>> t, Class<?> type,
			Type genericType, Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, Object> httpHeaders,
			OutputStream entityStream) throws IOException,
			WebApplicationException {
		String json = createJson(t);
        try(PrintWriter out = new PrintWriter(entityStream)){
        	out.print(json);
        } catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private String createJson(ArrayList<LinkedHashMap<String, String>> ArrayList) {
		try {
			String json = mapper.writeValueAsString(ArrayList);
			return json;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

アプリケーションクラス(org.sample.application.SampleApplication)を下記のようにする。

■アプリケーションクラス(org.sample.application.SampleApplication)

package org.sample.application;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

import org.sample.provider.JsonListMapMessageBodyReader;
import org.sample.provider.JsonListMapMessageBodyWriter;
import org.sample.provider.JsonMapMessageBodyReader;
import org.sample.provider.JsonMapMessageBodyWriter;
import org.sample.resource.SampleResource;

@ApplicationPath("")
public class SampleApplication extends Application {
	
	@Override
	public Set<Class<?>> getClasses() {
		Set<Class<?>> s = new HashSet<Class<?>>();
		s.add(SampleResource.class);
		s.add(JsonMapMessageBodyReader.class);
		s.add(JsonListMapMessageBodyReader.class);
		s.add(JsonMapMessageBodyWriter.class);
		s.add(JsonListMapMessageBodyWriter.class);
		return s;
	}

}

■web.xml(JAX-RX非対応のコンテナの場合)

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>Jersey REST Service</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>org.sample.application.SampleApplication</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>Jersey REST Service</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

■web.xml(JAX-RX非対応のコンテナ、且、アプリケーションクラス使用しない場合)

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>Jersey REST Service</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <!-- リソースクラスだけでなくプロバイダクラスのパッケージも指定可能 -->
      <!-- 複数指定したい場合は「;」で区切る -->
      <param-value>org.sample.resource;org.sample.provider</param-value>
      <!-- 上記の場合は「org.sample」だけでもOK -->
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>Jersey REST Service</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

以上でサンプル更新完了。
HTTP POSTメソッドで「http://[FQDN]:8080/JerseyHelloWorld/sample/map」、Content-Typeをapplication/json、ボディを下記でアクセスすると

{
    "Key0001": "Value0001",
    "Key0002": "Value0002",
    "Key0003": "Value0003"
}

下記が返却される。

{"Key0001":"Value0001","Key0002":"Value0002","Key0003":"Value0003"}

HTTP POSTメソッドで「http://[FQDN]:8080/JerseyHelloWorld/sample/list」、Content-Typeをapplication/json、ボディを下記でアクセスすると

[
    {
        "Key0001": "Value0001",
        "Key0002": "Value0002",
        "Key0003": "Value0003"
    },
    {
        "Key0001": "Value0001",
        "Key0002": "Value0002",
        "Key0003": "Value0003"
    }
]

下記が返却される。

[{"Key0001":"Value0001","Key0002":"Value0002","Key0003":"Value0003"},{"Key0001":"Value0001","Key0002":"Value0002","Key0003":"Value0003"}]




参考:http://d.hatena.ne.jp/w650/20110119/1295411262