レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

ICEFaces AND Spring JavaEE翻訳

TSSより、JSFコンポーネントライブラリである「ICEFace」の紹介記事です。
Springとの組み合わせをやっています。


February 2008 2008年2月

ICEFaces is a JSF component library that adds a unique approach to AJAX: it renders a DOM (Document Object Model) on the serverside, and delivers changes to that DOM to a client via AJAX push. What this means is that data can be updated from one client, and the updates can be reflected almost immediately - with no client intervention - on other clients.

ICEFaseは、AJaxに対するユニークな特徴を持ったJSFコンポーネントライブラリです。ICEFaceではドキュメントオブジェクトモデルの描画をサーバーサイドで行います。そして、Ajaxを経由してDOMの変更をクライアントに配信します。この機能によって、データを1つのクライアントによって更新でき、またクライアントに干渉せずに、他のクライアントに対してほとんど即時に変更を反映することができるのです。


The ICEFaces component suite is fairly complete, including updates to the “normal” JSF components to use AJAX and a few other capabilities. For example, the inputText component, which renders <input type=”text” />, is now aware of user roles, and can participate in partial submits (so that the server gets data as it's entered, rather than waiting until a full page submit is executed.)

ICEFaceコンポーネントスイートは、ほとんど完全なものです。Ajaxとその他のいくつかの能力を使うために標準のJSPコンポーネントの更新を含みます。たとえば、<input type=”text” />を描画するinputTextコンポーネントは、ユーザーのロールを知っています。また、部分的なサブミットに参加することができます。(サーバーは、ページ全体がサブミットされるまで待つというよりも、むしろinputTextコンポーネント自身の入力データを受け取るということです。)

The component suite also adds styling, a menu bar, a connection status widget, effects (I.e., highlights, pulses, fades), a progress bar, a file upload widget, charts, and a capable set of panels.

コンポーネントスイートを使うことで新しいスタイルが追加されます。メニューバーや接続状態ウィジェット、エフェクト(たとえばハイライト、パルス、フェイド)プログレスバー、ファイルアップロードウィジェット、チャート、そして有能なパネル集などです。

However, all this comes at a price: ICEFaces works with JSF 1.2, but its Java EE support is not complete. This tutorial shows how to deploy ICEFaces in a Java EE container, and how to retain the ease of development and deployment that EJB 3 gives you - even without EJB3.

しかしながら、これらのすべての機能は、相当な代償を払って実現しています。ICEFaceは、JSF1.2で動作しますが、JavaEEのサポートは完全ではありません。このチュートリアルは、JavaEEコンテナにICEFaceをデプロイする方法を紹介します。そして、いかにEoDを保ちながら、(EJB3を使っていなかったとしても)EJB3へのデプロイを行うかを紹介します。

The problem is that ICEFaces 1.6 and 1.7 still require some older mechanisms to work. Therefore, a web application that uses ICEFaces needs to use the Servlet 2.4 application descriptor, rather than Servlet 2.5.

問題となるのは、ICEFaceの1.6と1.7が動くためにいくつかの古いメカニズムを必要としていることです。それゆえに、ICEFaceを使うWebアプリケーションは、Servlet2.5ではなくServlet2.4のアプリケーションディスクリプター(web.xml)を利用する必要があるのです。

Relying on Servlet 2.4 makes no difference whatsoever, until you realise that - according to the spec, and as implemented in the RI for Java EE - web applications that use Servlet 2.4 do not get the benefit of injected resources. This means that accessing EJBs has to be done the “old way” - if at all.

Servlet2.4に依存することで異なることはまったくありません。(仕様に従っていれば、またJavaEEのRIで実装された)Servlet2.4を使ったWebアプリケーションは、リソースのインジェクトからメリットを得られません。これはEJBに接続するとしても「古いやりかた」を取る必要があるということです。

For example, a managed bean in a Servlet 2.5 application might refer to a Stateless Session EJB with code similar to the following:

たとえば、Servlet2.5のマネージドビーンアプリケーションは、ステートレスセッションEJBを参照するときに、以下のような短いコードで済みます。

MyManagedBean.java:

import com.tss.ejb.SLSBLocal;

public class MyManagedBean {
 @EJB
 SLSBLocal slsblocal;
 public String getValueFromEJB() {
  return slsblocal.getValue();
 }
}

In Servlet 2.4, however, the annotation is ignored. This can be upsetting, until one finds the workarounds - namely, Spring, which manages to provide us everything we normally want from a local EJB without an EJB being involved at all.

Servlet 2.4では、しかしながらアノテーションが無視されます。このことは、Springと呼ばれる1つの回避方法を見つけるまでのあいだ、動揺をまねきました。
(通常、全くEJBに関わることなく、ローカルEJBでの欲しい機能を提供してくれます)。

First, let's create a web.xml for an ICEFaces application. Create a web application in your favorite environment, specifying (or supplying) JSF 1.2. Then, add the ICEFaces requirements to web.xml, which means your web.xml should look like the following:

では、ICEFaceアプリケーションのためのweb.xmlを作って行きましょう。Webアプリケーションを作るのはJSF1.2の仕様に準拠している(または満たしている)ものであれば、自分の好きな環境でかまいません。それから、ICEFaceが必要とするweb.xmlとするために自分のweb.xmlに以下のような記述を追加しましょう。

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>Persistent Faces Servlet</servlet-name>
        <servlet-class>com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>Blocking Servlet</servlet-name>
        <servlet-class>
  com.icesoft.faces.webapp.xmlhttp.BlockingServlet
 </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

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

    <servlet-mapping>
        <servlet-name>Persistent Faces Servlet</servlet-name>
        <url-pattern>/xmlhttp/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>Blocking Servlet</servlet-name>
        <url-pattern>/block/*</url-pattern>
    </servlet-mapping>

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

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

    <listener>
        <listener-class>
  com.icesoft.faces.util.event.servlet.ContextEventRepeater
 </listener-class>
    </listener>
</web-app>

Now we need to do some work to create a managed bean that updates the DOM. This example uses a clock (using java.util.Date) and a “refresh count,” meaning the total number of times a link has been selected. Most of this can be found in the ICEFaces Developer Guide, but here's the source for faces-config.xml and TimeBean.java:

さて、DOMを更新するマネージドBeanを作るためにいくつかの作業が必要です。このサンプルは、時計(java.util.Date)を使い、カウントを更新します。これはリンクが選択された時間数値のトータルを意味します。このほとんどはICEFaceのデベロッパーガイドで見つけることができます。しかしここではfaces-config.xmlのソースとTimeBean.javaのソースを紹介します。

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_1_2.xsd"
              version="1.2">

    <managed-bean>
        <managed-bean-name>renderManager</managed-bean-name>
        <managed-bean-class>com.icesoft.faces.async.render.RenderManager</managed-bean-class>
        <managed-bean-scope>application</managed-bean-scope>
    </managed-bean>

    <managed-bean>
        <managed-bean-name>timebean</managed-bean-name>
        <managed-bean-class>com.tss.beans.TimeBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>renderManager</property-name>
            <value>#{renderManager}</value>
        </managed-property>
    </managed-bean>

</faces-config>

TimeBean.java:

package com.tss.beans;

import com.icesoft.faces.async.render.IntervalRenderer;
import com.icesoft.faces.async.render.RenderManager;
import com.icesoft.faces.async.render.Renderable;
import com.icesoft.faces.webapp.xmlhttp.PersistentFacesState;
import com.icesoft.faces.webapp.xmlhttp.RenderingException;

import java.util.Date;

public class TimeBean implements Renderable {
    static int refreshCount = 0;
    int interval = 1000;
    PersistentFacesState state;
    IntervalRenderer clock;

    public TimeBean() {
        init();
    }

    private void init() {
        state = PersistentFacesState.getInstance();
    }

    public int getRefreshCount() {
        return refreshCount;
    }

    public void setRefreshCount(int refreshCount) {
        this.refreshCount = refreshCount;
    }

    public Date getNow() {
        return new Date();
    }

    public String refresh() {
        refreshCount++;
        return null;
    }

    public void setRenderManager(RenderManager renderManager) {
        clock = renderManager.getIntervalRenderer("clock");
        clock.setInterval(interval);
        clock.add(this);
        clock.requestRender();
    }

    public PersistentFacesState getState() {
        return state;
    }

    public void renderingException(RenderingException renderingException) {
        if (clock != null) {
            clock.remove(this);
            clock = null;
        }
    }
}

Lastly, we create an example.jsp, invoked as “example.iface”, that looks like this:

終わりに、example.ifaceを起動するexample.jspを作ります。以下のようにします。

example.jsp:

<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="ice" uri="http://www.icesoft.com/icefaces" %>
<%@ taglib prefix="comp" uri="http://www.icesoft.com/icefaces/component" %>
<f:view>
    <html>
    <head>
        <title>ICEFaces Example</title>
    </head>
    <body>
    <h:form>
        <comp:outputConnectionStatus/><br/>
        Time: <comp:outputText value="#{timebean.now}"/><br/>
        Refresh Count: <comp:outputText value="#{timebean.refreshCount}"/><br/>
        <comp:commandLink value="Refresh" action="#{timebean.refresh}"/>
    </h:form>
    </body>
    </html>
</f:view>

Now, if this application is deployed into an application server, two client sessions (say, one in IE, and the other in Firefox) should be able to hit the page, and both will display a ticking clock - and if one selects the “Refresh” link, the counter will update on both browser windows.

では、アプリケーションがアプリケーションサーバーにデプロイされ、2つのクライアントセッション(例えば1つはIEから、もう1つはFirefox)がページにアクセスしたとします。またこの両方は、時計を進めます。そして、1つが"Refresh"のリンクを謳歌したら、カウンターが両方のブラウザで更新されます。

This is being done by sending DOM updates from the server, using AJAX Push. As the DOM updates are very small, the bandwidth consumed is negligible - but remember that bandwidth isn't entirely free, so to be responsible, you still have to measure the network traffic to make sure that you're not exceeding your limits.

これは、DOMの更新をサーバーからAJaxのプッシュによって送信した結果です。DOMの更新はとても小さくすみます。ネットワーク帯域の占有は極わずかです。しかし、ネットワーク帯域は完全に自由ではないことは覚えておいてください。責任のために、依然としてネットワークトラフィックの量を過度な制限をすることなしに確かめる必要があります。

The trouble with EJBs is related directly to the use of Servlet 2.4. This revision of the specification relies on EJB2, not EJB3, and using the EJB3 syntax and constructs is problematic at best. Since EJB2 is the “bad old way,” requiring remote and home interfaces, it's probably easier to drink the Spring Kool-aid - which gives you almost all of the advantages with only a little more configuration, thanks to Spring 2.5's configuration changes.

EJBに関する問題は、Servlet2.4を使うことに直接的に関連しています。この仕様のリビジョンでは、EJB3ではなくEJB2に依存しています。そして、EJB3シンタックスと構造を使うことは、せいぜい疑わしい程度です。EJB2からの"古い悪い方法"は、リモート&ホームインターフェイスを必要とする事です。この問題は、Springのカッコイイやり方にすれば、より容易になります。それはホンの小さな設定を行うことでほとんどのアドバンテージが得られます。Spring2.5の設定ファイルの変更に感謝します。

So we aren't defining an EJB so much as a bean, even though we're doing everything that an EJB would (or could) do. In this example, we're going to ignore transactions, because their inclusion doesn't change anything about the application or deployment.

そんなにEJBを定義するよりもBeanを定義しています、しかしながらEJBがやること(もしくはできること)すべてをやっています。このサンプルではトランザクションを無視するようにします、なぜならこのサンプルではアプリケーションまたはデプロイに関して何も変更しないからです。
Our interface and implementation look like the following:

インターフェイスと実装は以下のようになります。

Hello.java:

package com.tss.beans;

public interface Hello {
    String sayHello(String name);
}

HelloImpl.java:

package com.tss.beans;

import java.util.Date;

public class HelloImpl implements Hello {
    public String sayHello(String name) {
        return "Hello, "+name+" ("+new Date()+")";
    }
}

(You can probably see for yourself why transactions aren't important for this bean.)

(おそらく、このBeanではなぜトランザクションが重要ではないのかを見ることができます。)

Now it becomes important to provide Spring configuration to the web.xml, in the form of a context-param and two listeners. Here is what we add to web.xml:

さて、フォームのコンテキストパラメータと2つのリスナーにおいて、web.xmlに記述したSpring設定を提供するためのが重要になってきます。web.xml追記する内容は以下です。

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext*.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>

We also need a new faces-config.xml, to allow Spring as a name resolver. Our faces-config.xml should now look like this:

新しいfaces-config.xmlというファイルSpringのネームリゾルバとしても必要になります。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_1_2.xsd"
              version="1.2">
    <application>
        <variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
    </application>
</faces-config>

Now some of the fun stuff comes from Spring. First, let's look at the applicationContext.xml file, which belongs in WEB-INF:

さて、Springでちょっと面白いことがおこります。では、「WEB-INF」ディレクトリにあるapplicationContext.xmlファイルを見てみましょう。

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    <context:annotation-config />
    <bean id="renderManager" class="com.icesoft.faces.async.render.RenderManager" scope="singleton" />
    <bean id="timebean" class="com.tss.beans.TimeBean" lazy-init="true" scope="session">
        <property name="renderManager" ref="renderManager"/>
    </bean>
</beans>

It's very important that every bean that uses the renderManager reference have lazy-init=”true”. The reason is because the PersistentFacesState isn't necessarily ready when the bean is loaded, which will allow one render, but won't provide any live updates.

とても重要なことですが、renderManagerの参照を使うすべてのBeanは「lazy-init="true"」の記述をしていなければいけません。その理由は、「PersistentFacesState」は、描画を許可されたBeanがロードされたときには準備されている必要はないからです。しかし随時的な更新は提供されません。

Once these changes have been applied, you can reinvoke the example.iface page in two browsers, and watch them both update roughly every second, and the refresh count matches between them, as it was in the non-Spring version.

One thing worth noting, however, is that with Spring 2.5, our configuration can get simpler. It'll require some changes to TimeBean.java, but they're well worth it.

しかしながら、1つ注意する必要がある点は、Spring2.5と共に設定ファイルを、より簡単することができるということです。そのためにはTimeBean.javaへのいくつかの変更が必要ですが、その価値は十分あります。

We need to change the setRenderManager() method. Let's rename it to “initClock()” and remove the parameters, since we won't need it. We also want to add the @PostConstruct annotation, so that this method will be called immediately after the bean is instantiated.

setRenderManager()メソッドを変更する必要があります。では、"initClock()"に改名して、必要ないのでパラメタを取り除きましょう。また、Beanがインスタンス化される直後にこのメソッドが呼ばれるように@PostConstructアノテーションを加えたいと思います。

    @PostConstruct
    public void initClock() {
        System.out.println(renderManager);
        clock = renderManager.getIntervalRenderer("clock");
        clock.setInterval(interval);
        clock.add(this);
        clock.requestRender();
    }

Of course, now we're missing the “renderManager” reference. Let's add that, with the @Autowired annotation:

もちろん、いまでは"renderManager"の参照を見失っています。さあ、@Autowiredアノテーションを追加しましょう。

    @Autowired
    RenderManager renderManager;

Once again, let's look at the meat of our applicationContext.xml:

もういちど、applicationContext.xmlの意味を見てみましょう。

    <bean id="renderManager" class="com.icesoft.faces.async.render.RenderManager" scope="singleton" />
    <bean id="timebean" class="com.tss.beans.TimeBean" lazy-init="true" scope="session" />

There's nothing explicitly tying the two beans together: Spring is detecting an autowired property in TimeBean, then looking for a unique instance of that class in its configuration, and injecting it. This makes configuration a snap.

ここには、2つのBeanをお互いに紐付ける明確な記述はまったくありません。SpringはTimeBeanのプロパティーに自動的に紐付けする宣言をしています。そこで、設定ファイルに記述されたクラスの一意のインスタンスを探します。そして、それをインジェクトします。これは設定を簡単にします。

Now you've seen how to use ICEFaces with JSF 1.2, which is fairly normal and documented, and you've also seen how to use Spring 2.5 along with ICEFaces, using ICEFaces' best value proposition, the Direct-to-DOM rendering from server to client.

ICEFaceを標準的でドキュメントも豊富なJSF1.2と共に使う方法を見てきました。また、Spring2.5をICEFaceと共に使う方法もみてきました。ICEFaceを使うことは、サーバーで描画されたDOMをダイレクトにクライアントにつたえるには最高に価値のある計画です。