JakartaEE(JavaEE)

JSFプロジェクトで会話スコープを利用してみた

これまでこのブログでは、JSFプロジェクトのForm値と画面遷移処理を定義したクラスをセッションスコープで定義してきたが、これを会話スコープに変更することもできる。

会話スコープとは、ブラウザとサーバーの間で何往復かする間だけオブジェクト(バッキングビーン)を存続させるスコープで、リクエストスコープより長く、セッションスコープより短いスコープとなる。また、会話スコープの開始/終了は、プログラマが明示的に指定する必要がある。

今回は、Form値と画面遷移処理を定義したクラス(InputFormAction.java)を、会話スコープに変更してみたので、そのサンプルプログラムを共有する。

前提条件

以下の記事の実装が完了していること。

JSFプロジェクトで更新・削除機能を追加してみたJakartaEE(旧称:JavaEE)で利用可能なJPA(Java Persistence API)を利用すると、データの参照・追加に...

サンプルプログラムの作成

作成したサンプルプログラムの構成は、以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。

アクションクラスの内容はそれぞれ以下の通りで、画面遷移時に、会話スコープを開始/終了する処理を追加している。

package faces;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.model.SelectItem;
import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;

import common.CommonUtil;
import common.ConversationUtil;
import jpa.UserData;
import jpa.UserDataJpa;
import lombok.Data;
import lombok.ToString;

/**
 * 画面のフォーム値と画面遷移メソッドを定義.
 */
@Named(value="inputFormAction")
// セッションスコープから会話スコープに変更
@ConversationScoped
@Data
@ToString(exclude={"birthMonthItems","birthDayItems","sexItems"})
public class InputFormAction implements Serializable {

    // シリアルバージョンUID
    private static final long serialVersionUID = 7283339629129432007L;

    /** ID */
    private String id;

    /** 名前 */
    @NotEmpty
    @Size(min=1, max=10)
    private String name;

    /** 生年月日_年 */
    private String birthYear;

    /** 生年月日_月 */
    private String birthMonth;

    /** 生年月日_日 */
    private String birthDay;

    /** 性別 */
    @NotEmpty(message="{sex.NotEmpty.message}")
    private String sex;

    /** 性別(ラベル) */
    private String sexLabel;

    /** メモ */
    private String memo;

    /** 確認チェック */
    @AssertTrue
    private Boolean checked;

    /** 生年月日_月(選択リスト) */
    private List<SelectItem> birthMonthItems;

    /** 生年月日_日(選択リスト) */
    private List<SelectItem> birthDayItems;

    /** 性別(選択リスト) */
    private List<SelectItem> sexItems;

    /** UserDataテーブルへアクセスするJPA */
    @Inject
    private UserDataJpa userDataJpa;

    /** 会話スコープマネージャー */
    @Inject
    private Conversation conv;

    /**
     * コンストラクタ生成時に選択リストの値を設定.
     */
    public InputFormAction(){
        // 生年月日_月(選択リスト)
        birthMonthItems = new ArrayList<SelectItem>();
        birthMonthItems.add(new SelectItem("", ""));
        for(Integer i = 1; i <= 12; i++){
            birthMonthItems.add(new SelectItem(String.valueOf(i), String.valueOf(i)));
        }

        // 生年月日_日(選択リスト)
        birthDayItems = new ArrayList<SelectItem>();
        birthDayItems.add(new SelectItem("", ""));
        for(Integer i = 1; i <= 31; i++){
            birthDayItems.add(new SelectItem(String.valueOf(i), String.valueOf(i)));
        }

        // 性別(選択リスト)
        sexItems = new ArrayList<SelectItem>();
        sexItems.add(new SelectItem(String.valueOf(1),"男"));
        sexItems.add(new SelectItem(String.valueOf(2),"女"));
    }

    /**
     * 入力画面への遷移(更新用).
     * @return 入力画面へのパス
     */
    public String toMod(){
    	// 会話スコープを開始
        ConversationUtil.beginConv(conv);

        // 選択されたIDをもつユーザーデータを取得・設定
        this.setSelectItem();

        // 入力画面に遷移
        return "toMod";
    }

    /**
     * 確認画面への遷移.
     * @return 確認画面へのパス
     */
    public String confirm(){
        // 性別(ラベル)を設定
        if(!CommonUtil.isBlank(sex)){
            this.setSexLabel(this.getSexItems().get(
                Integer.parseInt(this.getSex())-1).getLabel());
        }

        // 確認画面に遷移
        return "confirm";
    }

    /**
     * 入力画面に戻る.
     * @return 入力画面へのパス
     */
    public String back(){
        // 入力画面に戻る
        return "back";
    }

    /**
     * 完了画面への遷移.
     * @return 完了画面へのパス
     */
    public String send(){
        // 画面の入力内容を登録または更新
        if(id != null){
            userDataJpa.update(getUserData());
        }else{
            userDataJpa.regist(getUserData());
        }

        // 会話スコープを終了
        ConversationUtil.endConv(conv);

        // 完了画面への遷移
        return "send";
    }


