App Engine スタンダード環境で Java からフォームを使う

Bookshelf の Java チュートリアルのこのパートでは、サーブレットからフォームデータにアクセスする方法を説明します。

このページは複数ページからなるチュートリアルの一部です。最初からセットアップ手順を確認するには、Java Bookshelf アプリに移動してください。

ローカルマシンでのアプリの実行

アプリをローカルで実行するには:

  1. getting-started-java/bookshelf-standard/2-structured-data ディレクトリで、次のコマンドを入力し、ローカル ウェブサーバーを起動します。

    mvn package appengine:run
  2. ウェブブラウザで http://localhost:8080 にアクセスします。

App Engine スタンダード環境へのアプリのデプロイ

アプリを App Engine スタンダード環境にデプロイするには:

  1. Bookshelf アプリにアップロードされる Datastore インデックスを作成するには、少なくとも 1 つの書籍を作成して、[My Books] をクリックします。clean コマンドを使用してアプリをビルドすると、そのローカル インデックスが削除されます。したがって、デプロイするアプリのビルドでは必ずこの作業を行ってください。
  2. getting-started-java/bookshelf-standard/2-structured-data ディレクトリで、次のコマンドを入力してアプリをデプロイします。
    mvn appengine:deploy -Dappengine.appId=[YOUR-PROJECT-ID] -Dappengine.version=[YOUR-VERSION]
    [YOUR-PROJECT-ID] は実際のプロジェクト ID に、[YOUR-VERSION] は実際のバージョン(12 などの文字列値)に置き換えます。
  3. ウェブブラウザに次のアドレスを入力します。

    https://PROJECT_ID.REGION_ID.r.appspot.com

    以下を置き換えます。

アプリを更新した後は、アプリを最初にデプロイしたときと同じコマンドに、同じプロジェクト ID とバージョンを指定することで、更新されたバージョンを再デプロイできます。これにより、現在デプロイされているアプリが上書きされます。更新時のコマンドラインで別のバージョン文字列を指定すると、新しいデプロイによってアプリの新しいバージョンが作成され、それが現行の提供バージョンとして採用されます。

アプリの提供バージョン以外のバージョンを削除することで、コストを削減できます。

アプリのバージョンを削除するには:

  1. Cloud Console で、App Engine の [バージョン] ページに移動します。

    [バージョン] ページに移動

  2. デフォルト以外で削除するアプリのバージョンのチェックボックスをオンにします。
  3. [削除] をクリックして、アプリのバージョンを削除します。

課金対象のリソースをクリーンアップする方法の詳細については、このチュートリアルの最後のステップにあるクリーンアップ セクションをご覧ください。

フォームによるユーザーの投稿に対応する

フォームによって、アプリ内で書籍情報の送信内容を追加、編集できます。

追加 / 編集フォームの画像

ブラウザが /create をリクエストすると、サーブレットの Google Kubernetes Engine が CreateBookServlet を読み込み、doGet メソッドを呼び出します。続いて、このリクエストは /base.jsp サーブレット(ここに /form.jsp サーブレットが含まれる)に転送されます。このフォームを送信すると、CreateBookServletdoPost メソッドが呼び出されます。

CreateBookServlet.java では、doGet メソッドがフォームのリクエストを処理し、JSP ファイルで使用される属性を設定します。

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
  req.setAttribute("action", "Add");          // Part of the Header in form.jsp
  req.setAttribute("destination", "create");  // The urlPattern to invoke (this Servlet)
  req.setAttribute("page", "form");           // Tells base.jsp to include form.jsp
  req.getRequestDispatcher("/base.jsp").forward(req, resp);
}

base.jsp ファイルが、このアプリの HTML ページの土台になります。このファイルは、JSP Standard Tag Library(JSTL) のインクルードで始まります。<c: で始まるタグは JSTL コアタグで、これにより、変数、フロー制御、URL 管理、その他のサポートが提供されます。<fn: で始まるタグは JSTL 関数で、これにより、コレクションや文字列を操作するライブラリが提供されます。Bookshelf アプリで使用される JSTL タグの概要を次に示します。

