JSF 2.2ではf:eventのpreRenderViewではなくf:viewActionを使う?

JSF 2.2でf:viewActionというコンポーネントが追加されています。
基本はJSF 2.0で初期画面表示時に処理を動作させる時に使っていた
f:eventのpreRenderViewと同じなのですが微妙に違うところがあります。


まずは単純に呼び出された回数をカウントするバッキングビーンを作ります。

package sample;

import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class ViewAction {
    private int count = 0;
    public int getCount() {
        return count;
    }
    public void init() {
        count++;
    }
}


そして画面

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>ViewAction Sample</title>
    </h:head>
    <h:body>
        <f:metadata>
            <f:viewAction action="#{viewAction.init()}"/>
        </f:metadata>
        <div>Count: #{viewAction.count}</div>
        <h:form>
            <h:commandButton value="Click" />
        </h:form>
    </h:body>
</html>


f:viewActionのactionでバッキングビーンのinitメソッドを呼び出しています。
では実際に動かしてみます。


画面を表示するとinitメソッドが動作してカウントが1と表示されます。
f:id:den2sn:20130624213717p:plain


ボタンを押してもカウントは増えません。
f:eventのpreRenderViewでは画面表示の度に処理が実行されていましたが
f:viewActionでは初期表示時に1回しか動作しないようになっています。


再表示時に処理を動作させたい場合はonPostbackにtrueを指定します。

<f:viewAction action="#{viewAction.init()}" onPostback="true"/>


するとクリックを押す度にカウントが増えて行きます。
f:id:den2sn:20130624214117p:plain


また、f:viewActionでは処理を実行するフェーズを指定する事ができます。
指定出来るフェーズはフェーズ2から5のAPPLY_REQUEST_VALUES, PROCESS_VALIDATIONS,
UPDATE_MODEL_VALUES, INVOKE_APPLICATIONの4種類です。
たとえばAPPLY_REQUEST_VALUESフェーズで処理を実行させたい場合は

<f:viewAction action="#{viewAction.init()}"  phase="APPLY_REQUEST_VALUES"/>


と指定します。
f:eventのpreRenderViewはRenderer Responseフェーズで動作すると思いますが、
f:viewActionでは指定できないのでもしかすると使い分けが必要になってくるかもしれません。


f:viewActionはアクションというだけあってoutcome値を返すことも出来ます。
バッキングビーンで

public String init() {
    return "actionView";
}


とするとactionViewに遷移します。
f:viewParamと組み合わせることでパラメータによって画面分岐等も行うことができるようになっています。


最後におまけですが実は上記サンプルプログラムは現状のGlassFish4.0では動作させることができません。
実はネームスペースのバグがあって
http://xmlns.jcp.org/jsf/core
とすると動作しないようです。
2015/4/26 追記 GlassFish4.1で修正されました。


http://java.sun.com/jsf/core
を指定するとひとまず動作させる事ができます。

JSF 2.2 で待ちに待ったFileUploadを試す

JSF 2.2 でやっと導入されたFileUploadを試してみました。


まずサーバ側のBackingBeanを作成します。

package sample;

import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
import javax.servlet.http.Part;

@Named
@RequestScoped
public class UploadPage {

    private Part file;

    public Part getFile() {
        return file;
    }

    public void setFile(Part file) {
        this.file = file;
    }
}


JSFすごい。もはや処理すらありません。
fileのゲッターセッターを準備します。
ファイルはServlet3.0のファイルアップロードと同様にjavax.servlet.http.Partで受け取れます。


では送信するFecelets側の処理を書いてみましょう。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Upload Test</title>
    </h:head>
    <h:body>
        <h:form enctype="multipart/form-data">
            <h:inputFile id="file" value="#{uploadPage.file}" /><br />
            <h:commandButton value="Upload" /><br />
            <h:outputText id="fileName" value="#{uploadPage.file == null ? '' : uploadPage.file.submittedFileName}" />
        </h:form>
    </h:body>
</html>


ファイルアップロードなのでもちろんformにenctype="multipart/form-data"を指定します。
あとはh:inputFileを配置するだけです。
ファイルが存在している場合はServlet3.1で追加されたsubmittedFileNameを呼び出してファイル名を表示しています。


