ひこぽんのーと

覚書と雑記です。

JavaEE 7をやってみよう。 JSF 画面遷移 その2

その1のつづき。

登録画面用のManaged Beanを作る。
その前に、Managed Beanで使う艦種情報検索用の機能をJPAの回で作ったEJBに追加する。

WarshipServiceLocal.java

EJBモジュールのインターフェイス
ここにgetAllTypeList()とgetWarship(Warship key)を追加する。

getAllTypeList()は艦種リストを取得するメソッド
主に艦種選択リストのデータ取得に使用する。

getWarship(Warship key)は艦艇IDから艦艇情報を取得するメソッド
これはまだ使う予定は無いが、更新を作るときに使うのでついでに作る。

package ejbModule;

import java.util.List;

import javax.ejb.Local;

import model.Warship;
import model.WarshipType;

@Local
public interface WarshipServiceLocal {
    public List<Warship> getAllList();
    public List<WarshipType> getAllTypeList();
    public Warship getWarship(Warship key);
}

WarshipService.java

getAllTypeList()とgetWarship(Warship key)の実装を追加する。
getAllTypeList()に関しては、getAllListメソッドと同じ作りなので楽勝。

条件を付ける方にしても、プライマリキーで検索する場合では、
すでにEntityManaegerに用意されているfindメソッドを使えば良いのでこれまた楽勝。
findメソッドの第1パラメータが取得するクラス、第2パラメータがキー値。
この場合、取得クラスはWarshipクラスで、キー値はStringのID。

もっとも、対象テーブルが複合キーだったりすると、
このやり方は通用しないのだが(なんせ、第2パラメータに渡せるのはキー値そのものだから)、
そのことについてはまた、別の機会に。

package ejbModule;

import java.util.List;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import model.Warship;
import model.WarshipType;

/**
 * Session Bean implementation class WarshipService
 */
@Stateless
@LocalBean
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class WarshipService implements WarshipServiceLocal {
    @PersistenceContext
    private EntityManager entityManager;
    
    /**
     * Default constructor. 
     */
    public WarshipService() {
    }

    @Override
    public List<Warship> getAllList() {
        return entityManager.createNamedQuery("Warship.findAll", Warship.class).getResultList();
    }

    @Override
    public List<WarshipType> getAllTypeList() {
        return entityManager.createNamedQuery("WarshipType.findAll", WarshipType.class).getResultList();
    }

    @Override
    public Warship getWarship(Warship key) {
        return entityManager.find(Warship.class, key.getId());
    }
}

EJBを直したら、次はManaged Bean。

WarshipDefault.java

艦種、艦名保持用のフィールド、
艦種リスト取得、
艦艇取得を共通化するためのabstractクラス。

編集画面でも同じような構成の画面になるので、こんなクラスを作成してみた。
新規登録画面でのManaged Beanはこれを継承したものとなる。

package app.manage;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.model.ListDataModel;

import model.Warship;
import model.WarshipType;
import ejbModule.WarshipServiceLocal;

public abstract class WarshipDefault {
    @EJB
    protected WarshipServiceLocal ejb;

    /** 入力フィールド値の格納用 **/
    private Warship warship;
    
    @PostConstruct
    private void init() {
        warship = new Warship();
        warship.setWarshipType(new WarshipType());
    }
    
    public ListDataModel<WarshipType> findAllWarshipType() {
        return new ListDataModel<WarshipType>(ejb.getAllTypeList());
    }
    
    public void find() {
        Warship key = new Warship();
        key.setId(getWarshipId());
        warship = ejb.getWarship(key);
    }
    
    public Warship getWarship() {
        return warship;
    }
    
    public void setWarship(Warship warship) {
        this.warship = warship;
    }
    
    public abstract Integer getWarshipId();
    
    public abstract void update();
}

WarshipRegister.java

今のところ登録処理は作ってないので、
実質、空実装に等しいが、登録メソッドも作った。

package app.manage;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@RequestScoped
@Named(value="WarshipReg")
public class WarshipRegister extends WarshipDefault {

    @Override
    public Integer getWarshipId() {
        // 新規登録ではfindを使わないため、空実装
        return null;
    }

    @Override
    public void update() {
        // TODO ejbに登録メソッドを作成後に実装
    }

}

ここまでで、ひとまず、Java側はおしまい。
新規登録画面のinsert.xhtml艦種リストの呼び出しと登録メソッドの呼び出しを追加する。

