GAE/JにStruts1.3のサンプルを乗せてみた

Springframeworkにも、(古くから)SpringMVCというモジュールがあるが、いまだにApache Strutsを使うことが多い。10年くらいJavaEEのアプリをやっている方なら、Struts1.0の頃の衝撃を覚えているに違いない(昔話が多いような。。。)。
ActionForm、ActionErrorsとか、「なんて便利なんだろう」と思った。当時は、フレームワークブームで、Strutsの他にも、TurbineTapestry、WebWorkなどといった「MVC2モデル前提でアプリ開発をするフレームワーク」がたくさんあった(Turbineとか、Tapestryはページ・レイアウトのやり方なんかで、「毛色の変わった」フレームワークだった)。v1.1が全盛期で、今でもStruts1.1ベースで実稼動しているシステムが多くあるに違いない。

その後、オリジナル開発者のC.R.McClanahan氏がJSFに流れてしまい、v1.3の初期は旧来のStrutsJSFのプロジェクトが混在していた。
現在は、Struts1.3系が旧来のStrutsStruts2.0系が旧来のWebWork2のプロジェクトになっている(JSFのプロジェクトはShale?という名前で、Apacheのトップにあったが見当たらない。どこに行ってしまったんだろう)。

前置きが長くなってしまったが、Struts1.3.10に付属するサンプルを、Google Apps Engine/Java(GAE/J)に乗せてみた。
そういえば、DWRやSpringframework(DI、AOP)の実験はしたが、JSPやTaglibの実験をしていないことに(今更ながら)気が付いたので、これらが動くかどうかも実験する。

以下の作業は、GAE/JのプラグインがインストールされたEclipseGanymede)で行った。

Apache Strutsのダウンロード

本家ページより、struts-1.3.10-all.zipをダウンロードして解凍する。
作業を簡単にするために、struts-examples-1.3.10.warを、ローカル環境にインストールしたTomcat6.0にデプロイした。

プロジェクトの作成と、ファイルのコピー

ワークスペースに新規のWeb Applicationプロジェクトを作成する。

Tomcatに展開後のディレクトリー構成は以下のようになっている。


GAE/Jでは、コンテキストルートがwar配下になるので、このことに注意して、以下のようにTomcatからファイルをコピーする。