最後にこのままだと日本語のファイル名が文字化けしてしまうのでglassfish-web.xmlにdefault-charsetでUTF-8を指定しておきます。NetBeansで自動生成したものに1行追加。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
  <class-loader delegate="true"/>
  <jsp-config>
    <property name="keepgenerated" value="true">
      <description>Keep a copy of the generated servlet class' java code.</description>
    </property>
  </jsp-config>
  <parameter-encoding default-charset="UTF-8" />
</glassfish-web-app>


では実行してみましょうファイルを添付して
f:id:den2sn:20130618233444p:plain


Uploadボタンを押すと
f:id:den2sn:20130618233500p:plain


無事ファイルがアップロードされてファイル名が表示されました。


では今度は処理をAjaxでのファイルアップロードに修正してみましょう。

<h:form enctype="multipart/form-data">
    <h:inputFile id="file" value="#{uploadPage.file}"></h:inputFile><br />
    <h:commandButton value="Upload">
        <f:ajax execute="@this" render="fileName" />
    </h:commandButton><br />
    <h:outputText id="fileName" value="#{uploadPage.file == null ? '' : uploadPage.file.submittedFileName}" />
</h:form>


では同じように実行してみましょうファイルを添付して
f:id:den2sn:20130618233444p:plain


Uploadボタンを押すと
f:id:den2sn:20130618234615p:plain


こちらも無事ファイルがアップロードされてファイル名が表示されました。AjaxによりoutputTextの部分しか更新されていないため入力部分のファイルのパスが消えていません。


実は現状AjaxのファイルアップロードはIEではもう一度実行しようとするとエラーになってしまいます。また、ChromeFirefox等の別のブラウザでは見えてはいけないiFrameが表示されて処理が途中で終了してしまいます。


GlassFishJSF実装のMojarraのJIRAに障害報告されているようなのでいずれ修正されると思います。

Java EE 7のTransactionalアノテーションを試してみる

Java EE 7からの新機能 JTA 1.2で追加されたTransactionalアノテーションを試してみた。


まずmessageというフィールドをもった単純なEntityクラスを作成して

@Entity
public class Test implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String message;

    // GetterとSetterは長いので省略
}


次にデータを追加するクラスを作成するのだけれども今まで作成していたSessionBeanはこんな感じ。

@Stateless
public class TestFacade {
    @PersistenceContext(unitName = "TESTPU")
    private EntityManager em;

    public void test() throws Exception {
        Test t = new Test();
        t.setMessage("test");
        em.persist(t);
    }
}


でも今回はこれをTransactionalを付加したCDIのクラスに修正する。

@RequestScoped
public class TestFacade {
    @PersistenceContext(unitName = "TESTPU")
    private EntityManager em;

    @Transactional
    public void test() throws Exception {
        Test t = new Test();
        t.setMessage("test");
        em.persist(t);
    }
}


TestFacadeをインジェクトしてtestメソッドを実行するともちろんデータが増える
f:id:den2sn:20130617221833p:plain


ではデータ登録後にExceptionを発生させるように修正する

@Transactional
public void test() throws Exception {
    Test t = new Test();
    t.setMessage("test");
    em.persist(t);
    throw new Exception(); //追加
}


実行するとExceptionがスローされるがチェック例外なのでやっぱりデータは追加される。
f:id:den2sn:20130617221946p:plain


ではExceptionではなくてRuntimeExceptionを発生させる。

@Transactional
public void test() throws Exception {
    Test t = new Test();
    t.setMessage("test");
    em.persist(t);
    throw new RuntimeException(); //変更
}


実行すると今度は非チェック例外なのでjavax.transaction.TransactionalExceptionがスローされて追加処理はロールバックされる。
f:id:den2sn:20130617221946p:plain


ではTransactionalアノテーションのrollbackOnにExceptionクラスを指定したうえでExceptionを発生させてみる

@Transactional(rollbackOn = Exception.class)
public void test() throws Exception {
    Test t = new Test();
    t.setMessage("test");
    em.persist(t);
    throw new Exception();
}


