Apache Wink と Ajax を使ってリッチな Java Web アプリケーションを作成する

この記事では、RESTful (REST: Representational State Transfer) な Web サービスを作成するためのフレームワーク、Apache Wink を紹介します。Apache Wink は Apache のインキュベーター・プロジェクトの 1 つです。Apache Wink の目標は RESTful な Web サービスを容易に作成する手段を提供することであり、そのために Java™ アノテーションを使ってクラス内でサービスを定義できるようになっています。

Nathan A. Good, Senior Consultant and Freelance Developer, Freelance Developer

Nathan GoodNathan A. Good はミネソタ州の Twin Cities エリアに住んでいます。彼はプロとしてソフトウェア開発やソフトウェア・アーキテクチャー、システム管理などを行っています。彼はソフトウェアを書いている時以外は、PC やサーバーを構築したり、新しい技術について資料を読んだり、そうした技術に取り組んだり、彼の友人達をオープソース・ソフトウェアに移行させようとしたりしています。彼は数多くの本や記事を執筆、あるいは共同で執筆しており、その中には『Professional Red Hat Enterprise Linux 3』や『Regular Expression Recipes: A Problem-Solution Approach』、『Foundations of PEAR: Rapid PHP Development』などがあります。



2010年 2月 16日

この記事では Apache Wink について紹介し、Apache Wink のインストール方法と、タスク・リストを管理する単純かつ RESTful な Web サービスの例について説明します。この記事で説明する例は、Apache Wink のディストリビューションに付属している Bookmarks の例を少し参考にしていますが、リッチなユーザー・エクスペリエンスを実現するために異なるメッセージ・フォーマットを使用しており、Web ページから Ajax (Asynchronous JavaScript + XML) を使って容易に呼び出し可能なメッセージ・フォーマットになっています。

REST と Ajax

RESTful な Web サービスは特定の構造の URL を使用して公開されるサービスであり、Web サービスで CRUD (Create、Read、Update、Delete) 操作を行うための、単純化されたインターフェースが用意されています。RESTful な Web サービスは、MIME (Multipurpose Internet Mail Extensions) タイプで指定される多種多様なフォーマット (JSON (JavaScript Object Notation)、XML、その他のバイナリー・データなど) のメッセージを利用すること、そして提供することができます。

RESTful な Web サービスは単純なため、Ajax などの Web クライアント技術を使ってこのサービスにアクセスするのはとても簡単です。また JSON をメッセージ・フォーマットに使用すれば、RESTful な Web サービスとのやり取りは一層単純になります。

REST

SOAP とは異なり、RESTful な Web サービスは必ずしもメッセージのペイロードとして XML 文書を要求しておらず、メッセージの構成方法を記述する標準的な XSD (XML Schema Definition) はありません。そのため、RESTful な Web サービスは、URL にアクセスすると文書が返されるだけ、という単純なものになります。実際、Web ページは RESTful な Web サービスというプロファイルに適合するのです。

メッセージ・フォーマットに XML が要求されないため、ほとんどあらゆるタイプのコンテンツをメッセージに含めることができます。例えば、プレーン・テキストを URL に POST するためには、RESTful な Web サービスの POST メソッドを呼び出します。また、他の単純なメッセージ・フォーマット、例えば JSON を使用することもできます。Apache Wink は、指定された MIME タイプのファイルを利用したり、提供したりするためのサービス・メソッドを使用して、HTTP 操作のなかで指定されている MIME タイプを検出します。

RESTful な Web サービスは、さまざまな操作を行うサービスを公開するために、さまざまな HTTP 操作を使用します。いわゆる定義された標準というものはありませんが、どの HTTP 操作が特定のタスクに推奨されるかを規定するガイドラインがいつくかあります。表 1 を見てください。

表 1. REST 操作と URL の例
HTTP 操作URL の例目的
GEThttp://localhost:8080/Task/rest/tasksサービスによって発見されるすべてのタスクを一覧表示します。
GEThttp://localhost:8080/Task/rest/tasks/1ID が 1 のタスクを取得します。
POSThttp://localhost:8080/Task/rest/tasksPOST されたデータを使って新しいタスクを作成します。
PUThttp://localhost:8080/Task/rest/tasks/1ID が 1 の指定タスクを、データ・リクエストを使って更新します。
DELETEhttp://localhost:8080/Task/rest/tasks/1ID が 1 のタスクを削除します。

