【入門編】Slim3で始める!GAE/JでWebアプリケーション開発 (第2回)

Controllerの作成

記事登録を行うControllerの実装(続き)

  • 必要な処理の実装PostEntryController を以下のように修正しました。

    PostEntryController.java

    package simplebbs.controller.bbs;
    
    import java.util.Date;
    
    import org.slim3.controller.Controller;
    import org.slim3.controller.Navigation;
    import org.slim3.controller.validator.Validators;
    import org.slim3.util.BeanUtil;
    import org.slim3.util.StringUtil;
    
    import simplebbs.model.bbs.Body;
    import simplebbs.model.bbs.Head;
    import simplebbs.service.bbs.BbsService;
    
    public class PostEntryController extends Controller {
    
        @Override
        public Navigation run() throws Exception {
            if (!isPost() || !validate()) {
                return forward("create");
            }
            Head head = new Head();
            Body body = new Body();
    
            BeanUtil.copy(request, head);
            BeanUtil.copy(request, body);
            head.setPostDate(new Date());
    
            BbsService service = new BbsService();
            service.insert(head, body);
    
            return redirect(basePath);
        }
    
        private boolean validate() {
            Validators v = new Validators(request);
            v.add("subject", v.required(),v.maxlength(50));
            v.add("username", v.required(),v.maxlength(50));
            v.add("text", v.required());
            if (!StringUtil.isEmpty(param("password"))) {
                v.add("password", v.minlength(6),v.maxlength(20));
            }
            return v.validate();
        }
    }
    

    PostEntryController では最初に入力値のバリデーションを行い、有効な入力値であれば記事の登録を行います。

    • バリデーションについて

      PostEntryController#run() の最初で検証を行っています。

      if (!isPost() || !validate()) {
          return forward("create");
      }
      

      isPost() はControllerが提供するメソッドで、リクエストがPOSTメソッドで送信されてきたかどうかを判定します。バリデーションは validate() メソッドを作成してまとめました。上記はPOST以外で遷移してきた場合、またはバリデーションエラーの場合に入力画面( /bbs/create )に戻すようにしています。

      また、Slim3はControllerが実行される前にリクエストパラメータ(request.getParameter())の値を自動的にリクエストスコープにコピー(request.setAttribute())してくれるので、上記のようにいきなりJSPに遷移させても、JSPから(コピーされた)パラメータにアクセスできます。JSPのテキストフォームに ${f:text(…)} と記述するだけで入力値を出力できるのはこのためです。

      バリデーションにはSlim3のValidatorsクラスを使います。

      private boolean validate() {
          Validators v = new Validators(request);
          v.add("subject", v.required(),v.maxlength(50));
          v.add("username", v.required(),v.maxlength(50));
          v.add("text", v.required());
          if (!StringUtil.isEmpty(param("password"))) {
              v.add("password", v.minlength(6),v.maxlength(20));
          }
          return v.validate();
      }
      

      リクエストパラメータの値を検証するので、Validatorsのコンストラクタに request をセットしてインスタンス化します。

      Validators v = new Validators(request);

      バリデーションの指定は次のように行います。

      v.add("subject", v.required(), v.maxlength(50));

      v.add() は第一引数に検証するパラメータ名を、次に検証メソッドを指定します。検証メソッドの指定は可変引数になっているので、1つ以上の任意の数の検証メソッドを指定できるようになっています。上記の例では subject の値に対し、入力必須、最大50文字までであること、の2つの検証を指定しています。検証メソッドは指定した順番に実行され、検証エラーになった時点でそのパラメータの検証は終了します。上記の場合、もし subject が未入力だった場合はそこでバリデーションエラーになるので v.maxlength(50) の検証は実行されません。

      password の検証がif文で括られているのは、アプリ要件でpasswordは未入力を許しており、入力がある場合にだけ値を検証したいためです。

      if (!StringUtil.isEmpty(param("password"))) {
          v.add("password", v.minlength(6),v.maxlength(20));
      }
      

      StringUtilクラスは文字列操作に使えるSlim3の便利クラスです。 StringUtil.isEmpty() は引数が null または長さ0の文字列であるかを判断しています。 param() はControllerが提供するメソッドで、 request.getParameter() を省略したメソッドです。

      一通りパラメータの検証メソッドを v.add() で指定したら、最後に検証を実行します。

      return v.validate();

      v.validate() は指定された検証メソッドを実行し、検証結果を boolean で返します。また、検証エラーだった場合には適切なエラーメッセージを準備してくれます。具体的には、リクエストスコープ上にあるErrorsというMapオブジェクトに Errors#put(“パラメータ名”, “エラーメッセージ”) を行ってくれます。これによりJSPでエラーメッセージを出力することが容易にできるようになります。

      エラーメッセージの文言は、プロジェクトのsrc直下にある application_(location).properties に定義されています。

      application_ja.properties

      validator.required={0}は必須です。
      validator.byteType={0}はバイトでなければいけません。
      validator.shortType={0}は短整数でなければいけません。
      validator.integerType={0}は整数でなければいけません。
      validator.longType={0}は長整数でなければいけません。
      …
      

      {0} の部分はパラメータ名に置き換えられますが、よりユーザーフレンドリーなメッセージにするには、パラメータ名ではなく画面表示用の名称に置き換えた方がよいでしょう。パラメータ名を画面表示用の名称に置き換えるには、同じ application_(location).properties に以下のように定義を追加します。

      label.subject=タイトル
      label.username=お名前
      label.text=本文
      label.password=パスワード
            

      こうすることで「タイトルは必須です。」のようなエラーメッセージになります。

      もし、特定の検証だけ共通エラーメッセージとは違った文言を表示したい場合には、以下のようにすることもできます。

      v.add("subject", v.required("タイトルが未入力だよ!"), v.maxlength(50, "タイトルは50文字までだよ!"));

      検証メソッドにはメッセージ用のStringを指定できるメソッドがあり、このように個別に任意のエラーメッセージを表示することもできます。

    • 記事の登録検証で有効な入力値であることをチェックしたら、いよいよ記事として登録します。入力値をHead、Bodyにセットし、前回作成した BbsService#insert() で登録します。
      Head head = new Head();
      Body body = new Body();
      
      BeanUtil.copy(request, head);
      BeanUtil.copy(request, body);
      head.setPostDate(new Date());
      
      BbsService service = new BbsService();
      service.insert(head, body);
      

      BeanUtil.copy() は2つのBeanオブジェクト間で同名のプロパティのコピーを行うメソッドですが、requestやMapオブジェクトでも使えるので非常に便利です。

      BeanUtil.copy(request, head);
      BeanUtil.copy(request, body);
      

      上記は、リクエストパラメータを head、body のプロパティにコピーしています。これは次のように書いたコードと同じ意味です。

      head.setSubject(asString("subject"));
      head.setUsername(asString("username"));
      head.setPassword(asString("password"));
      body.setText(asString("text"));
      

      asString() はControllerの提供するメソッドで、リクエストスコープの値をString型で取得します。他にも asLong() 、 asKey() など主要なラッパー型に変換して取得できるメソッドがあります。これらを使ったパラメータの移し替えも十分便利ですが、入力フォームのname属性とモデルのプロパティ名を合わせておけば、このようなパラメータの移し替えが BeanUtil.copy() で済みます。また、コピー元とコピー先でプロパティの型が違っていても自動的に型変換を行ってくれます。例えばリクエストパラメータ(値は全てString型)からモデルのLong型やKey型などのプロパティへのコピーも簡単に行えます。

      入力値を head, body にセットしたら BbsService#insert() を使ってデータストアに記事データとして保存します。保存が終わったら<トップ>ページへリダイレクトして記事登録の処理は完成です。ここで forward ではなく redirect で遷移させるのは、登録後に F5 ボタンなどで再読み込みされた際に、記事作成のリクエストが再度送信されないようにするためです。

  • テストケースの実行PostEntryController の実装が終わったら保存してテストを実行してみましょう。
    PostEntryControllerTestの実行イメージ
    結果がグリーンになれば PostEntryController は完成です。実際に画面からも挙動を確認してみます。先にブラウザで開いていた http://localhost:8888/bbs/create の記事作成フォームで、まずは未入力の状態で「記事を投稿する」ボタンを押してみます。
    未入力で投稿した場合の画面イメージ
    バリデーションによってエラーメッセージが表示され、入力フォームの背景色も変わっていることが確認できました。では、いよいよ記事を登録するために有効な値を入れて投稿してみます。
    有効な入力値で投稿した場合の画面イメージ
    <トップ>ページにリダイレクトされて、今投稿した記事が一覧に表示されているはずです。これで記事登録処理は完成です。

作成、修正したソースは以下になります。

  • simplebbs.controller.bbs.PostEntryController.java
  • simplebbs.controller.bbs.PostEntryControllerTest.java
  • application_en.properties
  • application_ja.properties

第2回の内容はここまでですが、入出力を伴う画面の実装を通して、Slim3のController、およびJSPの基本的な実装方法は理解できたと思います。Controllerはバリデーションも含めて非常にシンプルに実装でき、さらにHOT reloadingと相まってサクサク画面の開発を進めることができるようになっています。

次回は残りの画面を実装し、いよいよプロダクションサーバにデプロイを行う予定です。