insert.xhtml

入力項目の格納先には、Managed Beanに用意したフィールドをあてがう*1
フィールドはWarshipクラスなので、Warshipクラスのメンバをそれぞれ指定することになる。

艦種選択リストのOptionタグに当たる部分については、selectItemsタグを使ってListDataModelから生成できる。
selectItemsタグはselectOneMenuタグの中に記述する。
使い方はdataTableタグと似ている。
valueにはリスト値を、varにはその要素を表す変数名を指定できる点は、全く同じ。
他に、itemLabelには表示する値、itemValueには選択時に送信される値を設定する。
itemLabelEscpedは文字通り表示値をエスケープするか否かで、trueにすれば、
HTMLコードもそのまんま文字として出力できる。

あとは、登録時の呼び出しとして、
登録ボタンにactionListenerを追加した。
actionListenerには、登録メソッドたる、updateメソッドの呼び出しを記述した。

actionListenerはManaged Beanのメソッドの呼び出しが行える。
ただし、呼び出すメソッドにはパラメータを持たせることが出来ない。
戻り値はvoidかStringであることが条件。

JSFでは、actionLisener → actionの順で処理が行われるので、
actionListenerを使えば、メソッド呼び出し後に画面遷移を行うことができる。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets" 
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core">
    <h2><h:outputLabel value="#{staticText['warship.reg.lblSreenTitle']}" /></h2>
    <hr/>
    <h:messages id="errorText" layout="table" errorClass="error-message"/>
    <h:outputLabel value="#{staticText['warship.reg.lblWarshipType']}" />
    <h:selectOneMenu id="warshipType" value="#{WarshipReg.warship.warshipType.typeId}" styleClass="warship-type-select" >
        <f:selectItems value="#{WarshipReg.findAllWarshipType()}" var="warshipType"
                itemLabel="#{warshipType.name}" itemValue="#{warshipType.typeId}" itemLabelEscaped="true" />
    </h:selectOneMenu>
    <h:outputLabel value="#{staticText['warship.reg.lblWarshipName']}" />
    <h:inputText id="warshipName" value="#{WarshipReg.warship.name}" styleClass="warship-name-input" required="true" autocomplete="off" />
    <br/>
    <h:commandButton value="#{staticText['warship.reg.btnRegister']}" action="catalog" actionListener="#{WarshipReg.update()}" />
    <h:commandButton value="#{staticText['warship.reg.btnRegisterAndRegister']}" />
</ui:composition>

といったところで、テスト実行。
画面を動かしてメニューから新規登録を選ぶと入力画面へ遷移でき、
入力画面では艦種選択リストが表示できている。
また、艦名を入力した後に登録ボタンを押すと一覧画面に遷移できる。

f:id:nagamitsu1976:20151030145214p:plain
f:id:nagamitsu1976:20151106180159p:plain

こんな感じで選択リストの表示と、登録ボタンの処理はできた。
次回はJPAの続きとして新規登録(Insert)の実装を行う。

*1:ほんとは、入力項目と1対1とフィールドを設けたほうがわかりやすいとは思うけど、色々思ってこうした

JavaEE 7をやってみよう。 JSF 画面遷移 その1 navigation-rule

前回、簡単な画面を作成したので、
今回はそれを拡張して、新規登録画面への画面遷移を作る。
サイドメニューの「艦艇登録」リンクを押下して、登録画面へ遷移するイメージ。

通常であれば、HTMLのリンクには、遷移先のURLを記載すればOKなのだが、
それではミもフタもないので、JSFの機能であるNavigation-ruleを使ってみる。

Navigation-ruleはfaces-config.xmlに記載する画面遷移設定のことで、
条件をXMLに記載しておけば、
JSF側のソースにURLを書かずとも画面遷移を構築できる。
遷移元→遷移先のURLを設定ファイルに持つため、
URL変更の手間も軽減できる*1……かもしれない。

といったところで、実装開始。
まず、遷移元のソースはあるので、遷移先のソースを作る。
はじめに書いた通り、新規登録の入力画面を作る。

jsf/contents/warship/insert.xhtml

