コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

クラウド活用のためのAndroid業務アプリ開発入門のコードを改変してみたよ JSTL編

前置き

クラウド活用のためのAndroid業務アプリ開発入門

クラウド活用のためのAndroid業務アプリ開発入門


の本を用いてクラウド+Androidアプリの勉強会をすることになったため、その予習を兼ねての記録。
書籍管理アプリを作る都合、書籍という言葉を使った場合には書籍管理の書籍とする。また、「クラウド活用のためのAndroid業務アプリ開発入門」は本とする。

前回に続いて、今回はJSTL+ELで書きなおした。

JSTL

JSTLはGAEに標準で使用できる状態になっている。ただし、そのクラスへの参照がEclipseからは見えない状態になっているため以下のファイルをプロジェクトの直下にlibというファイルを作成しコピーする。

%APPENGINE_HOME%\appengine-java-sdk-1.4.3\lib\tools\jsp\repackaged-appengine-jakarta-jstl-1.1.2.jar
%APPENGINE_HOME%\appengine-java-sdk-1.4.3\lib\tools\jsp\repackaged-appengine-jakarta-standard-1.1.2.jar

コピーした後、その2つのファイルをビルドパスに追加する。具体的には、プロジェクトを右クリックし「Properties」を選択する。表示されたダイアログから、「Java Build Path」を選択する。その画面の「Libraries」タブを選択し、「Add JARS...」ボタンからコピーした2つのファイルを選択する。追加し、完成した後のプロジェクトのパッケージ構成は以下のようになる。

以下、実際に変更したコードを確認していく。前回から変わったコードのみ紹介する。

まず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" version="2.5">
    <servlet>
        <servlet-name>Bookshelf</servlet-name>
        <servlet-class>com.example.gae.bookshelf.BookshelfServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Bookshelf</servlet-name>
        <url-pattern>/action/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <jsp-config>
        <jsp-property-group>
            <url-pattern>*.jsp</url-pattern>
            <el-ignored>false</el-ignored>
            <page-encoding>UTF-8</page-encoding>
            <scripting-invalid>true</scripting-invalid>
            <include-prelude>/WEB-INF/jsp/common.jsp</include-prelude>
            <trim-directive-whitespaces>true</trim-directive-whitespaces>
        </jsp-property-group>
    </jsp-config>
</web-app>

前回との違いは、jsp-configの設定の追加である。jsp-configを使用することでjsp共通の設定を記述することができる。url-paternが「*.jsp」となっているためそのパターンに合致するファイルが対象となる。そのファイルに対して、以下の設定をしている。

項目 設定値 説明
el-ignored false ELを使用する。
page-encoding UTF-8 ページエンコーディングはUTF-8
scripting-invalid true スクリプトレットは使用しない。
include-prelude /WEB-INF/jsp/common.jsp jspファイルの最初に/WEB-INF/jsp/common.jspをインクルードする。共通のヘッダファイルみたいなもの。
trim-directive-whitespaces true 余分なwhitespaceを除去する。詳しくは最後に。

jsp-configの設定をすることでJSPの共通の記述をまとめることができる。J2EE 1.4からの仕様となる。

ここでjsp-configで出てきた/WEB-INF/jsp/common.jspを確認する。

<%@page language="java" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

中身は単純で、ページエンコーディングの設定とtaglibでJSTLの読み込みの設定を行っている。この設定でJSPJSTLが使用できるようになる。ちなみに、最初に行ったjarファイルの参照をしていないと、taglibの設定で警告が出る。

次にBookshelfServletを確認する。大きくは変わってはいないが、少しActionを増やした。JSTL+ELで行う場合にはJSPは完全に表示のみに徹して行うことが多い(はず)。そのため、前回と異なりActionで表示のための情報の取得をすべて行う必要があり、それ用のActionが増えている。さらに、直接JSPに遷移することがエラー以外はなくなっている。

package com.example.gae.bookshelf;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.action.Action;
import com.example.gae.bookshelf.action.DeleteAction;
import com.example.gae.bookshelf.action.JsonAction;
import com.example.gae.bookshelf.action.ListAction;
import com.example.gae.bookshelf.action.NewAction;
import com.example.gae.bookshelf.action.RegistAction;
import com.example.gae.bookshelf.action.UpdateAction;
import com.example.gae.bookshelf.action.UpdateRequestAction;

@SuppressWarnings("serial")
public class BookshelfServlet extends HttpServlet {
    /** actionごとに実行するクラスを指定 */
    private static final Map<String, Class<? extends Action>> ACTION_MAP;