Ajax

ここ数年で、Ajax は非常に一般的な Web 技術となりました。その結果 Ajax によって、比較的リッチなユーザー・エクスペリエンスが提供されるようになり、標準的な Web ページが効果的にデータを使用できるようになりました。Web ページに Ajax を使用すると、ブラウザーでページ全体を更新しなくてもデータを処理できるため、ユーザーに対して非常に速やかな動作のインターフェースを提供することができます。

Ajax は、Web ブラウザーで利用可能な JavaScript と XML オブジェクトを、Web ページのなかで使用して操作を行うにすぎません。Ajax を使用する JavaScript 関数は、ある URL にアクセスし、その URL からのレスポンスを使って何かを行います。この、JavaScript 関数の動作は、Ajax を使って RESTful な Web サービスを呼び出す上で理想的です。JavaScript メソッドは RESTful な Web サービスの URL に対し、それが通常の Web ページであるかのようにアクセスし、そこから返される結果を解釈するだけでよいからです。

この記事で説明する例では JavaScript ライブラリーとして jQuery を使用し、Ajax を使って RESTful な Web サービスにアクセスします。


Wink をインストールする

Apache Wink のバイナリー・ディストリビューションにはいくつかのライブラリーが含まれており、これらのライブラリーを動的な Web アプリケーション・プロジェクトに含める必要があります。Apache Wink をインストールするためには、単純に Apache Wink のバイナリー・ディストリビューションをダウンロードし、そこに含まれているライブラリーを新しい動的 Web プロジェクトにインポートするだけです。その後、Apache Wink ライブラリーに含まれるクラスを使用して、Java クラスやメソッドにアノテーションを追加します。

この記事の RESTful な Web サービスを作成するためには、サーバー・ライブラリーが必要です。Apache Wink にはクライアント・ライブラリーも含まれており、このライブラリーを使って Java コードから RESTful な Web サービスを呼び出すことができます。しかしこのプロジェクトでは、これらのクライアント・ライブラリーは必要ありません。

まず、Apache Wink のバイナリー・ディストリビューション (「参考文献」を参照) をコンピューターの任意の場所にダウンロードします。このライブラリーを任意のディレクトリーに解凍し、必要なライブラリーを新しい動的な Web プロジェクトにインポートできるようにします。


サンプルの Wink サービス

サンプルの Wink サービスを Eclipse で作成するためには、以下の手順に従います。

  1. Eclipse を開き、Eclipse メニューから「New (新規)」 > 「Project (プロジェクト)」の順に選択します。「New Project (新規プロジェクト)」画面から、「Web」の配下にある「Dynamic Web Project (動的 Web プロジェクト)」を選択し、「Next (次へ)」をクリックします。
  2. プロジェクト名を入力し、ターゲットのサーバー・ランタイムを選択します。この記事の例では、IBM® WebSphere® Application Server Community Edition (Community Edition) v2.1 サーバーのランタイムを使用しています。サーバー・ランタイムが Eclipse のインスタンスにインストールされていない場合には、「New (新規)」をクリックして追加します (図 1)。ターゲットのランタイムを選択したら、「Next (次へ)」をクリックします。
    図 1. 動的な Web プロジェクトを作成する
    ターゲット・ランタイムとして WASCE 2.1 を選択しているウィンドウが表示されています。
  3. 「Next (次へ)」をクリックし、デフォルトのソース・フォルダーと出力フォルダーはそのままにします。
  4. 「Context root (コンテキスト・ルート)」に例えば「Tasks」と入力し、「Content directory (コンテンツ・ディレクトリー)」を「WebContent」のままにします。「Generate web.xml deployment descriptor (web.xml デプロイメント記述子を生成)」を選択し、「Next (次へ)」または「Finish (完了)」をクリックします。
  5. Community Edition v2.1 ランタイムを使用している場合には、次のウィンドウに含まれている情報が Geronimo デプロイメント・プラン・ファイルに入力されます (図 2)。「Group Id (グループ ID)」、「Artifact id (成果物 ID)」、「Version (バージョン)」にそれぞれ、例えば「com.example.group」、「tasks」、「1.0.0」と入力し、入力が終わったら「Finish (完了)」をクリックします。
    図 2. Geronimo デプロイメント・プランの情報を入力する
    Geronimo のデプロイメント情報を示すウィンドウが表示されています。

