かゆいところへ

JSFで、Webの痒いところへ手が届くか検証しました。エラー遷移と、ページレイアウトテンプレートと、ファイルダウンロード。それで、JSFってホントに簡単なのか?という疑問が浮上。

JSFは簡単??

JSFの採用を決めたのはいいのですが、まだまとまった情報がないような気がしました。いろいろ探しても、2年前くらいの「はじめての○○」系の古い記事ばかり。「JSFで簡単に開発できる」とは良く聞くけど、簡単なことしか説明していないのでは??との疑惑を持ちはじめています。
こちらの記事はとても分かりやすくて参考になりました。

知りたかったことは、一般的にWebアプリに求められるであろう、単純な内容です。

  • レイアウト(デザイン)テンプレート
  • サーバエラー時の画面遷移
  • HTTPヘッダー制御(ファイルダウンロードやリダイレクト)

JSFの問題領域ではナイのかもしれないけど、どうサバくのがJSFらしいやりかたなのか。

ググってもイマイチだし、これといった決定版な書籍も見あたらず。結局、JavaEEチュートリアルと、MyFaces Tomahawkのソースを読むことになってしまいました。

  ・・というか、JSF本、山形に売ってない・・orz。
  通りすがりの詳しいみなさま、教えていただければ幸いです。

今のところ感じているJSFの印象は、
簡単なものを作るにはめっぽう簡単。多少こみいってくると、ごっつい仕様書との格闘が。。。少なくとも、カスタムコンポーネントの作り方は、異様に煩雑だ。でもさすがにStrutsに戻りたいとは思わない。
です。

ぐぅ~、これで「簡単だ」と言い切るから、Javaギークな人達はすごい。
・・・疲れた。

以下、メモです。

レイアウトテンプレート

Struts Tilesのようなレイアウトテンプレートの機能が欲しいわけですが、JSFにはそんなTagはナイ。だからといって、他のフレームワークをごちゃごちゃといれるのはイヤ。

まあでも、CSSでレイアウトするのが今っぽいのでしょうから、せめて、ヘッダー情報を一括で入れ替えられるようにしたいです。

ということで、JSP2.0の機能、jsp-config を使うことにしました。
以下のように設定をしてやると、各JSPのアタマとオシリにフラグメントを挿入してくれます。各JSPでは、<body>タグの中身だけを記述することになります。

 ■web.xmlの抜粋
 <jsp-config>
  <jsp-property-group>
   <url-pattern>*.jsp</url-pattern>
   <include-prelude>/jspf/header.jspf</include-prelude>
   <include-coda>/jspf/footer.jspf</include-coda>
  </jsp-property-group>
 </jsp-config>

 ■/jspf/header.jspfの例:pageディレクティブとHTML-HEADを定義。
 <%@page contentType="text/html"%>
 <%@page pageEncoding="UTF-8"%>
 <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
 <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
 <%@taglib prefix="my" uri="/WEB-INF/tlds/custom_validator"%>
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
 <html>
  <head>
   <meta http-equiv="Content-Type"
     content="text/html; charset=UTF-8">
   <link href="/stylesheets/site.css" media="all"
     rel="Stylesheet" type="text/css" />
  </head>
  <body>

 ■/jspf/footer.jspfの例:header.jspfのタグを閉じる
  </body>
 </html>

エラー制御

JSFを触りはじめると、まず悩むのが、MangedBeanがPOJOであることです。
一見キレイに思えるのですが、いざHTTPやSevletを意識しなければならない場面になると、唸ってしまいます。例えば、リダイレクトやファイルダウンロード、内部サーバエラーの捕獲など。

システムエラーの捕獲は、試してみればすぐにわかるのですが、ManagedBeanのメソッドからテキトーなExceptionをスローすると、コンテナがServletExceptionにラップしてくれるようでした。

こんな感じのフツーの対処で十分。

 ■ManagedBeanの抜粋:
 public String managedBeanMethod() throws AppException{
   if( true ) throw new AppException();
   return "from-outcome"
 }

 ■web.xmlの抜粋:
 <error-page>
   <exception-type>javax.servlet.ServletException</exception-type>
   <location>/error.jsp</location>
 </error-page>

 ■error.jspの抜粋:
 <%@ page isErrorPage="true" %>
 <html>
  <body>
   <h1>エラー!</h1>
   ${pageContext.errorData.throwable.cause}
  </body>
 </html>