    static {
        ACTION_MAP = new HashMap<String, Class<? extends Action>>();
        ACTION_MAP.put("new", NewAction.class);
        ACTION_MAP.put("update", UpdateAction.class);
        ACTION_MAP.put("update_request", UpdateRequestAction.class);
        ACTION_MAP.put("delete", DeleteAction.class);
        ACTION_MAP.put("regist", RegistAction.class);
        ACTION_MAP.put("json", JsonAction.class);
        ACTION_MAP.put("list", ListAction.class);
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String[] params = request.getRequestURI().split("/");

        String forwardPage = "/action/list";
        if (params.length >= 3) {
            try {
                Action action = ACTION_MAP.get(params[2]).newInstance();
                forwardPage = action.execute(request, response);
                if (forwardPage == null) {
                    return;
                }
            } catch (InstantiationException e) {
            } catch (IllegalAccessException e) {
            }
        }

        request.getRequestDispatcher(forwardPage).forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        String[] params = request.getRequestURI().split("/");

        String forwardPage = "/WEB-INF/jsp/error.jsp";
        try {
            Action action = ACTION_MAP.get(params[2]).newInstance();
            forwardPage = action.execute(request, response);
        } catch (InstantiationException e) {
        } catch (IllegalAccessException e) {
        }

        request.getRequestDispatcher(forwardPage).forward(request, response);
    }
}

図で画面遷移を確認すると以下の通り。

まず、GETメソッドの処理。

  • /action/listのリクエストはListAction#executeを実行
  • /action/registのリクエストはRegistAction#executeを実行
  • /action/jsonのリクエストはJsonAction#executeを実行
  • それ以外のactionは、/action/listにフォワード

次に、POSTメソッドの処理。

  • /action/newのリクエストはNewAction#executeを実行
  • /action/updateのリクエストはUpdateAction#executeを実行
  • /action/update_requestのリクエストはUpdateRequestAction#executeを実行
  • /action/deleteのリクエストはDeleteAction#executeを実

以下、順番にActionを確認していく。

ListAction

まずは、新しく加わったListActionを確認する。ListActionでは/WEB-INF/JSP/index.jspにおいて書籍の一覧を表示するためにリクエストスコープに、書籍の検索結果を格納している。

package com.example.gae.bookshelf.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.BookDao;

public class ListAction implements Action {
    @Override
    public String execute(HttpServletRequest request,
            HttpServletResponse response) {
        request.setAttribute("list", BookDao.selectAll());

        return "/WEB-INF/jsp/index.jsp";
    }
}

ここで、index.jspを先に確認する。JSTLとELを使用しているため以前とは全く異なるイメージとなる。ListActionでリクエストスコープにセットした書籍情報を出力しているだけである。重要なのはListActionとindex.jspは事実上セットで動作することになる。内容的には、if文での分岐はc:ifタグに置き換え、for文でのループはc:forEachタグで置き換えている。

<%@page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <title>書籍管理</title>
 </head>
 <body>
  <a href="/action/regist">書籍登録</a>
  <c:if test="${not empty list}">
   <table class="sample1">
    <thead>
     <th>タイトル</th>
     <th>著者</th>
     <th>出版社</th>
     <th></th>
     <th></th>
    </thead>
   <tbody>
    <c:forEach items="${list}" var="book">
     <tr>
      <td><c:out value="${book.title}"/></td>
      <td><c:out value="${book.author}"/></td>
      <td><c:out value="${book.publisher}"/></td>
      <td><form action="/action/update_request/<c:out value='${book.id}'/>" method="post"><input type="submit" value="更新"></form></td>
      <td><form action="/action/delete/<c:out value='${book.id}'/>" method="post"><input type="submit" value="削除"></form></td>
     </tr>
    </c:forEach>
   </tbody>
  </table>
  </c:if>
  <c:if test="${empty list}">
   書籍が登録されていません。
  </c:if>
 </body>
</html>

以下、ほとんどActionの内容は変わっていないため解説は必要なところのみとする。

/action/regist
package com.example.gae.bookshelf.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.Book;

public class RegistAction implements Action {
    @Override
    public String execute(HttpServletRequest request,
            HttpServletResponse response) {
        request.setAttribute("action", "/action/new");
        request.setAttribute("book", new Book("", "", ""));
        request.setAttribute("label", "登録する");

        return "/WEB-INF/jsp/regist.jsp";
    }
}
/action/update_request
package com.example.gae.bookshelf.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.BookDao;