これでプロジェクトが作成できたので、以下の手順で Apache Wink のライブラリーと他の依存関係をこのプロジェクトにインポートします。

  1. プロジェクトをクリックし、コンテキスト・メニューを開いて「Import (インポート)」を選択します。「File System (ファイル・システム)」を選択し、「Next (次へ)」をクリックします。
  2. Apache Wink ライブラリーを解凍した場所までナビゲートし、dist フォルダーを開きます。以下の JAR (Java Archive) ファイルをインポートします。
    • wink-1.0-incubating.jar
    • wink-common-1.0-incubating.jar
    • wink-server-1.0-incubating.jar
  3. Apache Wink ライブラリーをインポートしたら、バイナリー・ディストリビューションの中にある lib フォルダーから以下の依存関係をインポートします。
    • activation-1.1.jar
    • commons-lang-2.3.jar
    • jaxb-api-2.1.jar
    • jaxb-impl-2.1.4.jar
    • jsr311-api-1.0.jar
    • slf4j-api-1.5.8.jar
    • slf4j-simple-1.5.8.jar
    • stax-api-1.0-2.jar
  4. 最後に、JSON プロバイダーを含む JAR ファイルをインポートする必要があります。このサンプル・サービスは JSON フォーマットでデータを生成するからです。Apache Wink ディストリビューションの ext/wink-json-provider ディレクトリーの中にある JAR ファイル (wink-json-provider-1.0-incubating.jar と json-20080701.jar) を含めます。

必要なライブラリーをインポートしたので、初めての RESTful な Web サービスの作成準備はほとんど完了しました。しかしまず、web.xml ファイルを更新し、Apache Wink の RestServlet を含める必要があります (RestServlet は適切なリソースにリクエストをルーティングします)。

web.xml の例を見てください (リスト 1)。

リスト 1. web.xml ファイル
<?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>Tasks</display-name>
    <description>REST services for managing tasks.</description>

    <servlet>
        <servlet-name>restService</servlet-name>
        <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class>

        <init-param>
            <param-name>applicationConfigLocation</param-name>
            <param-value>/WEB-INF/resources</param-value>
        </init-param>

        <init-param>
            <param-name>propertiesLocation</param-name>
            <param-value>/WEB-INF/tasks.properties</param-value>
        </init-param>

    </servlet>

    <servlet-mapping>
        <servlet-name>restService</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

</web-app>

web.xml ファイルには、サーブレット・クラス (org.apache.wink.server.internal.servlet.RestServlet) の初期化に使われるパラメーターがいくつか含まれています。最初のパラメーターは applicationConfigLocation です。このパラメーターは、サービスの中で作業に使われるリソースの完全修飾クラス名を含むファイルの名前です。

2 番目のパラメーターは propertiesLocation パラメーターです。このパラメーターには、RestServlet クラスが使用するプロパティーを持つファイルの名前が含まれています (これらのプロパティーの完全なリストは「参考文献」を参照してください)。

web.xml ファイルの propertiesLocation は WEB-INF/tasks.properties の場所を指定します。WEB-INF ディレクトリーの中に tasks.properties というファイルを作成し、その中にリスト 2 の設定を追加します。

リスト 2. tasks.properties ファイル
wink.defaultUrisRelative=false

これで web.xml ファイルは RestServlet をロードするように変更されたので、URL が一致した場合にこのサーブレットに呼び出されるリソースを作成します。

リソースを作成するためには、単純に TasksResource というクラスを作成します。このクラスのアノテーションを追加し、さまざまな REST 操作をアノテーションによって処理するメソッドを作成します (リスト 3)。

リスト 3. メソッドの処理を実装する前の TasksResource クラス
package com.nathanagood.services;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.apache.wink.server.utils.LinkBuilders;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

@Path("tasks")
public class TasksResource {

    private static final String TASK = "tasks";
    private static final String ITEM_PATH = "{tasks}";
    private static final String ENCODING = "UTF-8";
    
    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONArray getTasks() {
        // TODO:  Add implementation
        return null;
    }