tomcat GAE/Jプロジェクト
index.html war/index.html
welcome.jsp war/welcome.jsp
dispatch/ war/dispatch/
exercise/ war/exercise/
upload/ war/upload/
validator/ war/validator/
WEB-INF/struts-config.xml war/WEB-INF/struts-config.xml
WEB-INF/dispatch/ war/WEB-INF/dispatch/
WEB-INF/exercise/ war/WEB-INF/exercise/
WEB-INF/upload/ war/WEB-INF/upload/
WEB-INF/validator/ war/WEB-INF/validator/
WEB-INF/lib/* war/WEB-INF/lib/


また、ソースコードが「WEB-INF/validator/src/java」下にあるので、それをプロジェクトのソースフォルダーにコピーする。

web.xml

struts-examples-1.3.10.warに付随する/WEB-INF/web.xmlから、以下の部分をwar/WEB-INF/web.xmlにコピーする。(注記:Tomcatstruts-examplesを動かすと、日本語が文字化けするのは、このcharsetの宣言がラテン1に設定されているため。utf-8に変更すれば、文字化けしなくなる。)

    <display-name>Struts Examples Application</display-name>
    <!-- Standard Action Servlet Configuration (with debugging) -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>

        <!-- Default -->
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>

        <!-- Exercise module -->
        <init-param>
            <param-name>config/exercise</param-name>
            <param-value>/WEB-INF/exercise/struts-config.xml</param-value>
        </init-param>

        <!-- File Upload module -->
        <init-param>
            <param-name>config/upload</param-name>
            <param-value>/WEB-INF/upload/struts-config.xml</param-value>
        </init-param>

        <!-- Validator module -->
        <init-param>
            <param-name>config/validator</param-name>
            <param-value>/WEB-INF/validator/struts-config.xml,
                /WEB-INF/validator/struts-config-bundles.xml,
                /WEB-INF/validator/struts-config-i18nVariables.xml,
                /WEB-INF/validator/struts-config-type.xml,
                /WEB-INF/validator/struts-config-validwhen.xml
            </param-value>
        </init-param>

        <!-- Dispatch Action module -->
        <init-param>
            <param-name>config/dispatch</param-name>
            <param-value>/WEB-INF/dispatch/struts-config.xml</param-value>
        </init-param>

        <init-param>
            <param-name>debug</param-name>
            <param-value>2</param-value>
        </init-param>
        <init-param>
            <param-name>detail</param-name>
            <param-value>2</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <!-- Standard Action Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!-- The Usual Welcome File List -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>index.html</welcome-file>
        <welcome-file>upload.jsp</welcome-file>
    </welcome-file-list>


struts-examplesでは、ServletAPI 2.3で宣言がされている(GAE/Jプロジェクトに作成されるweb.xmlは2.5)が、内を丸ごと入れ替えてしまってよい。結果、war/WEB-INF/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"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

    <display-name>Struts Examples Application</display-name>
    <!-- Standard Action Servlet Configuration (with debugging) -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>

        <!-- Default -->
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>

        <!-- Exercise module -->
        <init-param>
            <param-name>config/exercise</param-name>
            <param-value>/WEB-INF/exercise/struts-config.xml</param-value>
        </init-param>

        <!-- File Upload module -->
        <init-param>
            <param-name>config/upload</param-name>
            <param-value>/WEB-INF/upload/struts-config.xml</param-value>
        </init-param>

        <!-- Validator module -->
        <init-param>
            <param-name>config/validator</param-name>
            <param-value>/WEB-INF/validator/struts-config.xml,
                /WEB-INF/validator/struts-config-bundles.xml,
                /WEB-INF/validator/struts-config-i18nVariables.xml,
                /WEB-INF/validator/struts-config-type.xml,
                /WEB-INF/validator/struts-config-validwhen.xml
            </param-value>
        </init-param>

        <!-- Dispatch Action module -->
        <init-param>
            <param-name>config/dispatch</param-name>
            <param-value>/WEB-INF/dispatch/struts-config.xml</param-value>
        </init-param>

        <init-param>
            <param-name>debug</param-name>
            <param-value>2</param-value>
        </init-param>
        <init-param>
            <param-name>detail</param-name>
            <param-value>2</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <!-- Standard Action Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!-- The Usual Welcome File List -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>index.html</welcome-file>
        <welcome-file>upload.jsp</welcome-file>
    </welcome-file-list>
</web-app>

appengine-web.xmlの変更

struts-examplesではセッションを使うので、war/WEB-INF/appengine-web.xmlにセッションを有効にする設定をする。また、ロギングにLog4Jを使うことにしたので、それも一緒に設定する。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
	<application></application>
	<version>1</version>
	
	<system-properties>
		<property name="org.apache.commons.logging.Log" value="org.apache.commons.logging.impl.Log4JLogger"/>
	</system-properties>

	<sessions-enabled>true</sessions-enabled>
	
</appengine-web-app>

log4j.properties

ソースフォルダー下にあるlog4j.propertiesを変更する。サンプルをDebugモードで動かすと、org.apache.strutsパッケージ、org.apache.commonsパッケージからたくさんログが吐き出されるので、アペンダーを追加してそれを抑止する。

# A default log4j configuration for log4j users.
#
# To use this configuration, deploy it into your application's WEB-INF/classes
# directory.  You are also encouraged to edit it as you like.

# Configure the console as our one appender
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n

# tighten logging on the DataNucleus Categories
log4j.category.DataNucleus.JDO=WARN, A1
log4j.category.DataNucleus.Persistence=WARN, A1
log4j.category.DataNucleus.Cache=WARN, A1
log4j.category.DataNucleus.MetaData=WARN, A1
log4j.category.DataNucleus.General=WARN, A1
log4j.category.DataNucleus.Utility=WARN, A1
log4j.category.DataNucleus.Transaction=WARN, A1
log4j.category.DataNucleus.Datastore=WARN, A1
log4j.category.DataNucleus.ClassLoading=WARN, A1
log4j.category.DataNucleus.Plugin=WARN, A1
log4j.category.DataNucleus.ValueGeneration=WARN, A1
log4j.category.DataNucleus.Enhancer=WARN, A1
log4j.category.DataNucleus.SchemaTool=WARN, A1

log4j.rootLogger=DEBUG,A2
log4j.appender.A2=org.apache.log4j.ConsoleAppender
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
log4j.logger.org.apache.struts=WARN,A2
log4j.logger.org.apache.commons=WARN,A2

コンパイル

war/WEB-INF/libに追加したjarファイルをビルドパスに追加し、プロジェクトをリフレッシュすると、コンパイルがかかる。
struts-examplesでは、org.apache.struts.webapp.upload.UploadAction.javaコンパイルエラーとなった。エラーとなったのは以下の部分。

          OutputStream bos = new FileOutputStream(theForm.getFilePath());

GAE/Jのランタイム環境でFileOutputStreamがサポートされていないのが原因。コードを見ると、コンテキストパス配下のパスに(Uploadされた)ファイルを書き出す処理となっている。GAEの環境は、「ファイルシステムとしては利用できない」ので、こういった処理は「データストア(Big Table: Blobの保管が可能)に変更する必要があるのだろう(今回は、挙動の確認をするだけなので、手を入れなかった)。

テストとデプロイ

ローカルサーバーにデプロイしてみたところ、strutsタグののサンプルでエラーが出た(Tomcat6では正常)。

JSPのサンプルは初めてだったのだが、思わぬところでエラーが発生した。Eclipseからデプロイすると、以下のエラーが出て、デプロイできない。コンソールを見ていると、デプロイ前にJSPコンパイルしている際に発生している模様。

java.lang.IllegalStateException: cannot find javac executable based on java.home, tried "C:\Program Files\Java\jre6\bin\javac.exe" and "C:\Program Files\Java\bin\javac.exe"
Unable to upload app: cannot find javac executable based on java.home, tried "C:\Program Files\Java\jre6\bin\javac.exe" and "C:\Program Files\Java\bin\javac.exe"

JREにjavac.exeを探しに行っているのが原因(C:\Program Files\Java\jre6\がJava6のJREのインストールディレクトリだが、ここはRuntimeなのでjavacはない)。

Eclipseのデフォルトの設定では、JREのインストールディレクトリがC:\Program Files\Java\jre6\になる。これを回避するには、「Window => Preferences => Java => Installed JREs」を開いてJava_Home(に指定しているパス)を追加・選択する(下の図)。


これでデプロイは(JSPコンパイル分だけ余計に時間がかかるが)終了する。GAEにアクセスすると以下の画面が確認できる(この画面もstrutsタグを使って作成されている)。

アクションの遷移、Strutsタグ(Taglib)、I18N(メッセージリソースの取得)などの基本的な機構が動くことが確認できるが、ローカルサーバーとは違い、自動的にブラウザーロケールを判断することができないような感じがする(また、上記のようにUploadはGAE/Jに乗せるためには、改変が必要となる)。
サンプルからは、ロケールを選んでの変更はできるので、研究の余地がありそう。