RESTful Web Service¶
概要¶
ここでは intra-mart Accel Platform 上で TERASOLUNA Server Framework for Java (5.x) のREST APIの実装方法について説明します。
TERASOLUNA Server Framework for Java (5.x) でREST APIを作成する場合には、@RestControllerアノテーションを付けたControllerクラスを作ります。 intra-mart Accel Platform ではControllerクラスの替わりに Web API Maker を使います。 Web API Maker のサービスクラスを intra-mart Accel Platform のControllerクラスに対応させ、 intra-mart Accel Platform のServiceクラスを呼出しビジネスロジックを実行します。
Web API Maker についての詳細は、 「Web API Maker プログラミングガイド」を参照してください。
REST APIの作成¶
TERASOLUNA Server Framework for Java (5.x) Development Guideline の 「チュートリアル(Todoアプリケーション REST編)」 と同じようにtodoを公開するためのREST APIを作成します。
API名 HTTP メソッド パス ステータス コード 説明 GET Todos GET /api/v1/todos 200 (OK) Todoリソースを全件取得する。 POST Todos POST /api/v1/todos 201 (Created) Todoリソースを新規作成する。 Get Todo GET /api/v1/todos/{todoId} 200 (OK) Todoリソースを一件取得する。 PUT Todo PUT /api/v1/todos/{todoId} 200 (OK) Todoリソースを完了状態に更新する。 DELETE Todo DELETE /api/v1/todos/{todoId} 204 (No Content) Todoリソースを削除する。 
REST APIの実装¶
以下の手順で実装を進めます。
 Web API Maker のファクトリクラスの作成TodoServiceFactory を作成します。 Web API Maker のサービスクラスの作成TodoApiService, TodoApiServiceImpl を作成します。 packagesファイルの作成src/main/resources/META-INF/im_web_api_maker/packages を作成します。intra-mart Accel Platform のServiceクラスとして、TodoService, TodoServiceImplが既にあるとします。
Web API Maker のファクトリクラスの作成¶
Web API Maker のサービスクラスに intra-mart Accel Platform の Service クラスをDIするために、 getService() メソッドでは ApplicationContextProvider.getApplicationContext() から Web API Maker のサービスクラスのビーンを取得しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  | package com.sample.todo.api.todo;
import jp.co.intra_mart.foundation.web_api_maker.annotation.ProvideFactory;
import jp.co.intra_mart.foundation.web_api_maker.annotation.ProvideService;
import jp.co.intra_mart.foundation.web_api_maker.annotation.WebAPIMaker;
import jp.co.intra_mart.framework.extension.spring.context.ApplicationContextProvider;
@WebAPIMaker
public class TodoServiceFactory {
    @ProvideFactory
    public static TodoServiceFactory getFactory() {
        return new TodoServiceFactory();
    }
    @ProvideService
    public TodoApiService getService() {
        return ApplicationContextProvider.getApplicationContext().getBean(TodoApiService.class);
    }
}
 | 
ファクトリクラスには @WebAPIMaker を記述します。ファクトリ取得メソッドに @ProvideFactory 、サービスクラス取得メソッドに @ProvideService を記述します。サービスクラス取得メソッドでは、ApplicationContextProvider.getApplicationContext() から Web API Maker のサービスクラスのビーンを取得します。
Web API Maker のサービスクラスの作成¶
TodoApiService インタフェースと TodoApiServiceImpl 実装クラスを作成します。 Web API Makerのアノテーションはインタフェース側に記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50  | package com.sample.todo.api.todo;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jp.co.intra_mart.foundation.web_api_maker.annotation.BasicAuthentication;
import jp.co.intra_mart.foundation.web_api_maker.annotation.Body;
import jp.co.intra_mart.foundation.web_api_maker.annotation.DELETE;
import jp.co.intra_mart.foundation.web_api_maker.annotation.GET;
import jp.co.intra_mart.foundation.web_api_maker.annotation.POST;
import jp.co.intra_mart.foundation.web_api_maker.annotation.PUT;
import jp.co.intra_mart.foundation.web_api_maker.annotation.Path;
import jp.co.intra_mart.foundation.web_api_maker.annotation.Required;
import jp.co.intra_mart.foundation.web_api_maker.annotation.Response;
import jp.co.intra_mart.foundation.web_api_maker.annotation.Variable;
import com.sample.todo.api.NotFoundException;
@BasicAuthentication(pathPrefix = "")
@Todo
public interface TodoApiService {
    @GET(summary = "Todo情報を取得します。", description = "Todo情報の一覧を取得します。")
    @Path("/api/v1/todos")
    @TodoTag
    public List<TodoResource> getTodos();
    @POST(summary = "Todo情報を登録します。", description = "Todo情報を登録します。idは自動採番します。")
    @Path("/api/v1/todos")
    @Response(code = 201)
    @TodoTag
    public TodoResource postTodos(@Required @Body TodoResource todoResource, HttpServletRequest request, HttpServletResponse response);
    @GET(summary = "Todo情報を取得します。", description = "指定したTodo情報を取得します。指定したTodo情報が無い場合、404を返します。")
    @Path("/api/v1/todos/{todoId}")
    @TodoTag
    public TodoResource getTodo(@Required @Variable(name = "todoId") String todoId) throws NotFoundException;
    @PUT(summary = "Todoを完了します。", description = "Todo情報の完了フラグをONにします。")
    @Path("/api/v1/todos/{todoId}")
    @TodoTag
    public TodoResource putTodo(@Required @Variable(name = "todoId") String todoId);
    @DELETE(summary = "Todo情報を削除します。", description = "Todo情報を削除します。")
    @Path("/api/v1/todos/{todoId}")
    @Response(code = 204)
    @TodoTag
    public void deleteTodo(@Required @Variable(name = "todoId") String todoId);
}
 | 