実行するとチェック例外であってもデータはロールバックされて追加されない。エラーはもちろんTransactionalExceptionになる。
f:id:den2sn:20130617221946p:plain


ではExceptionを継承したClassNotFoundExceptionを発生させてみる

@Transactional(rollbackOn = Exception.class)
public void test() throws Exception {
    Test t = new Test();
    t.setMessage("test");
    em.persist(t);
    throw new ClassNotFoundException();
}


実行するとバグなのか仕様なのかわからないけれど処理はロールバックされずにコミットされた。継承クラスじゃ駄目なのかな。
(バグっぽい:https://java.net/jira/browse/GLASSFISH-20533)
2015/4/26 追記:GlassFish4.1ではロールバックするように修正されていました。

f:id:den2sn:20130617222243p:plain


基本的にはSessionBeanのトランザクション制御と大きな大差は無い感じ。今後はSessionBeanはあまり使わずにこっちがメインになるかもね。

Java EE 7(GlassFish V4)でJAX-RSのMVCテンプレートを動かす

GlassFish V4でJersyのMVCテンプレートを動かしてみました。
基本は下のブログで書いたGlassFish V3の時と大きく変わりません。


Javaを知らない世代が今からはじめるJava EE開発
http://den2sn.hatenablog.com/entry/2012/12/19/001146


V4で注意する点は2つ

Viewableのクラスパスが変わっている


Jerseyのパッケージが「com.sun」から「org.glassfish」に変わったこともあり以前のパッケージは

com.sun.jersey.api.view.Viewable


となっていましたが、GlassFish V4で使用しているJersy2.0では

org.glassfish.jersey.server.mvc.Viewable


に変わっています。


Mavenプロジェクトの場合

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-mvc</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
</dependency>


を追加すれば大丈夫でしょう。

ApplicationConfigクラスでJspMvcFeatureを追加する


GlassFish V4ではそのままではJSPのプロバイダが登録されていないのでApplicationConfigでJspMvcFeatureを追加します。

Class jspProvider = Class.forName("org.glassfish.jersey.server.mvc.jsp.JspMvcFeature");
resources.add(jspProvider);


ApplicationConfigの全ソースはこんな感じ。

package com.den2sn.mavenproject1;

import java.util.Set;
import javax.ws.rs.core.Application;

@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new java.util.HashSet<Class<?>>();
        try {
            Class jsonProvider = Class.forName("org.glassfish.jersey.jackson.JacksonFeature");
            resources.add(jsonProvider);
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(getClass().getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        // ここから
        try {
            Class jspProvider = Class.forName("org.glassfish.jersey.server.mvc.jsp.JspMvcFeature");
            resources.add(jspProvider);
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(getClass().getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        // ここまで
        addRestResourceClasses(resources);
        return resources;
    }

    private void addRestResourceClasses(Set<Class<?>> resources) {
        resources.add(com.den2sn.javaee7test.TestResource.class);
    }
}


これで快適なMVCライフを送る事が出来ます。

JavaからCacoo APIを簡単に呼び出せるCacoo4jを作った

Cacoo(https://cacoo.com/)のAPIJavaから簡単に呼び出すライブラリを作りました。

・Cacoo4J
https://github.com/den2sn/cacoo4j

クラスパスにzip内の「cacoo4j-1.0.0.jar」を通すと使えるようになると思います。
認証にOAuthを使用する場合はさらに「signpost-core-1.2.1.1.jar」が必要ですがAPI Keyで認証する場合は不要です。

ソースとか汚いですがなんとなく動くと思うので気にしないで下さい。

Mavenリポジトリにはめんどくさいのでまだ登録してません。
気が向いたらやるかもね。

JSFのFaceletsでxhtmlに直接アクセスさせない方法

JSFでFaceletsを作成すると/facesでアクセスする場合は良いのですが、直接xhtmlファイルにアクセスするとソースがそのまま表示されて微妙な感じですよね。xhtmlファイルに直接アクセスさせないようにするには基本JSPで行う対処法と変わりません。


ただし、JSPでもっともよく使われているWEB-INF配下にファイルを配置する方法が、faceletsの場合テンプレートファイルでは可能ですが普通のファイルでは使えないと思うのでその他2つの方法を試してみたいと思います。

web.xmlのsecurity-constraintを使用する方法

特にアプリケーションの認証でsecurity-constraintを使用していない場合、security-constraintでxhtmlにアクセス制限を付加する事で直接アクセスを防ぐ事ができます。ただし、web.xmlのURLパターンマッチングは簡単なマッチングしか記載出来ないためパスがfacesで始まるかどうかをハンドリングできません。そのためFacesServletのURLパターンマッチングを.jsf等に変えてあげる必要があります。


servlet-mappingを変更

    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>


security-constraintを設定

  <security-constraint>
    <display-name>XHTML Security</display-name>
    <web-resource-collection>
      <web-resource-name>Protected Area</web-resource-name>
      <url-pattern>*.xhtml</url-pattern>
    </web-resource-collection>
    <auth-constraint>
    </auth-constraint>
  </security-constraint>


このような設定を行うことで.jsfへのアクセスは許可し、.xthmlへのアクセスは拒否することができます。パターンマッチングを*.jsfに変える事でURLが変わってしまうのでその点は注意して使用する必要があります。

Filterを使う方法

ServletFilterを使用してxhtmlへのアクセスを制限します。ここでもurl-patternマッチングが貧弱なので*.xhtmlへのアクセスをフィルタで受けてそのパスが/facesで始まっているか確認します。


web.xml

    <filter>
        <filter-name>XHTMLSecurity Filter</filter-name>
        <filter-class>filter.XHTMLSecurityFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>XHTMLSecurity Filter</filter-name>
        <url-pattern>*.xhtml</url-pattern>
    </filter-mapping>


XHTMLSecurityFilter.java

package filter;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;

public class XHTMLSecurityFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String path = ((HttpServletRequest) request).getServletPath();
        if (path.startsWith("/faces")) {
            chain.doFilter(request, response);
        } else {
            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    @Override
    public void destroy() {
    }
    
}


これでxhtmlファイルが表示されてしまう問題が解消できます。

結論

web.xmlのURLパターンマッチングをどうにかしてほしい。

BootstrapやjQueryをWebJarsで簡単管理

f:id:den2sn:20080622164547j:plain:rightBootstrapやjQuery等のクライアントサイドライブラリを管理するのって意外とめんどくさいですよね。WebJarsはよく使用されるクライアントサイドライブラリをJarに固めてJVMベースのWebアプリケーションで簡単に扱えるようにしようというプロジェクトです。


WebJars
http://www.webjars.org/


クライアントサイドライブラリをJarにすることでMaven等のライブラリ管理ツールでインストールや依存関係を管理する事が出来るようになります。


利用方法は非常に簡単。Mavenの場合、まずWebJarsの必要なライブラリをpom.xmlに記載します。例えばBootstrapを使用したい場合は以下のような感じ。

<dependencies>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>bootstrap</artifactId>
        <version>2.3.1</version>
    </dependency>
</dependencies>


JSF2ではlibに配置されたjar内のMETA-INF/resources内のファイルに静的にアクセス可能なのであとはHTMLで参照すればOKです。たとえばJSFであればこんな感じで使用できます。

<h:outputStylesheet name="webjars/bootstrap/2.3.1/css/bootstrap.min.css" />
<h:outputScript name="webjars/bootstrap/2.3.1/js/bootstrap.min.js" />
<h:outputScript library="webjars/jquery/1.9.0" name="jquery.min.js" />


ここで試してみると気づくと思いますが、特に定義していなくてもbootstrapが依存しているjqueryのライブラリもちゃんと一緒にlibに配置されて使用可能になっています。


ドキュメントを見ると、Play 2やServlet3、Grails、Spring MVC等でも利用可能なようなので大抵のJVMベースのWebアプリケーションでは使用できるのではないでしょうか。


やや最新のライブラリの配置が遅かったりするようですが、わざわざ各サイトにいってjsファイル等をダウンロードしてくるよりはとても簡単にライブラリ管理が出来るようになります。