    @Path(ITEM_PATH)
    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject getTask(@Context LinkBuilders link, @Context UriInfo uri,
            @PathParam(TASK) String taskId) {
        // TODO:  Add implementation
        return null;
    }

    @POST
    @Consumes( { MediaType.APPLICATION_FORM_URLENCODED })
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject createTask(MultivaluedMap<String, String> formData,
            @Context UriInfo uriInfo, @Context LinkBuilders linksBuilders) {
        // TODO:  Add implementation
        return null;
    }

    @Path(ITEM_PATH)
    @PUT
    @Consumes( { MediaType.APPLICATION_JSON })
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject updateTask(JSONObject task, @Context UriInfo uriInfo,
            @Context LinkBuilders linksBuilders) {
        // TODO: Add implementation
        return null;
    }

    @Path(ITEM_PATH)
    @DELETE
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject deleteTask(@Context LinkBuilders link, @Context UriInfo uri,
            @PathParam(TASK) String taskId) {
        // TODO: Add implementation
        return null;
    }
}

GETPOSTPUTDELETE という Java アノテーションは、そのタイプの HTTP 操作に直接マッピングされ、クライアントはその HTTP 操作を使ってサービスにアクセスします。Apache Wink サーブレットは、HTTP 操作に応じた適切なメソッドにリクエストをルーティングします。Apache Wink は HTTP 操作を使う他に、HTTP リクエストの MIME タイプを使用して適切なメソッドにリクエストをマッピングします。Consumes アノテーションと Produces アノテーションを使用すると MIME タイプを定義することができ、これらの MIME タイプが適切なメソッドによって処理されます。どのアノテーションを使った場合も JSON のMIME タイプ (MediaType.APPLICATION_JSON) が返されることに注意してください。POST メソッドは MediaType.APPLICATION_FORM_URLENCODED タイプを使用します。このタイプは後ほど重要になります。サービスの URL への POST に使われる Ajax メソッドが、このコンテンツ・タイプを使用するからです。

実装を追加する

ここまでの段階で作成したメソッドは大部分が空のメソッドでした。この記事では簡単にするために、別々の GET 操作を処理する 2 つのメソッドと、POST 操作を処理するメソッドの実装方法を説明します。getTasks() タスクと getTask() タスクはそれぞれ、すべてのタスクの一覧、ID で指定された個々のタスクを取得します。createTask() メソッドを使用すると、Ajax クライアントのデータ・ストアの中に新しいタスクを作成することができます。

リソースに実装コードを追加する前に、関数のテストのための十分な実装を提供するクラスを作成します。Task クラスには、タスク・リストに保存されているタスクに関する情報が含まれています (リスト 4)。Task クラスを素早く作成するためには、フィールドからアクセサーやコンストラクターを作成する Eclipse のフィーチャーを使用します。

リスト 4. Task クラス
package com.nathanagood.services;

public class Task {

    private boolean complete;
    private String notes;
    private String dueDate;
    private String id;
    private String name;

    public Task(String id, String name, String notes, String dueDate) {
        super();
        this.id = id;
        this.name = name;
        this.notes = notes;
        this.dueDate = dueDate;
    }

    public String getNotes() {
        return notes;
    }