@GET, @POSTなどHTTPメソッドを表しています。 @PathでURIと各メソッドの対応を表しています。@Responseはステータスコードを指定する時に使います。省略時は200になります。@BasicAuthentication はベーシック認証を使うことを示すアノテーションです。@Todo, @TodoTag は APIドキュメントアノテーションです。それぞれのアノテーションについては、 「Web API Maker プログラミングガイド」を参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72  | package com.sample.todo.api.todo;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dozer.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import com.sample.todo.api.NotFoundException;
import com.sample.todo.domain.model.Todo;
import com.sample.todo.domain.service.todo.TodoService;
@Component
public class TodoApiServiceImpl implements TodoApiService {
    @Inject
    TodoService todoService;
    @Inject
    Mapper mapper;
    @Override
    public List<TodoResource> getTodos() {
        Collection<Todo> todos = todoService.findAll();
        List<TodoResource> todoResources = new ArrayList<>();
        for (Todo todo : todos) {
            todoResources.add(mapper.map(todo, TodoResource.class));
        }
        return todoResources;
    }
    @Override
    public TodoResource postTodos(TodoResource todoResource, HttpServletRequest request, HttpServletResponse response) {
        Todo createdTodo = todoService.create(mapper.map(todoResource, Todo.class));
        TodoResource createdTodoResponse = mapper.map(createdTodo, TodoResource.class);
        ServletUriComponentsBuilder uriBuilder = ServletUriComponentsBuilder.fromRequest(request);
        URI uri = uriBuilder.pathSegment(createdTodoResponse.getTodoId()).build().toUri();
        response.addHeader("Location", uri.toASCIIString());
        return createdTodoResponse;
    }
    @Override
    public TodoResource getTodo(String todoId) throws NotFoundException {
        try {
            Todo todo = todoService.findOne(todoId);
            TodoResource todoResource = mapper.map(todo, TodoResource.class);
            return todoResource;
        } catch (ResourceNotFoundException e) {
            throw new NotFoundException(e.getMessage());
        }
    }
    @Override
    public TodoResource putTodo(String todoId) {
        Todo finishedTodo = todoService.finish(todoId);
        TodoResource finishedTodoResource = mapper.map(finishedTodo, TodoResource.class);
        return finishedTodoResource;
    }
    @Override
    public void deleteTodo(String todoId) {
        todoService.delete(todoId);
    }
}
 | 
@Component をつけて TodoApiServiceImplをビーン登録します。@Inject TodoService で Service をDIします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  | package com.sample.todo.api;
import jp.co.intra_mart.foundation.web_api_maker.annotation.Response;
@Response(code = 404)
public class NotFoundException extends Exception {
    private static final long serialVersionUID = 1L;
    public NotFoundException() {
        super();
    }
    public NotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
    public NotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    public NotFoundException(String message) {
        super(message);
    }
    public NotFoundException(Throwable cause) {
        super(cause);
    }
}
 | 
例外時に特定のステータスコードを返したい場合には @Response アノテーションを記述した例外クラスを使います。ここでは TodoApiServiceImpl#getTodo(String) で NotFoundException がスローされた場合、 404のステータスコードを返します。
packagesファイルの作成¶
「src/main/resources/META-INF/im_web_api_maker」 ディレクトリに 「packages」 ファイルを作成し、ファクトリクラスのパッケージを記述します。 このサンプルでは、 「com.sample.todo.api.todo」 になります。