    /**
     * 削除確認画面への遷移(更新用).
     * @return 入力画面へのパス
     */
    public String toDel(){
        // 会話スコープを開始
        ConversationUtil.beginConv(conv);

        // 選択されたIDをもつユーザーデータを取得・設定
        this.setSelectItem();

        // 削除確認画面に遷移
        return "toDel";
    }

    /**
     * 削除確認画面から一覧画面への遷移.
     * @return 一覧画面へのパス
     */
    public String del(){
        // 画面の入力内容を削除
        userDataJpa.delete(getUserData());

        // 会話スコープを終了
        ConversationUtil.endConv(conv);

        // 一覧画面に遷移
        return "del";
    }

    /**
     * 相関チェックを実施し、エラーの場合はエラーメッセージを表示.
     * @param compSysEvent JSFシステムイベント
     */
    public void validate(ComponentSystemEvent compSysEvent) {
        UIComponent component = compSysEvent.getComponent();

        // 生年月日の年・月・日を取得する
        UIInput birthYearUI = (UIInput)component.findComponent("birthYear");
        UIInput birthMonthUI = (UIInput)component.findComponent("birthMonth");
        UIInput birthDayUI = (UIInput)component.findComponent("birthDay");
        String birthYearSt = (String)birthYearUI.getLocalValue();
        String birthMonthSt = (String)birthMonthUI.getLocalValue();
        String birthDaySt = (String)birthDayUI.getLocalValue();

        // 年・月・日がすべて空白値の場合はエラーメッセージを返す
        if(CommonUtil.isBlank(birthYearSt) && CommonUtil.isBlank(birthMonthSt)
            && CommonUtil.isBlank(birthDaySt)){
            addErrorMessage("org.hibernate.validator.constraints.NotEmpty.message"
                , component);
            return;
        }

        // 生年月日が存在しない日付の場合はエラーメッセージを返す
        String dateStr = birthYearSt + CommonUtil.addZero(birthMonthSt) 
            + CommonUtil.addZero(birthDaySt);
        if(!CommonUtil.isCorrectDate(dateStr, "uuuuMMdd")){
            addErrorMessage("date.Invalid.message", component);
        }
    }

    /**
     * 引数のメッセージKeyをもつエラーメッセージを追加.
     * @param messageKey メッセージKey
     * @param component JSFコンポーネント
     */
    private void addErrorMessage(String messageKey, UIComponent component){
        FacesContext context = FacesContext.getCurrentInstance();
        String message = CommonUtil.getMessage(messageKey);
        FacesMessage facesMessage = new FacesMessage(message, message);

        facesMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
        context.addMessage(component.getClientId(), facesMessage);
        context.renderResponse();
    }

    /**
     * 登録時に利用するユーザー情報を生成.
     * @return ユーザー情報
     */
    private UserData getUserData(){
        UserData userData = new UserData();
        try{
            if(this.id != null){
                userData.setId(Integer.parseInt(this.id));
            }
            userData.setName(this.getName());
            userData.setSex(this.getSex());
            userData.setMemo(this.getMemo());
            userData.setBirthYear(Integer.parseInt(this.getBirthYear()));
            userData.setBirthMonth(Integer.parseInt(this.getBirthMonth()));
            userData.setBirthDay(Integer.parseInt(this.getBirthDay()));
        }catch(Exception ex){
            System.err.println(ex);
        }
        return userData;
    }

    /**
     * 選択されたIDをもつユーザーデータを取得・設定.
     */
    private void setSelectItem(){
        // リクエストパラメータの値を取得
        FacesContext fc = FacesContext.getCurrentInstance();
        Map<String,String> params = fc.getExternalContext().getRequestParameterMap();
        String selectId = params.get("selectId");

        // 選択したIDをもつユーザーデータを取得
        UserData userData = userDataJpa.getById(selectId);

        // フィールドの各値に取得した値を設定
        if(userData != null){
            id = String.valueOf(userData.getId());
            name = userData.getName();
            birthYear = String.valueOf(userData.getBirthYear());
            birthMonth = String.valueOf(userData.getBirthMonth());
            birthDay = String.valueOf(userData.getBirthDay());
            sex = userData.getSex();
            sexLabel = this.getSexItems().get(
                 Integer.parseInt(userData.getSex())-1).getLabel();
            memo = userData.getMemo();
        }
    }

}
package faces;

import java.io.Serializable;
import java.util.List;

import javax.enterprise.context.Conversation;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;

import common.ConversationUtil;
import jpa.UserData;
import jpa.UserDataJpa;
import lombok.Getter;

/**
 * USER_DATAリスト取得処理を定義.
 */
//「更新」「削除」リンクを機能させるため、RequestScopedからSessionScopeに変更
@Named(value="userListAction")
@SessionScoped
public class UserListAction implements Serializable{

    // シリアルバージョンUID
    private static final long serialVersionUID = -8890511854883241114L;

    /** USER_DATAリスト */
    @Getter
    private List<UserData> userDataList;

    /** UserDataテーブルへアクセスするJPA */
    @Inject
    private UserDataJpa userDataJpa;