JSTL のタグ 説明
<c:if ... test を評価し、true の場合はコンテンツをインクルードする。
<c:choose> c:when および c:otherwise の場合のコンテキストを確立する条件付きタグ。
<c:when ... test が true の場合にコンテンツをインクルードする。
<c:otherwise> c:when が true でない場合に、このコンテンツをインクルードする。
<c:import url="" /> url のコンテンツをページ内にインクルードする。
<c:out value="" /> 式を評価して value を求め、エンコード後の結果を出力する。
${fn:escapeXml()} ウェブページの特殊文字をエスケープする。


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<html lang="en">
  <head>
    <title>Bookshelf - Java on Google Cloud Platform</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
  </head>
  <body>
    <div class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
          <div class="navbar-brand">Bookshelf</div>
        </div>
        <ul class="nav navbar-nav">
          <li><a href="/">Books</a></li>
          <c:if test="${isAuthConfigured}"><li><a href="/books/mine">My Books</a></li></c:if>
        </ul>
        <p class="navbar-text navbar-right">
          <c:choose>
          <c:when test="${not empty token}">
          <!-- using pageContext requires jsp-api artifact in pom.xml -->
          <a href="/logout">
            <c:if test="${not empty userImageUrl}">
              <img class="img-circle" src="${fn:escapeXml(userImageUrl)}" width="24">
            </c:if>
            ${fn:escapeXml(userEmail)}
          </a>
          </c:when>
          <c:when test="${isAuthConfigured}">
          <a href="/login">Login</a>
          </c:when>
          </c:choose>
        </p>
      </div>
    </div>
    <c:import url="/${page}.jsp" />
  </body>
</html>

この HTML フォームの作成には JavaServer Pages が使われています。これは Java テンプレート エンジンの一種ですが、サーブレットとして実行されます。次の JSP コードは、このフォームで Title(タイトル)、Author(著者)、Date Published(出版日)、Description(説明)の各テキスト入力フィールドと Cover Image(表紙画像)および Save(保存)ボタンがインクルードされるようにします。

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<div class="container">
  <h3>
    <c:out value="${action}" /> book
  </h3>

  <form method="POST" action="${destination}">

    <div class="form-group">
      <label for="title">Title</label>
      <input type="text" name="title" id="title" value="${fn:escapeXml(book.title)}" class="form-control" />
    </div>

    <div class="form-group">
      <label for="author">Author</label>
      <input type="text" name="author" id="author" value="${fn:escapeXml(book.author)}" class="form-control" />
    </div>

    <div class="form-group">
      <label for="publishedDate">Date Published</label>
      <input type="text" name="publishedDate" id="publishedDate" value="${fn:escapeXml(book.publishedDate)}" class="form-control" />
    </div>

    <div class="form-group">
      <label for="description">Description</label>
      <textarea name="description" id="description" class="form-control">${fn:escapeXml(book.description)}</textarea>
    </div>

    <div class="form-group ${isCloudStorageConfigured ? '' : 'hidden'}">
      <label for="image">Cover Image</label>
      <input type="file" name="file" id="file" class="form-control" />
    </div>

    <div class="form-group hidden">
      <label for="imageUrl">Cover Image URL</label>
      <input type="hidden" name="id" value="${book.id}" />
      <input type="text" name="imageUrl" id="imageUrl" value="${fn:escapeXml(book.imageUrl)}" class="form-control" />
    </div>

    <button type="submit" class="btn btn-success">Save</button>
  </form>
</div>

フォームの送信

doPost メソッドは、req.getParameter() を呼び出してフォームのパラメータを読み取り、Book オブジェクトを作成します。その Book オブジェクトが新しい BookDao オブジェクトに渡されて、新しい書籍のエンティティが Datastore に追加されます。書籍が Datastore に保存されると、ページが /read にリダイレクトされ、保存された内容が表示されます。セッションへのアクセス権と書籍の生成については、後ほど説明します。

@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
  Book book = new Book.Builder()
      .author(req.getParameter("author"))   // form parameter
      .description(req.getParameter("description"))
      .publishedDate(req.getParameter("publishedDate"))
      .title(req.getParameter("title"))
      .imageUrl(null)
      .build();

  BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
  try {
    Long id = dao.createBook(book);
    resp.sendRedirect("/read?id=" + id.toString());   // read what we just wrote
  } catch (Exception e) {
    throw new ServletException("Error creating book", e);
  }
}