GAE/JにStruts1.3のサンプルを乗せてみた
Springframeworkにも、(古くから)SpringMVCというモジュールがあるが、いまだにApache Strutsを使うことが多い。10年くらいJavaEEのアプリをやっている方なら、Struts1.0の頃の衝撃を覚えているに違いない(昔話が多いような。。。)。
ActionForm、ActionErrorsとか、「なんて便利なんだろう」と思った。当時は、フレームワークブームで、Strutsの他にも、Turbine、Tapestry、WebWorkなどといった「MVC2モデル前提でアプリ開発をするフレームワーク」がたくさんあった(Turbineとか、Tapestryはページ・レイアウトのやり方なんかで、「毛色の変わった」フレームワークだった)。v1.1が全盛期で、今でもStruts1.1ベースで実稼動しているシステムが多くあるに違いない。
その後、オリジナル開発者のC.R.McClanahan氏がJSFに流れてしまい、v1.3の初期は旧来のStrutsとJSFのプロジェクトが混在していた。
現在は、Struts1.3系が旧来のStruts、Struts2.0系が旧来のWebWork2のプロジェクトになっている(JSFのプロジェクトはShale?という名前で、Apacheのトップにあったが見当たらない。どこに行ってしまったんだろう)。
前置きが長くなってしまったが、Struts1.3.10に付属するサンプルを、Google Apps Engine/Java(GAE/J)に乗せてみた。
そういえば、DWRやSpringframework(DI、AOP)の実験はしたが、JSPやTaglibの実験をしていないことに(今更ながら)気が付いたので、これらが動くかどうかも実験する。
以下の作業は、GAE/JのプラグインがインストールされたEclipse(Ganymede)で行った。
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にコピーする。(注記:Tomcatでstruts-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)が、
<?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タグの
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に乗せるためには、改変が必要となる)。
サンプルからは、ロケールを選んでの変更はできるので、研究の余地がありそう。