入力項目である艦種と艦名、登録ボタンを持つ画面を画面部品として作る。
画面項目などの項目名、ボタン名はリソースファイルに持たせた。
入力値の格納や登録処理を行うManaged Beanはまだ未作成のため、
画面のソースにも処理は記載していない。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets" 
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core">
    <h2><h:outputLabel value="#{staticText['warship.reg.lblSreenTitle']}" /></h2>
    <hr/>
    <h:messages id="errorText" layout="table" errorClass="error-message"/>
    <h:outputLabel value="#{staticText['warship.reg.lblWarshipType']}" />
    <h:selectOneMenu id="warshipType" value="" styleClass="warship-type-select" >
    </h:selectOneMenu>
    <h:outputLabel value="#{staticText['warship.reg.lblWarshipName']}" />
    <h:inputText id="warshipName" value="" styleClass="warship-name-input" required="true" autocomplete="off" />
    <br/>
    <h:commandButton value="#{staticText['warship.reg.btnRegister']}" action="catalog" />
    <h:commandButton value="#{staticText['warship.reg.btnRegisterAndRegister']}" />
</ui:composition>
site-title= 艦艇これくしょん
copyright=(C) ひこぽん(nagamitsu1976)
# 画面ラベル
# サイドメニュー
warship.menu.lnkCatalog=艦艇図鑑
warship.menu.lnkRegister=艦艇登録
warship.menu.lnkEdit=艦艇編集
warship.menu.lnkRemove=艦艇削除

# 艦艇登録
warship.reg.lblSreenTitle=艦艇登録
warship.reg.lblWarshipType=艦種
warship.reg.lblWarshipName=艦名
warship.reg.btnRegister=登録
warship.reg.btnRegisterAndRegister=連続登録
warship.reg.btnEdit=編集
warship.reg.btnBack=戻る
.warship-type-select {
    width: 120px;
    margin: 10px;
}
.warship-name-input {
    width: 220px;
    margin: 10px;
}

jsf/view/warship/insert.xhtml

上で書いた入力画面部品をレイアウトに適用した登録画面のソース。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition 
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html"
        template="/jsf/layout/common/layout.xhtml">

    <ui:define name="title">#{staticText['warship.menu.lnkRegister']}</ui:define>

    <ui:define name="content"> 
        <ui:include src="/jsf/contents/warship/insert.xhtml"/>           
    </ui:define>

</ui:composition>

jsf/contents/common/menu.xhtml

メニューの画面部品。
commandLinkタグを使って、メニューのリンクを追加した。
また、文字列をリソースファイルから取得するように合わせて修正。
ここで注目するのは、リンク先の指定について。
URLはタグ内に一切含めていない。action属性に文字列を設定しただけ。
comandLinkやcommandButtonタグのaction属性には基本、文字列を定義する。
JSFではリンクやボタンを押した時、action属性の値を使ってnavigation-ruleを参照し、
条件に該当するルールを特定する。
特定できたnaviation-ruleから遷移先のURLを取得して画面遷移を行う、というわけ。
ここでは"catalog", "register", "edit", "remove"というルールがあると想定している。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:h="http://java.sun.com/jsf/html">
    <div><h:commandLink action="catalog" value="#{staticText['warship.menu.lnkCatalog']}" immediate="true"/></div>
    <div><h:commandLink action="register" value="#{staticText['warship.menu.lnkRegister']}" immediate="true" /></div>
    <div><h:commandLink action="edit" value="#{staticText['warship.menu.lnkEdit']}" immediate="true" /></div>
    <div><h:commandLink action="remove" value="#{staticText['warship.menu.lnkRemove']}" immediate="true" /></div>
</ui:composition>

faces-config.xml

navigation-ruleをfaces-config.xmlに追加した。
navigation-ruleタグで囲った部分が今回の追加部分。

from-view-idタグはこのルールを適用するページのURLを指定する。
サンプルのようにアスタリスクを使ったりして条件範囲を指定することも可能。
サンプルではサイドメニュー用のnavigation-ruleなので、
遷移元画面が/jsf/view/配下すべての画面が対象となっている。

navigation-caseタグに囲われたブロックに
判定条件であるfrom-outcomeタグと
遷移先を指定するためのto-view-idを記載する。

from-outcomeタグに定義した値を
HTML側のcommandLinkタグのaction属性に設定すれば、
ここの値を読み取ってサーバーが勝手にリンクを作ってくれる。

