1月8日

発表の準備

発表用の資料をPowerPointで作成。
致命的なバグがあれば修正。

JSPで文字列を比較する方法

変数が特定の文字列と一致するかどうかを調べる。

<c:set var="hoge" value="abc" />
<c:if test="${hoge == 'abc'}">
hogeはabc
</c:if>

変数が他の変数と一致するかどうかを調べる。

<c:set var="foo" value="abc" />
<c:set var="bar" value="abc" />
<c:if test="${foo == bar}">
fooとbarが一致
</c:if>

HSQLDBのバックアップ

HSQLDBのバックアップを行うには、HSQL Database Manager を開いて次のコマンドを実行する。

SCRIPT 'c:\writable-dir\hsqldb.sql'

12月12日

2014年最後の授業!

作成したWebアプリの基本的機能が動作するようにしましょう!

発表に使うPowerPointの資料を準備しましょう。

最終日の発表・デモについて

Gitのpullでエラーが発生する場合の対処方法

  • プロジェクトを右クリックして[チーム]-[リセット]を選択し、リモート・トラッキングのorigin/masterなどを選択してリセットを実行する。
  • ローカルを削除してプロジェクトをcloneしなおす。

POSTメソッドで400エラーが発生する

単純に文字列でユーザーを検索しようとしていたところで発生。
検索結果でユーザー情報を取得したいという理由で、Controllerのメソッド引数に @Valid @ModelAttribute を指定していたが、JSP側でModelAttributeを構成するために必要な情報が不足していたために400エラーとなった。
Validation不要なので、該当引数を削除することで動作。

エンティティの追加でエラー

DAO経由でエンティティを追加しようとしたところで例外が発生。
@Id アノテーションをユーザー名のところにつけていたため。@Idを付けるメンバーを変更して解決。

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

12月11日

デバッグのヒント

画面レイアウトの調整

画面レイアウトにはあまり凝らないこと!
サイトがきちんと動作させることを優先して作業する。

基本的な画面表示がうまくいかない場合、ブラウザの開発者ツールを使うと原因を調査しやすい。
Internet Explorer の場合、画面右上の歯車アイコンをクリックし「F12 開発者ツール」を選択すると、開発者ツールの画面が表示される。
開発者ツールの左端のメニューから「DOM Explorer」を選択すると、DOMの構造と要素に対するスタイルやCSSがどのように割り当てられているかが確認できる。

dev-tool

Chromeの場合は、画面上で右クリックして[要素を検証]を選択すれば、デベロッパーツールの画面が開く。

12月5日

本日19時から福岡の天神にあるオラクル九州支社で、JavaOne 2014 報告会 at 福岡が開催されます!

行ける人は参加しましょう!

各ページへのリンク

11月28日

ログの出力

システムの動作状況をログに出力して確認する方法。

一般的に Apache commons の Logger を使うことが多い。

コンソールにログを出力するには、以下のようにコードを記述する。

HomeController.java

package jp.abc;