    /** 会話スコープマネージャー */
    // InputFormActionクラスの会話スコープを制御するために宣言
    @Inject
    private Conversation conv;


    /** 一覧画面の初期表示処理 */
    public void initialize(){
        // USER_DATAテーブルの全件を取得する
        userDataList = userDataJpa.getAll();
    }

    /**
     * 入力画面への遷移(追加用).
     * @return 入力画面へのパス
     */
    public String toAdd(){
        // 会話スコープを開始
        ConversationUtil.beginConv(conv);

        return "add";
    }

    /**
     * 一覧画面への遷移.
     * @return 一覧画面へのパス
     */
    public String toList(){
        // 会話スコープを終了
        ConversationUtil.endConv(conv);

        // 初期表示処理を呼び出し、一覧画面に遷移する
        this.initialize();
        return "list";
    }

}

アクションクラスで呼び出される、会話スコープを開始・終了するクラスの内容は、以下の通り。

package common;

import javax.enterprise.context.Conversation;

/**
 * 会話スコープを開始・終了するクラス.
 */
public class ConversationUtil {

    /**
     * 会話スコープを開始.
     * @param conv 会話スコープマネージャー
     */
    public static void beginConv(Conversation conv){
        // 会話スコープを開始していない場合は開始する
        if(conv.isTransient()){
            conv.begin();
        }
    }

    /**
     * 会話スコープを終了.
     * @param conv 会話スコープマネージャー
     */
    public static void endConv(Conversation conv){
        // 会話スコープを終了していない場合は終了する
        if(!conv.isTransient()){
            conv.end();
        }
    }

}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/javaee-jsf-conversation/demoJsf



「FlexClip」はテンプレートとして利用できる動画・画像・音楽などが充実した動画編集ツールだったテンプレートとして利用できるテキスト・動画・画像・音楽など(いずれも著作権フリー)が充実している動画編集ツールの一つに、「FlexCli...

サンプルプログラムの実行

サンプルプログラムの実行結果は以下の通りで、会話スコープを利用して、user_dataテーブルの更新・削除ができることが確認できる。

1) 実行前のuser_dataテーブルの中身は、以下の通り。

select * from USER_DATA order by ID asc
サンプルプログラムの実行結果_1

2) GlassFishサーバーを起動後、Webブラウザ上で「http:// (ホスト名):(ポート番号)/(Webアプリケーションのプロジェクト名)/」とアクセスすると、以下のように、user_dataテーブルの中身が一覧画面(list.html)に表示されることが確認できる。ここでID=4の「削除」リンクを押下する。
サンプルプログラムの実行結果_2

3) 以下のように、削除確認画面が表示されID=4のデータが表示されることが確認できる。また、会話スコープが開始したため、URLにパラメータcidが付与されたことが確認できる。ここで「戻る」ボタンを押下する。
サンプルプログラムの実行結果_3

4) 以下のように、一覧画面が表示され、先ほど削除リンクを押下したID=4のデータが表示されることが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここで再度「削除」リンクを押下する。
サンプルプログラムの実行結果_4

5) 削除確認画面で「送信」ボタンを押下する。なお、会話スコープが再度開始したため、URLにパラメータcidが付与され、cidが1増えていることが確認できる。
サンプルプログラムの実行結果_5

6) 以下のように、一覧画面に遷移し、ID=4のデータが削除されていることが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここでID=3の「更新」リンクを押下する。
サンプルプログラムの実行結果_6

7) 以下のように、入力画面が表示されID=3のデータが表示されることが確認できる。また、会話スコープが開始したため、URLにパラメータcidが付与され、cidが1増えていることが確認できる。ここで「戻る」ボタンを押下する。
サンプルプログラムの実行結果_7

8) 以下のように、一覧画面が表示され、先ほど更新リンクを押下したID=3のデータが表示されることが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここで再度「更新」リンクを押下する。
サンプルプログラムの実行結果_8

9) 入力画面でデータを編集後、「確認」ボタンを押下する。なお、会話スコープが再度開始したため、URLにパラメータcidが付与され、cidが1増えた状態で画面遷移することが確認できる。
サンプルプログラムの実行結果_9_1

サンプルプログラムの実行結果_9_2

10) 確認画面で「送信」ボタンを押下する。
サンプルプログラムの実行結果_10

11) 以下のように、完了画面に遷移することが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここで「一覧画面に戻る」ボタンを押下する。
サンプルプログラムの実行結果_11

12) 以下のように、一覧画面に遷移し、更新後のデータが表示されることが確認できる。
サンプルプログラムの実行結果_12

13) 実行後のuser_dataテーブルの中身は以下の通りで、削除・更新内容が反映されていることが確認できる。

select * from USER_DATA order by ID asc
サンプルプログラムの実行結果_13

要点まとめ

  • 会話スコープとは、ブラウザとサーバーの間で何往復かする間だけオブジェクト(バッキングビーン)を存続させるスコープで、会話スコープの開始/終了は、プログラマが明示的に指定する必要がある。