<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
    version="2.2">
    <application>
        <locale-config>
            <default-locale>ja</default-locale>
        </locale-config>
        <resource-bundle>
            <base-name>resources.statictext</base-name>
            <var>staticText</var>
        </resource-bundle>
    </application>

 <navigation-rule>
  <!-- 遷移元のJSP名を記述 -->
  <from-view-id>/jsf/view/*</from-view-id>
  <!-- アクションの結果によって遷移先を変更 -->
  <navigation-case>
   <from-outcome>catalog</from-outcome>
   <to-view-id>/jsf/view/warship.xhtml</to-view-id>
  </navigation-case>
  <navigation-case>
   <from-outcome>register</from-outcome>
   <to-view-id>/jsf/view/insert.xhtml</to-view-id>
  </navigation-case>
 </navigation-rule>

</faces-config>

画面とリンクの定義をしたら、動作確認をしてみる。
サイドメニューから艦艇図鑑や艦艇登録のリンクを押すと、
ルール通り遷移することが確認できる。
艦艇編集や艦艇削除のリンクは、
ルールを定義していないので押下してもなにも起きない。
f:id:nagamitsu1976:20151030145214p:plain
f:id:nagamitsu1976:20151030145622p:plain

次回は登録画面用のManaged Beanを作って、
登録処理のイベントを作る。

といったところで次回へ続く。

*1:軽減可能かどうかは定義次第だと思うよ、実際。

JavaEE 7をやってみよう。 JSF テンプレートXML その3

JSF テンプレートについて補足。

JSF テンプレートXML その1でHTML画面の合成について書いたが、
その際に載せたHTMLがあんまり良くないらしい。

実行時に合成された画面のソースを表示してみると複数のBODYタグが含まれている。
というのも"画面パーツ"として掲載したHTMLはすべて、
HTMLタグから始まって、BODYタグなどをもつ、HTML4の形式で作った。
それらをJSFのテンプレートを通して合成しているので、
出来上がるページにはHTMLタグやBODYタグが複数存在してしまう。

それであっても、ブラウザは想定通りに画面を表示してしまうので、
無視していたのだが、後々、これが原因で問題が発生した。

必須入力チェックのエラーメッセージ表示処理を作成した際、
画面には正しくエラーメッセージが表示できるのだが、
その度にサーバーログに警告が書き込まれる、といった事象がそれ。

警告: FacesMessage がキューに入っていますが、表示されていません。

この警告をいくら調べても解決方法がわからない。
以前、バリデータの回ですでに確認済みの記法で書いたのに警告が出る。
しかも、バリデータの回ではこんな警告は出なかった。

結局のところ、テンプレートを使わずにHTMLを書いてみたら警告が出なくなったので、
画面合成に問題があることがわかった。

正しい合成の仕方としては、次のようになる。

  • 画面パーツとなるHTMLにはHTMLタグは使わない。
    すべてcompositionタグから始め、compositionタグに利用するxmlnsのURLを記載する。
  • HTMLタグやBODYタグはテンプレート用HTMLにだけ使用する。
  • FORMタグについてはテンプレートのみに持たせる方が無難。
    画面パーツにも持たせられるが、合成時には注意が必要。

そんなわけで、前回掲載したHTMLは以下のようにするのが正しいので、
修正版を掲載する。

[修正後のHTMLはこちら](クリックで開く)

  • jsf/layout/common/layout.xml
<!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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
<ui:include src="/jsf/contents/common/htmlHeader.xhtml"/>
<ui:insert name="extHeader" />
<body>
    <h:form id="form1">
        <div id="wrapper">
            <div id="header">
                <div id="header_body">
                    <ui:include src="/jsf/contents/common/header.xhtml"/>
                </div>
            </div>
            <div id="contents">
                <ui:insert name="content" />
            </div>
            <div id="left-sidebar">
                <ui:include src="/jsf/contents/common/menu.xhtml"/>
            </div>
            <div id="footer">
                <ui:include src="/jsf/contents/common/footer.xhtml"/>
            </div>
        </div>
    </h:form>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <title><ui:insert name="title">Default title</ui:insert></title>
    <h:outputStylesheet library="css" name="default.css" />
  </h:head>
</ui:composition>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:h="http://java.sun.com/jsf/html">
  <h:outputLabel value="#{staticText['site-title']}" />
</ui:composition>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:h="http://java.sun.com/jsf/html">
   <h:outputLabel value="#{staticText['copyright']}" />
</ui:composition>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:h="http://java.sun.com/jsf/html">
    <div>艦艇図鑑</div>
    <div>艦艇登録</div>
    <div>艦艇編集</div>
    <div>艦艇削除</div>
</ui:composition>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"
      xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
      xmlns:x="http://java.sun.com/jsp/jstl/xml">
        <h2><h:outputLabel value="艦艇図鑑" /></h2>
        <h:dataTable var="item" value="#{warShipCat.allList}" styleClass="table" headerClass="headerrow" rowClasses="oddrow,evenrow">
            <h:column>
                <f:facet name="header">No.</f:facet>
                <h:outputText value="#{warShipCat.allList.rowIndex + 1}" />
            </h:column>
            <h:column>
                <f:facet name="header">艦種</f:facet>
                <h:outputText value="#{item.warshipType.name}" />
            </h:column>
            <h:column>
                <f:facet name="header">名前</f:facet>
                <h:outputText value="#{item.name}" />
            </h:column>
        </h:dataTable>
</ui:composition>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html"
        template="/jsf/layout/common/layout.xhtml">
    <ui:define name="title">#{staticText['warship.menu.lnkCatalog']}</ui:define>
    
    <ui:define name="content">
        <ui:include src="/jsf/contents/warship/content.xhtml"/>           
    </ui:define>
    
</ui:composition>

JavaEE 7をやってみよう。 Webデプロイメントアセンブリー

JPA編 その3の補足。

JPA編 その3で、
動的WebプロジェクトからEJBプロジェクトを参照できるようにするため、
動的Webプロジェクトの参照プロジェクトにEJBプロジェクトを加えていたが、
よくよくEclipseのマーカーを見ると警告が出ていることに気づいた。

f:id:nagamitsu1976:20151010150706p:plain

クラスパス・エントリー /SampleEjb は、エクスポートまたは公開されません。実行時に ClassNotFoundExceptions が発生する可能性があります。

なんのこっちゃ、とは思ったが、
よくよく考えてみれば、ワークスペース上でビルドが通る様にしているだけなので、
デプロイした場合、モジュールが欠落している可能性も考え得る。
その警告と読み取れた。

それで、設定すべき別の方法があるのでは無いかと、
エラーメッセージを手がかりに調べてみた。

……が、よくわからず、プロジェクトプロパティをもう一度見なおして、
それっぽいものもを見つけた。
それが、Webデプロイメントアセンプリーの項目。

f:id:nagamitsu1976:20151010151413p:plain

ここのManifest Entriesの追加ボタンを押すと、
追加対象にEJBプロジェクトが表示されたので、
試しに追加してみたら警告が消えた。

f:id:nagamitsu1976:20151010151627p:plain
f:id:nagamitsu1976:20151010151631p:plain

設定は正しいようなので、これはどういう事なのか改めて調べた。

この記事を読むと、Enterprizeアプリケーションプロジェクトでは、
グループ内のプロジェクトで依存関係が設定できるらしいことがわかった。

JPAプロジェクトはEnterpizeアプリケーションプロジェクトに加えるだけで参照可能になるので、
動的WebプロジェクトからEJBプロジェクトを参照可能とするために、
この操作が必要なのは多少解せないが、そういうものなのだろう。

といったところで、今回は終了。

JavaEE 7をやってみよう。 JSF テンプレートXML その2

その1のつづき。

前回、VIEWを作ったので、
今回は、DB検索を行うManaged Beanを作り、JSFで結果表示を行ってみる。
JSFのManaged BeanについてはCDIの回に少し書いた。
Managed Beanは画面アクションとBeanの処理を紐付けできるような、
いわゆるアクションクラス的な位置付けのクラス。
今回の要件で言えば、
画面表示時に艦艇テーブルから全件抽出できるような処理を持つJavaクラス、ということになる。

ま、JPA編のその8でRESTによるWebAPIを作ったので、
処理内容としてはそれと同じものを作ることになるんだけどね。

といったところで、ソース。

WarshipCatalogクラス

package app.manage;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.faces.model.ListDataModel;
import javax.inject.Named;

import model.Warship;
import ejbModule.WarshipServiceLocal;

@RequestScoped
@Named(value="warShipCat")
public class WarshipCatalog {
    @EJB
    private WarshipServiceLocal ejb;
    
    private ListDataModel<Warship> allList;
    
    @PostConstruct
    private void init() {
        List<Warship> warShipList = ejb.getAllList();
        allList = new ListDataModel<Warship>(warShipList);
    }

    public ListDataModel<Warship> getAllList() {
        return allList;
    }

    public void setAllList(ListDataModel<Warship> allList) {
        this.allList = allList;
    }
}

EJBの使い方はManaged Beanで使っても変わらないので、
難しいところは無いと思う。
@PostConstructアノテーションを付けたメソッドでDB参照を呼び出しているので、
このクラスのインスタンスが作られるたびにDB検索が行われメンバに格納される。
VIEWはそのメンバから値を読み出す、仕掛け的にはただそれだけ。

といったところで、完成。

動確、行ってみよう!

Wildflyを起動して、プロジェクトをデプロイ後、
下記のURLにアクセスするとDBに格納したデータが表示される。
http://localhost:8080/(Webプロジェクト名)/faces/jsf/view/warship.xhtml

f:id:nagamitsu1976:20151010130259p:plain

安直ではあるが、これでVIEWも完成。
登録、削除機能を作る下地もできたので、
以後はこれを流用して、JPAの調査を続けましょ。

といったところで、次回へ続く。

注) 生成したHTMLについては、JSF テンプレートXML その3にも言及があるので、
そちらも参照のこと。

注その2) PostConstruction内にDB参照を行っているが、この処理はあんまり良くない。

JavaEE 7をやってみよう。 JSF テンプレートXML その1

JavaEE JPA編から一時、横道にそれる。

JPAを使って登録、削除をやろうとした時、
どうしても入力が必要なので、
登録・削除用のVIEWを作ろうと思う。

どうせなら、JSFのテンプレート機能をを使おうと思って、
ちょっと調べてみた。

このへんの記事を読んで、わかったこと。

  • XMLネームスペースに"http://java.sun.com/jsf/facelets"を追加することで、
    テンプレート用(ってかFacelet用だけど)のタグが使える。
  • faceletsカスタムタグのinsertタグをテンプレートに定義しておくと、
    実態のVIEWからはめ込むhtmlを指定できる。

これだけで、一応テンプレートが作れそうなので、やってみた。
VIEWの形状をヘッダ、サイドメニュー、本文、フッタを持つ、よくある形式にしてみた。

構成

コンテンツは画面パーツ、レイアウトがひな形、
ビューがレイアウトに従い、パーツを組み合わせた画面と捉えるとわかりやすい。

WebContent
 + jsf
   + contents
   | + common
   | |  footer.xhtml
   | |  header.xhtml
   | |  htmlHeader.xhtml
   | |  menu.xhtml
   | |
   | + warship
   |   contents.xhtml
   |
   + layout
   | + common
   |   layout.xhtml
   |
   + view
      warship.xhtml
jsf/layout/common/layout.xml

画面の基礎となるレイアウト。
定義は単純で、画面構成のヘッダ、サイドメニュー、本文、フッタを定義している。
内、ヘッダ、サイドメニュー、フッタはincludeタグを使用してレイアウトで読み込むように定義して、
このレイアウトを使う限り、必ず付加されるようになっている。
加えて、metaタグなんかを含む共通のhead要素であるhtmlHeader.xhtmlも組み込んでいる。
唯一、本文だけはinsertタグを使ってレイアウトを使用するviewからhtmlを指定できる形になっている。

<!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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
<ui:include src="/jsf/contents/common/htmlHeader.xhtml"/>
<ui:insert name="extHeader" />
<body>
    <div id="wrapper">
        <div id="header">
            <div id="header_body">
                <ui:include src="/jsf/contents/common/header.xhtml"/>
            </div>
        </div>
        <div id="contents">
            <ui:insert name="content" />
        </div>
        <div id="left-sidebar">
            <ui:include src="/jsf/contents/common/menu.xhtml"/>
        </div>
        <div id="footer">
            <ui:include src="/jsf/contents/common/footer.xhtml"/>
        </div>
    </div>
</body>
</html>
jsf/contens/common/header.xhtml, htmlHeader.xhtml, footer.xhmtl, menu.xhtml

4ファイルともほぼスタブなのだが、特筆する箇所といえば、
htmlHeader.xhtmlに定義したHTMLタイトル。
タイトルタグを指定すると、ブラウザのタイトルバーにページタイトルが表示されるが、
あれを動的に設定できるようにしている。
動的に変えるコンテンツにはinsertタグを用いるのはこれまで通りだが、
タグの中にデフォルト文字列を書いておくことで、
設定時/未設定時の値を使い分けることができるってことらしい。

<!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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Cache-Control" content="no-cache" />
    <title><ui:insert name="title">Default title</ui:insert></title>
    <h:outputStylesheet library="css" name="default.css" />
  </h:head>
</html>

[残り3ファイルはこちら](クリックで開く)

<!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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
<h:body>
    <h:outputLabel value="#{staticText['site-title']}" />
</h:body>
</html>
<!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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
<h:body>
    <h:outputLabel value="#{staticText['copyright']}" />
</h:body>
</html>
<!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">
<h:body>
    <div>艦艇図鑑</div>
    <div>艦艇登録</div>
    <div>艦艇編集</div>
    <div>艦艇削除</div>
</h:body>
</html>

jsf/contents/warship/warship.xhtml

艦艇一覧を表示する画面パーツという位置付けのファイル。
以前、JSFの回に書いたテーブルタグによるリスト形式のhtmlと同じような内容となっている。
なんか、無駄にxmlnsをインクルードしているのはご愛嬌ってことで。。。
JPAの流れではJSF用のManaged Beanを作っていなけれど、
これに合わせて、後ほどManaged Beanを作ることになる。

<!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:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"
      xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
      xmlns:x="http://java.sun.com/jsp/jstl/xml">
<h:body>      
    <h:form id="form1">
        <h2><h:outputLabel value="艦艇図鑑" /></h2>
        <h:dataTable var="item" value="#{warShipCat.allList}" styleClass="table" headerClass="headerrow" rowClasses="oddrow,evenrow">
            <h:column>
                <f:facet name="header">No.</f:facet>
                <h:outputText value="#{warShipCat.allList.rowIndex + 1}" />
            </h:column>
            <h:column>
                <f:facet name="header">艦種</f:facet>
                <h:outputText value="#{item.warshipType.name}" />
            </h:column>
            <h:column>
                <f:facet name="header">名前</f:facet>
                <h:outputText value="#{item.name}" />
            </h:column>
        </h:dataTable>
    </h:form>
</h:body>
</html>
jsf/view/warship.xhtml

実際に呼び出す画面となるhtml。
注目すべきはcompositionタグ。
これを使うことでレイアウトを合成できる。
compositionタグの中にxmlnsの定義を書いているのが奇妙といえば奇妙だけれど、
そういうものなんだろうと、いうことにした。
テンプレート側でinsertタグを用いた箇所に値を設定するには、defineタグを用いる。
ここでは、HTMLタイトルと本文をdefineタグを使って値を設定している。

<!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">

  <ui:composition xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:h="http://java.sun.com/jsf/html"
      template="/jsf/layout/common/layout.xhtml">
      
    <ui:define name="title">艦艇図鑑</ui:define>
    
    <ui:define name="content"> 
      <ui:include src="/jsf/contents/warship/content.xhtml"/>           
    </ui:define>
    
  </ui:composition>
</html>

と、JSFのVIEWについてはこれまで。
あと、header.xhtmlやfooter.xhtmlに固定文字列を設定するためのリソースファイルや、
画面デザインのためのCSSを作成した。
これらをすべてWebプロジェクトに組み込めばViewの完成。
あとはManaged Beanを作るだけ。

といったところで、次回へ続く。

注) 生成したHTMLについては、JSF テンプレートXML その3にも言及があるので、
そちらも参照のこと。

[残りファイルはこちら](クリックで開く)

  • statictext.properties
site-title= 艦艇これくしょん
copyright=(C) ひこぽん(nagamitsu1976)
@CHARSET "UTF-8";
#wrapper {
  text-align: left;
  width: 804px;
  margin: 0 auto;
  padding: 0;
  border: 2px solid black;
}

#header {
  height: 100px;
  margin: 0;
  padding: 0;
  border-color: black;
  border-width: 0px 0px 2px 0px;
  border-style: solid;
  background-color: #e6e6fa;
}

#header_body {
  text-align: center;
  position: relative; 
  top: 40%; 
  right: 0; 
  bottom: 0; 
  left: 0;  
}

#contents {  
  width: 580px;
  float: right;
  margin: 0;
  padding: 10px 10px 10px 10px;
  border-color: black;
  border-width: 0px 0px 0px 2px;
  border-style: solid;
}

#left-sidebar {
  height: 100%;
  width: 180px;
  float: left;
  margin: 0;
  padding: 10px 10px 10px 10px;
  border: 0px solid black;
}

#footer {
  text-align: center;
  clear: both;
  margin: 0;
  padding: 0.5em 0;
  border-color: black;
  border-width: 2px 0px 0px 0px;
  border-style: solid;
  background-color: #e6e6fa;
}

.headerrow {
	background-color:#e6e6fa;
}
.oddrow {
    background-color:#ffffff;
}
.evenrow {
	background-color:#ffffcc;
}
.table {
	width: 300px;
	background-color: #b0c4de;
	border: 1px solid #boc4de;
}
  • faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
    version="2.2">
    <application>
        <locale-config>
            <default-locale>ja</default-locale>
        </locale-config>
        <resource-bundle>
            <base-name>resources.statictext</base-name>
            <var>staticText</var>
        </resource-bundle>
    </application>

</faces-config>

JavaEE 7をやってみよう。 JPA その8

その7のつづき。

今度はEJBを呼び出す処理を実装する。
Webアプリ側なので画面とか必要なのだが、
まずは、ちゃんとDB参照できるか手軽に確認するために、
RESTのWebAPIとして機能を作ってみる。

作るクラスは2つ。
RestApplicationクラスとEJBを呼び出すResourceクラスの2つ。
では、ソース。

  • RESTアプリケーションクラス
package app;


import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/rest")
public class RestApplication extends Application {

}
  • Warshipリソースクラス
package app.res;

import java.util.List;

import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import model.Warship;

import org.jboss.resteasy.annotations.providers.jaxb.Formatted;

import ejbModule.WarshipServiceLocal;

@RequestScoped
@Path("/warship")
public class WarshipResource {
    @EJB
    private WarshipServiceLocal ejb;
    
    @Path("/test")
    @GET
    @Produces("text/plain")
    public String getTest() {
        return "ALive!";
    }
    
    @Path("/all")
    @POST
    @Produces({"application/json;charset=UTF-8"})
    @Consumes({"application/json;charset=UTF-8"})
    @Formatted
    public List<Warship> getAll() {
        List<Warship> allList = ejb.getAllList();
        return allList;
    }
}

RESTアプリケーションのアノテーションなんかについては、以前のRESTの項を見てもらうとして*1
ここではWebアプリケーション→EJBの呼び出しについて補足する。
といっても前回作ったEJBメソッドを呼んでいるだけ。
メンバにEJBのローカルインターフェイスを定義して、目的の箇所で使用するだけOKなので簡単。
ここではgetAllメソッド内で、EJBからDB呼び出しを行っている。
また、EJBのローカルインターフェイスに@EJBアノテーションを付けておけば、
インスタンスは自動的にインジェクションされる。

といったところで、ようやっと、
Webプロジェクト → EJBJPA → DBの流れができたので、
動作確認をやってみよう!

デプロイ前に、Wildflyの状態を確認。
DBサーバが起動した状態でWildflyを起動して、
JDBCドライバがデプロイされているかを確認する。
手順についてはJDBCドライバをデプロイした回を参照のこと。

問題がなければ、Eclipseのサーバーのタブから、Wildflyを選択し、
コンテキストメニューから「追加および除去」を選ぶ。
ダイアログから今回作成したEnterprizeアプリケーションプロジェクトを追加して、完了を押す。
しばらく待つと、デプロイが始まってWildflyに配備される。
f:id:nagamitsu1976:20151008152254p:plain

デプロイ後、ブラウザからテストメソッドを叩いて正しくデプロイで着ているか確認してみる。
URLはhttp://localhost:8080/(Webプロジェクト名)/rest/warship/test
問題なければ、"ALive!"と文字列が表示できる。
f:id:nagamitsu1976:20151008153047p:plain

リソースクラスの配備にも問題が無いのがわかったところで、
JMeterから全件参照のメソッドを叩いてみる。
URLはhttp://localhost:8080/(Webプロジェクト名)/rest/warship/all
ちなみにWarshipResource.getAll()メソッドをPOSTで作ったので、
ブラウザからURLを直接叩いても値は取得できない。

あらかじめデータをDBに登録しておくと、こんな感じで抽出できる。
f:id:nagamitsu1976:20151008161038p:plain
f:id:nagamitsu1976:20151008161047p:plain

なんとかDB参照までこぎつけることが出来た。
あとは、追加、削除あたりをやってみないことには、
JPAの概要は得られないか。

といったところで、次回へ続く。