    public String getDueDate() {
        return dueDate;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public boolean isComplete() {
        return complete;
    }

    public void setComplete(boolean complete) {
        this.complete = complete;
    }

    public void setNotes(String description) {
        this.notes = description;
    }

    public void setDueDate(String dueDate) {
        this.dueDate = dueDate;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Task オブジェクトの取得および保存をするための TaskManager クラスを作成します (リスト 5)。

リスト 5. TaskManager クラス
package com.nathanagood.services;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class TaskManager {

    private static TaskManager instance = new TaskManager();
    private static final Map<String, Task> tasks = Collections
            .synchronizedMap(new HashMap<String, Task>());

    /* Initialize some tasks with hard-coded values */
    static {
        tasks.put("1", new Task("1", "Walk the dog", "Remember the leash", "Today"));
        tasks.put("2", new Task("2", "Go to the store", 
            "Buy milk and eggs", "Apr 1, 2010"));
    };

    private TaskManager() {
    }
    
    public static TaskManager getInstance() {
        return instance;
    }
    
    public Collection<Task> getTasks() {
        return Collections.unmodifiableCollection(tasks.values());
    }
    
    public Task getTask(String id) {
        return tasks.get(id);
    }
    
    public void addTask(String id, Task task)
    {
        tasks.put(id, task);
    }

}

これで作業に必要なオブジェクトが得られたので、TaskResource クラスの getTasks() メソッド、getTask() メソッド、createTask() メソッドに少し実装コードを追加します (リスト 6)。

リスト 6. TaskResource のメソッドの実装
// imports snipped
@Path("tasks")
public class TasksResource {

    private static final String TASK = "tasks";
    private static final String ITEM_PATH = "{tasks}";
    private static final String ENCODING = "UTF-8";

    private Task createTask(MultivaluedMap<String, String> 
        formData) throws UnsupportedEncodingException {

        String id = formData.getFirst("id");
        String name = URLDecoder.decode(formData.getFirst("name"), ENCODING);
        String dueDate = URLDecoder.decode(formData.getFirst("dueDate"), ENCODING);
        String notes = URLDecoder.decode(formData.getFirst("notes"), ENCODING);
        boolean complete = Boolean.parseBoolean(formData.getFirst("complete"));

        Task task = new Task(id, name, notes, dueDate);
        task.setComplete(complete);

        return task;
    }

    private JSONObject createJSONObject(Task task) throws JSONException {
        JSONObject obj = new JSONObject();
        obj.put("name", task.getName());
        obj.put("notes", task.getNotes());
        obj.put("dueDate", task.getDueDate());
        obj.put("complete", task.isComplete());
        obj.put("id", task.getId());
        return obj;
    }

    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONArray getTasks() {
        JSONArray result = new JSONArray();

        Collection<Task> tasks = TaskManager.getInstance().getTasks();

        for (Task task : tasks) {
            try {
                result.put(createJSONObject(task));
            } catch (JSONException e) {
                e.printStackTrace();
                throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
            }
        }

        return result;
    }

    @Path(ITEM_PATH)
    @GET
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject getTask(@Context LinkBuilders link, @Context UriInfo uri,
            @PathParam(TASK) String taskId) {

        Task task = TaskManager.getInstance().getTask(taskId);

        if (task == null) {
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }

        JSONObject result;
        try {
            result = createJSONObject(task);
        } catch (JSONException e) {
            e.printStackTrace();
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }
        return result;
    }

    @POST
    @Consumes( { MediaType.APPLICATION_FORM_URLENCODED })
    @Produces( { MediaType.APPLICATION_JSON })
    public JSONObject createTask(MultivaluedMap<String, String> formData,
            @Context UriInfo uriInfo, @Context LinkBuilders linksBuilders) {

        Task newTask;
        JSONObject obj;

        try {
            
            newTask = createTask(formData);
            TaskManager.getInstance().addTask(newTask.getId(), newTask);
            obj = createJSONObject(newTask);
            
        } catch (Exception e) {
            e.printStackTrace();
            throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
        }

        return obj;

    }

// snipped
}

ここに示したメソッドは TaskResource クラスの他に、先ほど作成した 2 つのクラス (TaskManagerTask) を使用します。TaskManager クラスの中には、Task オブジェクトの Java Collection があります。実際の本番シナリオでは、この内部的なコレクションは、データベースからデータをロードして Task オブジェクトに変換するサービスによって置き換えられます。

これらのメソッドを追加すると、REST サービスの動作を確認する準備がほとんど整ったことになります。TaskResource クラスの完全修飾 Java クラス名を WEB-INF/resources ファイルに追加します (リスト 7)。

リスト 7. WEB-INF/resources ファイル
com.nathanagood.services.TasksResource

このサービスをテストするためには、まずこのプロジェクトを WAR (Web Archive) ファイルとしてエクスポートし、次にこの WAR ファイルを Community Edition のインスタンスにデプロイします。「File (ファイル)」 > 「Export (エクスポート)」の順にクリックして「Web」配下の「WAR File (WAR ファイル)」を選択します。ファイルのエクスポート先を選択し、「Finish (完了)」をクリックします (図 3)。

図 3. WAR ファイルをエクスポートする
WAR ファイルのエクスポート先として /home/auser/Desktop/Tasks.war を指定しているウィンドウが表示されています。

今度は WAR ファイルを Community Edition にデプロイします。アプリケーションをデプロイするためには、Administrative Console (管理コンソール) を使う方法が最も簡単です。

Administrative Console にアクセスします。そのためには、Community Edition インスタンスの URL (例えば http://localhost:8080/) にアクセスし、「Administrative Console (管理コンソール)」をクリックします (図 4)。コンソールにログインします。ユーザー名とパスワードをデフォルトから変更していない場合には、ユーザー名に system、パスワードに manager を使用してログインします。

図 4. Administrative Console へのリンク
画面の隅に Administrative Console へのリンクが表示されています。

【ALT】

ログインしたら、「Deploy New Applications (新規アプリケーションをデプロイ)」リンクをクリックします (図 5)。

図 5. 「Deploy New Applications (新規アプリケーションをデプロイ)」リンク
画面の隅に「Deploy New Applications (新しいアプリケーションをデプロイ)」リンクが表示されています。

「Browse (ブラウズ)」をクリックし、プロジェクトからエクスポートした WAR ファイルを選択し、「Install (インストール)」をクリックします。WAR ファイルのインストールが成功すると、図 6 のようなメッセージが表示されます。

図 6. デプロイメントが成功したことを示すメッセージ
アプリケーションが無事にデプロイメントされ、起動したことを示すメッセージが表示されています。

これで Web アプリケーションのデプロイメントに成功したので、Web ブラウザーから http://localhost:8080/Tasks/rest/tasks という URL にアクセスすると、JSON で表現されたタスクが表示されます。JSON で表現された個々のタスクのうちの 1 つを表示するためには、そのタスクの ID を追加します (例えば http://localhost:8080/Tasks/rest/tasks/1 など)。単純にブラウザーから URL にアクセスするだけでサービスをテストできる機能は、RESTful な Web サービスの、もう 1 つの大きなメリットです。

http://localhost:8080/Tasks/rest/tasks という URL によって返される JSON はリスト 8 のようなものです。

リスト 8. JSON 出力の例
[
  {
    "complete": false,
    "dueDate": "Apr 1, 2010",
    "id": "2",
    "name": "Go to the store",
    "notes": "Buy milk and eggs"
  },
  {
    "complete": false,
    "dueDate": "Today",
    "id": "1",
    "name": "Walk the dog",
    "notes": "Remember the leash"
  }
]

Ajax クライアント

新しい RESTful な Web サービスが適切に起動して動作することが確認できたので、Web ページから Ajax を使ってデータを表示する準備が整ったことになります。Ajax 呼び出しの実装を容易にするためには、Ajax 呼び出しの詳細を処理するライブラリー、例えば jQuery JavaScript ライブラリー (「参考文献」) などを使用します。

WebContent ディレクトリーの中に scripts という新しいディレクトリーを作成します。jQuery ライブラリー (1 つの JavaScript ファイル) をダウンロードし、WebContent/scripts ディレクトリーの動的 Web プロジェクトの中にインポートします。


Ajax 呼び出しを行う

jQuery ライブラリーには、URL から JSON データを取得するための専用のメソッドが含まれています。このメソッドは RESTful な Web サービスの GET メソッドを呼び出す上で最適です (リスト 9)。

リスト 9. jQuery.getJSON 関数
$.getJSON(url, [data], [callback]);

また jQuery ライブラリーには、RESTful な Web サービスにデータを POST するための post メソッドも含まれています。jQuery ライブラリーはデータを application/x-www-form-urlencoded という MIME タイプとして POST します。そのため、createTask() メソッドは MediaType.APPLICATION_FORM_URLENCODED を利用します。この呼び出しの一例を示したものがリスト 10 です。

リスト 10. jQuery.post 関数
$.post(url, [data], [callback], [type]);

この 2 つの呼び出しを Web ページ上で使って RESTful な Web サービスを呼び出し、その結果を Web ページ上に表示します。


サンプルの Web ページ

WebContent ディレクトリーの中に index.html ファイルを作成します (リスト 11)。この Web ページには、jQuery スクリプトへの参照と、Ajax 呼び出しを行う関数が含まれています。

リスト 11. 完全な index.html ファイル
<!DOCTYPE html PUBLIC 
"-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Insert title here</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
    //<![CDATA[
    var url = 'http://172.27.35.137:8080/Tasks/rest/tasks';

    function getTasks() {
        $.getJSON(url, function(data) {
            $.each(data, function(i, item) {
                $('#taskList').append(
                        '<p><a href="#" onclick="getTask(\'' + item.id
                                + '\');">' + item.name + '</a></p>');
            });
        });
    }

    function getTask(id) {
        $.getJSON(url + '/' + id, function(data) {
            $('#singleTask').html(
                    '<p>' + data.notes + ' (Due ' + data.dueDate + ') - '
                            + (data.complete ? 'Complete' : 'Incomplete')
                            + '</p>');
        });
    }

    function addTask() {

        var taskId = document.getElementById('taskId').value;
        var taskName = document.getElementById('name').value;
        var taskNotes = document.getElementById('notes').value;
        var taskDue = document.getElementById('due').value;
        var taskComplete = document.getElementById('complete').checked;

        $.post(url, {
            name : taskName,
            id : taskId,
            dueDate : taskDue,
            notes : taskNotes,
            complete : taskComplete
        });

        $('#taskList').html('');
        getTasks();

    }

    $(document).ready(function() {
        getTasks();
    });

    //]]>
</script>

</head>
<body>
<h4>Tasks:</h4>
<div id="taskList"></div>
<h4>Task detail:</h4>
<div id="singleTask"></div>
<form>
<fieldset>
<p><label for="taskId">ID<br />
<input type="text" id="taskId" name="taskId" /></label></p>
<p><label for="name">Name<br />
<input type="text" id="name" name="name" /></label></p>
<p><label for="notes">Notes<br />
<input type="text" id="notes" name="notes" /></label></p>
<p><label for="due">Due<br />
<input type="text" id="due" name="due" /></label></p>
<p><label for="complete">Complete<br />
<input type="checkbox" id="complete" name="complete" /></label></p>
<p><a href="#" onclick=
    addTask();;
>Add Task</a></p>
</fieldset>
</form>
</body>
</html>

この index.html ファイルにはいくつかの JavaScript 関数が含まれており、これらの関数が RESTful な Web サービスからデータを取得し、各タスクをリンクとして表示します。このリンクをクリックすると、クリックされたタスクに関する詳細を JavaScript 関数が取得します。

jQuery ライブラリーと新しい index.html ファイルを追加したら、このプロジェクトを WAR ファイルとして先ほどと同じ手順で再度エクスポートします。そして Administrative Console を使ってこの WAR ファイルを再度デプロイします。WAR ファイルを再度デプロイすると、このサンプル Web ページにアクセスすることができます (URL は http://localhost:8080/Tasks です)。

この Web ページ (図 7) では、Apache Wink で実装された新しい RESTful な Web サービスに対する Ajax からの呼び出しをテストすることができます。リンクをクリックするとタスクの詳細が表示され、また情報を入力して「Add Task (タスクを追加)」リンクをクリックすると新しいタスクが追加されます。

図 7. 描画された index.html Web ページ
「Go to the store (店に行く)」と「Walk the dog (犬を散歩させる)」というタスクと、タスクの詳細を示すウィンドウが表示されています。テキストを入力できるボックスとして、「ID」、「Name (名前)」、「Notes (注記)」、「Due (期限)」が表示されています。

まとめ

Apache Wink は、RESTful な Web サービスを Java プログラミング言語で容易に作成することができるフレームワークです。こうした RESTful な Web サービスは、そのままの状態で、プレーン・テキスト、XML、JSON などの多種多様な MIME タイプのコンテンツを利用、生成することができます。

Java クラスの中でクラスやメソッドに対する単純なアノテーションを使うことで、Ajax を含む多種多様なクライアントに対して公開できる RESTful な Web サービスを作成することができます。Web ページで Ajax を使用すると、ページの更新に関して、より柔軟なユーザー・エクスペリエンスを実現することができます。


ダウンロード

内容ファイル名サイズ
Sample code for this articlecode-apache-wink-ajax.zip25KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Java technology
ArticleID=474226
ArticleTitle=Apache Wink と Ajax を使ってリッチな Java Web アプリケーションを作成する
publish-date=02162010