import java.security.Principal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HomeController {
    private static final Log logger = LogFactory.getLog(HomeController.class);

    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home(Model model, Principal p) {
        logger.info("home() called. model =" + model + ", Principal="+ p);
                :
    }

エンティティ数を調べる方法


テーブルに登録されているエンティティの数を調べるには DAO で以下のようにコードを記述する。

UserDaoImpl.java

	public long count() {
    	EntityManager manager = factory.createEntityManager();
		CriteriaBuilder builder = manager.getCriteriaBuilder();
		CriteriaQuery<Long> query = builder.createQuery(Long.class);
		query.select(builder.count(query.from(User.class)));
		return manager.createQuery(query).getSingleResult();
	}

ローカルで修正したファイルをGitリポジトリの状態に戻したい場合

ローカルでファイルを編集したけど、サーバー上にある状態に戻したい場合は以下の操作を行う。

戻したいファイルを右クリックし、コンテキストメニューで[置換]-[HEAD改訂]を選択する。

11月27日

ログインしているユーザー情報の取得(続き)

Principalによるユーザー名の取得

コントローラーの引数に Principal を追加すると、Spring が自動的にログイン中のユーザー情報を設定してくれる。

HomeController.java

    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home(Model model, Principal p) {
        model.addAttribute("name", p.getName());
        model.addAttribute("title", "成績の検索");
        return "home";
    }

Userインスタンスの取得

Principal から取得できるのは名前だけなので、詳細な情報を取得するには、名前をキーにしてUsersテーブルから取得する必要がある。
Usersテーブルから名前をキーにして取得するために、DAOにメソッド定義 findByName() を追加する。

UserDao.java

    public interface UserDao<T> {
        public List<T> getAll();
        public T findById(long id);
        public T findByName(String name);
        public void add(T data);
        public void update(T data);
        public void delete(T data);
        public void delete(long id);
    }

UserDaoImpl で、検索機能を実装する。

UserDaoImpl.java

public class UserDaoImpl implements UserDao<User> {
    private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("persistenceUnit");

    public List<User> getAll() {
        EntityManager manager = factory.createEntityManager();
        CriteriaBuilder builder = manager.getCriteriaBuilder();
        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root<User> root = query.from(User.class);
        query.select(root);
        List<User> list = null;
        try {
            list = manager.createQuery(query).getResultList();
        } catch (NoResultException e) {
            //
        }
        return list;
    }

    public User findById(long id) {
        EntityManager manager = factory.createEntityManager();
        CriteriaBuilder builder = manager.getCriteriaBuilder();
        CriteriaQuery<User> query = builder.createQuery(User.class);
        Root<User> root = query.from(User.class);
        query.select(root).where(builder.equal(root.get("id"), id));
        User u = null;
        try {
            u = manager.createQuery(query).getSingleResult();
        } catch (NoResultException e) {
            //
        }
        return u;
    }

    public User findByName(String name) {
        EntityManager manager = factory.createEntityManager();
        CriteriaBuilder builder = manager.getCriteriaBuilder();
        CriteriaQuery<User> query = builder.createQuery(User.class);
		Root<User> root = query.from(User.class);
		query.select(root).where(builder.equal(root.get("name"), name));
		User u = null;
		try {
			u = manager.createQuery(query).getSingleResult();
		} catch (NoResultException e) {
			//
		}
		return u;
	}

	public void add(User user) {
    	EntityManager manager = factory.createEntityManager();
    	EntityTransaction transaction = manager.getTransaction();
    	transaction.begin();
    	User u = findById(user.getId());
    	if (u == null) {
    		try {
    			manager.persist(user);
    			transaction.commit();
    		} catch (PersistenceException e) {
    			transaction.rollback();
    		}
        	manager.close();
    	} else {
    		throw new PersistenceException("Duplicate user-id : " + u.getId());
    	}
	}

	public void update(User user) {
    	EntityManager manager = factory.createEntityManager();
    	EntityTransaction transaction = manager.getTransaction();
    	transaction.begin();
        try {
            manager.merge(user);
            transaction.commit();
        } catch (PersistenceException e) {
            transaction.rollback();
        }
        manager.close();
    }

    public void delete(User user) {
        EntityManager manager = factory.createEntityManager();
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        try {
            manager.remove(user);
            transaction.commit();
        } catch (PersistenceException e) {
            transaction.rollback();
        }
        manager.close();
    }

    public void delete(long id) {
        delete(findById(id));
    }

}

コントローラでは、DAOを通してUserを取得し、UserのインスタンスをJSPに渡す。

HomeController.java

    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home(Model model, Principal p) {
        UserDao<User> dao = new UserDaoImpl();
        User u = dao.findByName(p.getName());
        model.addAttribute("user", u);
        model.addAttribute("title", "成績の検索");
        return "home";
    }

JSPでは、渡された User のインスタンスから必要な情報を取得するように変更する。

<p>ようこそ ${user.name} さん。</p>

ログインエラーメッセージのカスタマイズ

JSPにエラーメッセージを書き込む方法でよければ、次の方法で可能。

まず spring-securlty.xml で、認証エラーのハンドラを登録する。
defaultFailuerUrl で、パラメータとして error=true を渡せば、JSP側で取得できる。

spring-security.xml

    <sec:http auto-config="true">
        <intercept-url pattern="/**" access="ROLE_USER" />
        <sec:form-login login-page="/login.jsp"
            default-target-url="/home"
            authentication-failure-handler-ref="authenticationFailureHandler" />
        <sec:logout
            logout-url="/logout"
            logout-success-url="/"
            invalidate-session="true"
            delete-cookies="JSESSIONID" />
    </sec:http>

    <beans:bean id="authenticationFailureHandler"
        class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
        <beans:property name="defaultFailureUrl" value="/login.jsp?error=true"/>
        <beans:property name="useForward" value="true"/>
    </beans:bean>

JSPでは、errorパラメータの有無をチェックしてメッセージを表示する。

login.jsp

<h3>ユーザー名とパスワードを入力してください</h3>

<c:if test="${param.error}">
  <span style="color:#ff0000;">ユーザー名またはパスワードが違います。</span>
</c:if>

11月20日

Spring Securityでのログイン画面のカスタマイズ

Spring Security では、デフォルトでは Spring Security 標準のログイン画面が表示される。

login

これを、独自のログイン画面にカスタマイズする。

まず、独自のログイン画面を用意する。
src/main/webapp の直下に login.jsp を作成する。
formタグのactionは、Spring Security で標準のログイン処理に使われるURLを指定し、ユーザー名とパスワードも標準のログインで使用されるパラメータ名 j_username と j_password を使用する。

login.jsp

<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<title>ログイン</title>
</head>

<body>

<h3>ユーザー名とパスワードを入力してください</h3>
<form name='f' action='/<%=request.getContextPath() %>/j_spring_security_check' method='POST'>
 <table>
    <tr><td>ユーザー名:</td><td><input type='text' name='j_username' value=''></td></tr>
    <tr><td>パスワード:</td><td><input type='password' name='j_password'/></td></tr>
    <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
  </table>
</form>

</body>

</html>

spring-security.xml に24行目の設定を追加して、権限なしでこのファイルにアクセスできるようにする。

spring-security.xml

	<sec:http pattern="/" security="none"/>
	<sec:http pattern="/index.jsp" security="none"/>
	<sec:http pattern="/login.jsp" security="none"/>
	<sec:http pattern="/css/**" security="none"/>

ここで、login.jsp にアクセスできることを確認する。

login-1

次に、権限が必要なページにアクセスしたときは、ログイン画面に遷移するように spring-security.xml に設定を追加する。
追加したのは、28行目の1行のみ。

spring-security.xml

	<sec:http pattern="/" security="none"/>
	<sec:http pattern="/index.jsp" security="none"/>
	<sec:http pattern="/login.jsp" security="none"/>
	<sec:http pattern="/css/**" security="none"/>
	<sec:http auto-config="true">
		<intercept-url pattern="/**" access="ROLE_USER" />
		<sec:form-login login-page="/login.jsp" />
		<sec:logout
			logout-url="/logout"
			logout-success-url="/"
			invalidate-session="true"
			delete-cookies="JSESSIONID" />
	</sec:http>

これで、権限が必要なURLにアクセスしたとき、独自のログイン画面 login.jsp が表示されるようになる。
認証をパスできるユーザー名とパスワードを入力すれば、目的のページに遷移する。

ログインしたユーザー情報の取得

ログインしたユーザーを取得するには、コントローラの引数に Principal を追加する。
ここでは、追加した Princical から getName()メソッドでユーザー名を取得し、addAttribute() で name に値を設定している。

HomeController.java

	@RequestMapping(value = "/home", method = RequestMethod.GET)
	public String home(Model model, Principal p) {
		model.addAttribute("name", p.getName());
		model.addAttribute("title", "成績の検索");
		return "home";
	}

home.jsp では、EL式で名前を表示する。

home.jsp

<h1>${title}</h1>

<p>ようこそ ${name} さん。</p>
<p>受験番号を入力してください</p>

より詳細のユーザー情報が必要な場合は、DAO を使用して名前で検索してオブジェクトを取得する。

pom.xmlの追加(書き忘れ)

Spring Security を利用するために、pom.xml の最後に以下の設定を追加する。

		<!-- Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
			<version>3.2.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>3.2.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>3.2.4.RELEASE</version>
		</dependency>

11月13日

後期成績について

後期の成績は以下の基準を適用する。

  1. 個人のPCでチーム開発したWebアプリケーションを動作させる。
    • 個人のPCにGitと連携した開発環境を作る。
    • 個人のPCのHSQLDBデータベースに必要なテーブルを作成する。
    • 担当者がエンティティを生成してコミットする。
    • 他のメンバーはGitからエンティティを取得する。
  2. Backlogを利用してチーム内の課題を管理する
    • 課題を作成すること。
    • 課題を担当すること。
    • 担当した課題を実装しGitにコミット&プッシュすること。
    • 課題を処理済にすること。
    • 実装した課題を他のメンバーに確認してもらい完了にすること。
  3. 最終日に発表・デモ

最終日の発表・デモについて

PowerPoint等で簡単なプレゼンテーションを作成する。
発表内容は以下のとおり。

  1. 概要
    • チーム名
    • メンバー
    • 開発したサービス名
  2. デモ
    • ユーザー登録/ログイン/トップ画面
    • サービス機能の利用
  3. サービスの利用
    • 全員で使ってみる
    • ユーザー登録
    • ログイン
    • サービスの利用
    • ログアウト
  4. 経験したこと
    • 解決が困難だったことできるだけたくさん(バッドノウハウの共有)
    • 開発ツールに感じた便利と不便
    • 工夫した実装方法の紹介(どやっ!)
    • 開発において学んだこと(全員)
    • Backlogで担当した課題ひとつを紹介(全員)
  5. まとめ

トラブルシューティング

問題
DatabaseManagerを起動してアクセスすると接続できるのに、Tomcatからデータベースに接続できない問題が発生。
Tomcat起動時にコンソールにエラー database alias not found が出力される。

解決
サーバービューからサーバーを削除して作り直しら動作するようになった。

11月6日

重要な課題

  • 画面遷移(静的ページで可)
  • ログイン・ログアウト
  • パラメータからデータ受け取り
  • 受け取ったデータの表示
  • データの永続化
  • デザイン

Backlogの課題の粒度を小さくする

「各画面の○○」のような課題は、「A画面の○○」「B画面の○○」のように、画面別の課題にする。
DB連携はエンティティ別に分ける。
基本的な動きが完成したときは課題を完了させ、問題点はバグとして新しく課題を追加する。

Webアプリが期待通りに動作しないときの対処

コンテキストルート( http://localhost:8080/appname/ )にアクセスして404エラーになる場合は、設定に問題があるためにアプリケーションコンテキストが動作していない可能性が高い。
コンソールビューを参照して例外が発生していないか確認する。例外が発生している場合は、例外に表示されているメッセージをヒントにして問題を解決する。

コンテキストルートの設定が間違っていないかを確認する。プロジェクトのプロパティを開き、以下の内容をチェックする。

  • 「プロジェクト・ファセット」を選択して「動的Webモジュール」のバージョンを「2.5」に設定し、行の左端のチェックをONにする。
  • 「Webプロジェクトの設定」を選択して、「コンテキスト・ルート」の設定を確認する。

コンテキストルートにアクセスできて、期待通りに動作しない場合は、変更前のJSPやJavaによって動作している可能性があるので、古いファイルを削除するために以下の操作を試してみる。

  • [プロジェクト]-[クリーン]してプロジェクトをビルドしなおす
  • Tomcatを停止してクリーンする(サーバービューのTomcatを右クリックしてクリーン)
  • Tomcat配下のWebアプリを右クリックして「モジュール・ワーク・ディレクトリーをクリーン」を実行する
  • Tomcat配下のWebアプリを削除して追加しなおす
  • サーバービューでTomcatを削除して再登録する