JakartaEE(JavaEE)

JSFプロジェクトでJavaのstaticメソッドをXHTMLファイルで参照してみた

これまで、XHTMLファイル内でJavaのメソッドを呼び出す場合、呼び出すJavaのクラスに@Namedアノテーションを付与してバッキングビーンを生成してきたが、javax.faces.FACELETS_LIBRARIESにタグライブラリを追加する設定を行うことで、XHTMLファイル内で直接Javaのstaticメソッドを呼び出すことができる。

今回は、XHTMLファイル内で直接Javaのstaticメソッドを呼び出してみたので、そのサンプルプログラムを共有する。

前提条件

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

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

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

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

コード変換を行うユーティリティクラスの内容は以下の通りで、性別のコード値(“1”, “2”)から性別(“男”, “女”)を取得する処理を、staticメソッドで実行している。

package common;

/**
 * コード変換を行うユーティリティクラス.
 */
public class CodeConvUtil {

    // 性別をenumで定義
    private enum Sex {
        MAN("1", "男"),
        WOMAN("2", "女");

        // enumのフィールドを定義
        private final String code;
        private final String value;

        // enumのコンストラクタを定義
        private Sex(String code, String value){
            this.code = code;
           this.value = value;
        }
    }

    /**
     * 引数の性別のコードから性別の値を取得する.
     * @param sex 性別のコード
     * @return 性別の値
     */
    public static String getSexValue(String sex){
        String retVal = null;

        // 性別が男の場合
        if(Sex.MAN.code.equals(sex)){
            retVal = Sex.MAN.value;
        // 性別が女の場合
        }else if(Sex.WOMAN.code.equals(sex)){
            retVal = Sex.WOMAN.value;
        // 性別が上記以外の場合
        }else{
            retVal = "不明";
        }

        return retVal;
    }

}

JavaのstaticメソッドをXHTMLファイルで参照できるようにするには、javax.faces.FACELETS_LIBRARIESにタグライブラリを追加する必要がある。そのための設定は、以下の通り。

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
  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-facelettaglibrary_2_0.xsd"
  version="2.0">
  <!-- コード変換を行うユーティリティクラスをFACELETS_LIBRARIESに登録するための設定を追加 -->
  <namespace>http://demo.jsf.com/custom</namespace>
  <function>
    <function-name>getSexValue</function-name>
    <function-class>common.CodeConvUtil</function-class>
    <function-signature>java.lang.String getSexValue(java.lang.String)</function-signature>
  </function>
</facelet-taglib>

javax.faces.FACELETS_LIBRARIESにタグライブラリを追加する処理は以下の通りで、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"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">
  <display-name>demoJsf</display-name>
  <welcome-file-list>
    <!-- 画面遷移先でJSFタグを利用できるよう、Faces ServletのURL(faces/)を先頭に付与 -->
    <!-- 初期表示を入力画面(input.xhtml)から一覧画面(list.xhtml)に変更 -->
    <welcome-file>faces/list.xhtml</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <!-- コード変換を行うユーティリティクラスをFACELETS_LIBRARIESに登録 -->
  <context-param>
    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
    <param-value>/WEB-INF/custom.taglib.xml</param-value>
  </context-param>
  <!-- JSFでのテキスト・テキストエリアの文字化け防止用Filterを設定 -->
  <filter>
    <filter-name>Encoding</filter-name>
    <filter-class>common.EncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>Encoding</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

一覧画面・確認画面・削除確認画面の内容は以下の通りで、性別を表示する際に、追加したタグライブラリのメソッド(getSexValue)を呼び出している。

<?xml version="1.0" encoding="UTF-8" ?>
<!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:h="http://xmlns.jcp.org/jsf/html"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
    xmlns:cust="http://demo.jsf.com/custom">
<h:head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>一覧画面</title>
</h:head>
<h:body>
  <!-- 初期表示時に、UserListActionクラスのinitializeメソッドを呼び出す -->
  <f:metadata>
    <f:viewAction action="#{userListAction.initialize()}"/>
  </f:metadata>

  <p>ユーザーデータテーブル(user_data)の全データ</p>

  <!-- ユーザーデータが存在しない場合 -->
  <c:if test="${empty userListAction.userDataList}">
    ユーザーデータはありません。
  </c:if>

  <!-- ユーザーデータが存在する場合 -->
  <c:if test="${not empty userListAction.userDataList}">
    <table border="1" cellpadding="5">
      <tr>
        <th>ID</th>
        <th>名前</th>
        <th>生年月日</th>
        <th>性別</th>
        <th></th>
        <th></th>
      </tr>
      <c:forEach var="userData" items="#{userListAction.userDataList}">
        <tr>
          <td><h:outputText value="#{userData.id}" /></td>
          <td><h:outputText value="#{userData.name}" /></td>
          <td><h:outputText value="#{userData.birthYear}年 #{userData.birthMonth}月 #{userData.birthDay}日" /></td>
          <td>
            <!-- 性別の表示にコード変換を行うユーティリティクラスを利用 -->
            <h:outputText value="#{cust:getSexValue(userData.sex)}" />
          </td>
          <td>
            <h:form>
              <h:commandLink value="更新" action="#{inputFormAction.toMod()}">
                <f:param name="selectId" value="#{userData.id}" />
              </h:commandLink>
            </h:form>
          </td>
          <td>
            <h:form>
              <h:commandLink value="削除" action="#{inputFormAction.toDel()}">
                <f:param name="selectId" value="#{userData.id}" />
              </h:commandLink>
            </h:form>
          </td>
        </tr>
      </c:forEach>
    </table>
  </c:if>
  <br/><br/>

  <!-- データ追加ボタンを追加 -->
  <h:form>
    <h:commandButton value="データ追加" action="#{userListAction.toAdd()}" />
  </h:form>