業務エラーの遷移は、faces-config.xmlで設定。

 <navigation-rule>
   <navigation-case>
     <from-outcome>application_error</from-outcome>
     <to-view-id>/application_error.jsp</to-view-id>
     <redirect/> <!--←これがナイ場合は、forwardになる-->
   </navigation-case>
 </navigation-rule>

強制的にnavigation-ruleを操作することもできます。もし、何かしらのエラーハンドリングフレームワークを作るのであれば、この方法も悪くはないのかもしれません。

 //FacesContextを取得
 FacesContext context = FacesContext.getCurrentInstance();

 //アプリケーションコンテキストからNavigationHandlerを取得
 ApplicationFactory factory
  = (ApplicationFactory)FactoryFinder
   .getFactory(FactoryFinder.APPLICATION_FACTORY);
 NavigationHandler navi
  = factory.getApplication().getNavigationHandler();

 //強制的に遷移
 navi.handleNavigation(context, fromAction ,"application_error");

ファイルダウンロード

HTTPヘッダーを弄らなければならない場合、結局は、HttpServletResponseオブジェクトが必要になります。

このあたりのコンテナを意識したコードは、カスタムコンポーネントに隠蔽してやるのがJSF流なのでしょうけど、ファイルダウンロード程度のちょっとした機能のためにたくさんのカスタムクラスを作るのはイヤです。作り方も煩雑だし・・。

だからといって、安易にServletで作ってしまうと、フレームワークの意味がないような気がしますし、web.xmlに業務機能が入り乱れることにもなります。なので、あくまでもJSFの枠組みで実現する方法がほしい。

とりあえず動くものは、以下のように作ることはできました。

 ■JSPの抜粋:
 <h:form id="downloadForm">
   <h:commandLink id="downloadLink"
     actionListener="#{httpAction.download}">
     <h:outputText value="ダウンロード"/>
   </h:commandLink>
 </h:form>

 ■ManagedBean:
 public class HttpActionHandler {

  public void download(ActionEvent event) throws IOException{
   FacesContext facesContext = FacesContext.getCurrentInstance();
   ExternalContext exContext = facesContext.getExternalContext();
   HttpServletResponse response
     = (HttpServletResponse)exContext.getResponse();

   response.setContentType("text/plain");
   response.setHeader("Content-disposition"
      , "attachment; filename=\"test.txt\"");
   PrintWriter writer = response.getWriter();
   writer.print("test test test");
   writer.close();

   facesContext.responseComplete();//これ必要?
  }
 }

ExternalContextがミソ。
ここからRequest/Responseオブジェクトを取り出して、あとはServletコードを自由に書くことができるようです。RemoteUserやUserInRoleを取得したいときも、必要になるでしょう。

このManagedBeanは、POJOのようでPOJOにあらず。でも、それっぽい名前にして、Servletコードを集約すれば、許されるような気もしますw
先にも言及したように、こういうServlet的コードはカスタムRenderなどに隠蔽するのが、JSF流なのだと思います。


コメント
くりちゃん
2006/12/28
通りすがりのものですw

>レイアウトテンプレート
そのままStruts Tilesを使ってみてはいかがでしょう。
あとはご存知かもしれませんが非JSP言語のFaceletsというテンプレート言語がありますよ。結構よさげなので後々勉強しようかなと思ってます。

>ファイルアップロード
試してないのですが、下記のURLにMyFacesを使ったファイルアップロードの記事がありました。英語ゆえによく読んでないので、的外れだったらごめんなさい。
ttp://www.onjava.com/pub/a/onjava/2005/07/13/jsfupload.html?page=3
くりちゃん
2006/12/28
あー失礼、ファイル”ダウンロード”か(;・∀・)
武田ソフト
2006/12/28
くりちゃん、アドバイスありがとうございます。
他のフレームワーク導入も検討したいとは思いますが、今はより"標準"なやりかたが知りたいと思っています。RailsはRailsだけで簡単に開発できますが、JavaEEはJavaEEだけじゃだめなのか!?・・と、心底思うんですよ~。

コメントしてください

closed.