public class UpdateRequestAction implements Action {
    @Override
    public String execute(HttpServletRequest request,
            HttpServletResponse response) {
        Long bookId = Long.parseLong(request.getRequestURI().split("/")[3]);
        request.setAttribute("action", "/action/update/" + bookId);
        request.setAttribute("book", BookDao.searchByKey(bookId));
        request.setAttribute("label", "更新する");

        return "/WEB-INF/jsp/regist.jsp";
    }
}

ここで、/action/registや、/action/update_requestの遷移先であるregist.jspの中身を確認する。ここもJSTLやELを使用して簡潔に変更している。具体的には、c:outタグでリクエストスコープにある書籍の情報を出力している。

<%@page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <link href="/css/default.css" rel="stylesheet" type="text/css">
  <title>書籍登録</title>
 </head>
 <body>
  <form action="<c:out value='${action}'/>" method="post">
 タイトル<input type="text" name="title" value="<c:out value='${book.title}'/>"><br>
 著者<input type="text" name="author" value="<c:out value='${book.author}'/>"><br>
 出版社<input type="text" name="publisher" value="<c:out value='${book.publisher}'/>"><br>
 <input type="submit" value="<c:out value='${label}'/>">
  </form>
 </body>
</html>
/action/delete

/action/deleteは遷移先を/action/listに変更した。

package com.example.gae.bookshelf.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.BookDao;

public class DeleteAction implements Action {
    @Override
    public String execute(HttpServletRequest request,
            HttpServletResponse response) {
        Long bookId = Long.parseLong(request.getRequestURI().split("/")[3]);
        BookDao.delete(bookId);
        return "/action/list";
    }
}
/action/new

/action/newも遷移先を/action/listにしている。

package com.example.gae.bookshelf.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.BookDao;

public class NewAction implements Action {
    @Override
    public String execute(HttpServletRequest request,
            HttpServletResponse response) {
        String title = request.getParameter("title");
        String author = request.getParameter("author");
        String publisher = request.getParameter("publisher");

        BookDao.create(title, author, publisher);
        return "/action/list";
    }
}
/action/update

/action/updateも遷移先を/action/listにしている。

package com.example.gae.bookshelf.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.example.gae.bookshelf.BookDao;

public class UpdateAction implements Action {
    @Override
    public String execute(HttpServletRequest request,
            HttpServletResponse response) {
        String title = request.getParameter("title");
        String author = request.getParameter("author");
        String publisher = request.getParameter("publisher");

        Long bookId = Long.parseLong(request.getRequestURI().split("/")[3]);
        BookDao.update(bookId, title, author, publisher);
        return "/action/list";
    }
}

Java EE 5

Java EE 5の定義でweb.xmlが書かれているので、Java EE 5から追加されたtrim-directive-whitespacesを試してみた。
まず、web.xmlにtrim-directive-whitespacesを抜かした状態で/actionにアクセスすると出力されるHTMLは以下のようになる。




<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <title>書籍管理</title>
 </head>
 <body>

  <a href="/action/regist">書籍登録</a>
  
   <table class="sample1">
    <thead>
     <th>タイトル</th>
     <th>著者</th>
     <th>出版社</th>
     <th></th>

     <th></th>
    </thead>
   <tbody>
    
     <tr>
      <td>はじめてのGAE</td>
      <td>日本太郎</td>
      <td>GAE出版</td>

      <td><form action="/action/update_request/19" method="post"><input type="submit" value="更新"></form></td>
      <td><form action="/action/delete/19" method="post"><input type="submit" value="削除"></form></td>
     </tr>
    
   </tbody>
  </table>
  
  
 </body>
</html>

ブランク行が点在しているのがわかる。同じ状態でweb.xmlにtrim-directive-whitespacesをつけると以下のHTMLになる。

<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <title>書籍管理</title>
 </head>
 <body>
  <a href="/action/regist">書籍登録</a>
  <table class="sample1">

    <thead>
     <th>タイトル</th>
     <th>著者</th>
     <th>出版社</th>
     <th></th>
     <th></th>
    </thead>

   <tbody>
    <tr>
      <td>はじめてのGAE</td>
      <td>日本太郎</td>
      <td>GAE出版</td>
      <td><form action="/action/update_request/19" method="post"><input type="submit" value="更新"></form></td>
      <td><form action="/action/delete/19" method="post"><input type="submit" value="削除"></form></td>

     </tr>
    </tbody>
  </table>
  </body>
</html>

ブランク行の多くがなくなっているのがわかる。

次回

Slim3で書き換えようか悩み中。跡形も残らず変わってしまうのがどうかと。要望があればやります。