</h:body>
</html>
<?xml version="1.0" encoding="UTF-8" ?>
<!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:h="http://java.sun.com/jsf/html"
      xmlns:cust="http://demo.jsf.com/custom">
<h:head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <title>確認画面</title>
   <h:outputStylesheet library="css" name="demoJsf.css"/>
</h:head>
<h:body>
   <p>入力内容を確認し、問題なければ「送信」ボタンを押下してください。</p><br/>
   <h:form>
     <table border="0">
       <tr>
         <td align="left" valign="top">名前:</td>
         <td><h:outputText value="#{inputFormAction.name}" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">生年月日:</td>
         <td><h:outputText value="#{inputFormAction.birthYear}年 #{inputFormAction.birthMonth}月 #{inputFormAction.birthDay}日" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">性別:</td>
         <td>
             <!-- 性別の表示にコード変換を行うユーティリティクラスを利用 -->
             <h:outputText value="#{cust:getSexValue(inputFormAction.sex)}" />
         </td>
       </tr>
       <tr>
         <td align="left" valign="top">メモ:</td>
         <td><h:outputText value="#{inputFormAction.memo}" styleClass="lineBreakFormat" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">確認チェック:</td>
         <td>
            <h:outputText value="確認済" rendered="#{inputFormAction.checked}" />
            <h:outputText value="未確認" rendered="#{inputFormAction.checked == false}" />
         </td>
       </tr>
     </table>
     <br/>
     <h:commandButton value="送信" action="#{inputFormAction.send()}" />&nbsp;
     <h:commandButton value="戻る" action="#{inputFormAction.back()}" />
   </h:form>
</h:body>
</html>
<?xml version="1.0" encoding="UTF-8" ?>
<!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:h="http://java.sun.com/jsf/html"
      xmlns:cust="http://demo.jsf.com/custom">
<h:head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
   <title>削除確認画面</title>
   <h:outputStylesheet library="css" name="demoJsf.css"/>
</h:head>
<h:body>
   <p>下記内容を削除してよろしいでしょうか?問題なければ「送信」ボタンを押下してください。</p><br/>
   <h:form>
     <table border="0">
       <tr>
         <td align="left" valign="top">名前:</td>
         <td><h:outputText value="#{inputFormAction.name}" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">生年月日:</td>
         <td><h:outputText value="#{inputFormAction.birthYear}年 #{inputFormAction.birthMonth}月 #{inputFormAction.birthDay}日" /></td>
       </tr>
       <tr>
         <td align="left" valign="top">性別:</td>
         <td>
             <!-- 性別の表示にコード変換を行うユーティリティクラスを利用 -->
             <h:outputText value="#{cust:getSexValue(inputFormAction.sex)}" />
         </td>
       </tr>
       <tr>
         <td align="left" valign="top">メモ:</td>
         <td><h:outputText value="#{inputFormAction.memo}" styleClass="lineBreakFormat" /></td>
       </tr>
     </table>
     <br/>
     <h:commandButton value="送信" action="#{inputFormAction.del()}" />&nbsp;

     <!-- 戻るボタンを追加 -->
     <!-- その際、フォームの各項目値の入力チェックを省くため、immediate="true"を追加 -->
     <h:commandButton value="戻る" action="#{userListAction.toList()}" immediate="true" />
   </h:form>
</h:body>
</html>

その他、以下のアクションクラスから、性別のコード値(“1”, “2”)から性別(“男”, “女”)を取得する処理を削除している。

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 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(){
        // 確認画面に遷移
        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();
            memo = userData.getMemo();
        }
    }

}

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

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

サンプルプログラムの実行結果は、以下の記事の「サンプルプログラムの実行結果」と同じ結果となる。

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

例えば、一覧画面の表示内容は以下の通りで、性別が正しく変換されていることが確認できる。
サンプルプログラムの実行結果

要点まとめ

  • XHTMLファイル内で直接Javaのstaticメソッドを呼び出すには、呼び出したいメソッドのタグライブラリの設定を、(任意値).taglib.xmlに追加した上で、web.xml内のjavax.faces.FACELETS_LIBRARIESに、タグライブラリを追加する設定を行えばよい。