フェーズリスナを使ったセッションチェック

今回はフェーズリスナを使ったセッションチェックについてです。

【前提条件】

[環境]

【概要】

フェーズリスナはJSFで特定のフェイズを
インターセプトするためのリスナです。

今回のサンプルはレスポンスをレンダリングする前に
セッションの有無をチェックします。
セッションがない場合はログイン画面に遷移するサンプルです。

【サンプルコード】

[ManagedBean]
@ManagedBean(name = "login")
@RequestScoped
public class LoginSample {

    private String userId = null;

    public String login() {

        final FacesContext facesContext = FacesContext.getCurrentInstance();
        final ExternalContext externalContext = facesContext.getExternalContext();

        final HttpSession session = (HttpSession) externalContext.getSession(false);

        session.setAttribute("sessionKey", userId);

        return "menu.xhtml";
    }

    public String logout() {

        final FacesContext facesContext = FacesContext.getCurrentInstance();
        final ExternalContext externalContext = facesContext.getExternalContext();
        externalContext.invalidateSession();

        return "login.xhtml";
    }

    // アクセサメソッドは省略
}

ManagedBean自体はセッションスコープで管理すればよいのですが、
今回はリスナでチェックするため、
画面のユーザIDをセッションに設定しています。

HttpSessionのオブジェクトは
javax.faces.context.ExternalContext#getSessionメソッドを使用して取得します。
ExternalContextはFacesContext#getExternalContextメソッドを使用して取得します。

セッションをクリアするにはExternalContext#ivalidateSessionメソッドを使います。

[ページ]

まずはログイン画面です。
ログイン画面のファイル名は「login.xhtml」です。

<?xml version="1.0" encoding="Windows-31J" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"> 
    <f:view>
        <h:head>
            <meta http-equiv="Content-Type" content="text/html; charset=Windows-31J" />
            <title>ログイン</title>
        </h:head>
        <h:body>
            <h:form id="loginForm">
                <div id="userIdRow">
                    <span id="userIdLabel">ユーザID</span>
                    <span id="userIdInput">
                        <h:inputText id="userId" value="#{login.userId}" />
                    </span>
                </div>
                <div>
                    <h:commandButton action="#{login.login()}" value="ログイン" />
                </div>
            </h:form>
        </h:body>
    </f:view>
</html> 

入力項目としてはユーザIDのみです。

次は遷移先画面です。
遷移先の画面はログアウトボタンのみがあります。
ファイル名は「menu.xhtml」です。

<?xml version="1.0" encoding="Windows-31J" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"> 
    <f:view>
        <h:head>
            <meta http-equiv="Content-Type" content="text/html; charset=Windows-31J" />
            <title>メニュー</title>
        </h:head>
        <h:body>
            <h:form id="loginForm">
                <h:commandButton value="ログアウト" action="#{login.logout()}" />
            </h:form>
        </h:body>
    </f:view>
</html> 
[リスナ]

リスナのサンプルコートは↓になります。

public class SampleListener implements PhaseListener {

    private static final long serialVersionUID = -7462937908829777735L;

    @Override
    public void afterPhase(final PhaseEvent arg0) {

    }

    @Override
    public void beforePhase(final PhaseEvent event) {

        final FacesContext context = event.getFacesContext();
        final String viewId = context.getViewRoot().getViewId();

        final String LOGIN_PAGE = "/login.xhtml";
        if (!LOGIN_PAGE.equals(viewId)) {

            final ExternalContext extContext = context.getExternalContext();
            final HttpSession session = (HttpSession) extContext.getSession(false);
            if ((session == null) || session.getAttribute("sessionKey") == null) {

                try {

                    extContext.dispatch(LOGIN_PAGE);
                    context.responseComplete();
                } catch (IOException ioe) {

                    throw new RuntimeException("エラーです");
                }
            }
        }
    }

    @Override
    public PhaseId getPhaseId() {

        return PhaseId.RENDER_RESPONSE;
    }

}

フェーズリスナを使用するには
javax.faces.event.PhaseListenerクラスを継承します。
PhaseListenerクラスでは下記の三つの抽象メソッドが定義されています。

  • getPhaseIdメソッド
  • beforePhaseメソッド
  • afterPhaseメソッド

getPhaseIdメソッドはインターセプトするタイミングを指定します。
列挙型であるjavax.faces.event.PhaseIdクラスのオブジェクトを返します。

beforePhase/afterPhaseメソッドはインターセプトする対象となった
フェーズの直前/直後に実行されます。


サンプルではbeforePhaseメソッドを使用してセッションのチェックを行います。
ログイン画面からの遷移の場合はセッションのチェックを行わないようにしています。

遷移元を取得するにはjavax.faces.component.UIViewRoot#getViewIdメソッドを使用します。
UIViewRootを取得するにはFacesContext#getViewRootメソッドを使用します。

UIViewRoot#getViewIdメソッドを実行すると
コンテキストルート以降のパスが取得できます。
取得結果はスラッシュで始まります。

フェーズリスナの処理終了を知らせるためには
FacesContex#responseCompleteメソッドを呼び出します。

[faces-config.xml]

作成したリスナを有効にするには
faces-config.xmlに設定を追加します。

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">

    <lifecycle>
        <phase-listener>jp.glory.SampleListener</phase-listener>
    </lifecycle>
</faces-config>

lifecycleタグの子要素であるphase-listenerタグで指定します。
phase-listenerタグにはリスナのクラス名を指定します。

【最後に】

やや無理矢理な感じがしますが、
フェーズリスナを使えばセッションのチェックができます。

何かもっと簡単な方法がありそうな気がするのですが・・・