Wicket on GAE/j #01

会社の人と話をして色々思うところがあったので、本格的に個人で開発してみて、GAE/jに載せてみようと思います。で、取り敢えず以下のような構成を考えています。

単体のSlim3の方がspin-upも早くて最適解だとは思いますが、個人的にJSPはもう飽きたのでHTMLベースのWicketを使ってみようと思ってます。

以前環境構築をどこかに書いた気がしますが、ちょっと見つからなかったので備忘録を兼ねて。

Eclipseのインストール

GooglePlugin for Eclipseは3.3/3.4しかサポートしていないので、ここからEclipse IDE for Java Developers」をダウンロードしました。

GooglePlugin for Eclipseのインストール

Eclipseを起動して[Help]->[Software Updates...]->[Available Software]->[Add site]で表示されたダイアログに以下のURLを入力しました。GooglePlugin for EclipseのUpdate siteです
http://dl.google.com/eclipse/plugin/3.4

これで最低限の環境構築が完了しましたので、次に実際にプロジェクトを作成します。

プロジェクトの作成

Package Explorerのコンテキストメニューから[New]->[Web Application Project]をクリックしました。表示されたダイアログで、「Project name:gaej-tutorial」、「Package:net.masa.tutorial」と入力します。
また、最下部の「Generate GWT project sample code」のチェックを外して「Finish」ボタンをクリックしました。

プロジェクトが作成されたので、次にWicketが動作するようにします。

Wicketの準備

Wicket用のjarを${project}/war/WEB-INF/lib配下にコピーしました。取り敢えずwicket-quickstartで指定されているjarのみです。

  • wicket-1.4.7.jar
  • wicket-extensions-1.4.7.jar
  • slf4j-api-1.5.8.jar
  • slf4j-log4j12-1.5.8.jar
  • log4j-1.2.14.jar

追加したjarはコンテキストメニューの[Build Path]->[Add to Build Path]をクリックして、ビルドパスに追加しておきます。

Wicketの動作設定

appengine-web.xmlの設定

Wicketではセッションを利用する必要があるので、appengine-web.xmlでセッションを利用可能にします。以下の記述を追加しました。またapplicationにはgaej-tutorialと追加しました。

<application>gaej-tutorial</application>
<!-- 中略 -->
<sessions-enabled>true</sessions-enabled>
web.xmlの設定

web.xmlWicketのフィルター設定を追加しました。内容は以下の通りです。

<?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">
  <filter>
    <filter-name>wicket.tutorial</filter-name>
    <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
    <init-param>
      <param-name>applicationClassName</param-name>
      <param-value>net.masa.tutorial.application.TutorialApplication</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>wicket.tutorial</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>
Applicationクラスの作成

web.xmlで指定したWebApplicationクラスを作成します。GAE/JでWicketを動作させる場合、以下のポイントがあるので、その設定を行います。
WicketApplication#newSessionStore()でHttpSessionStoreを返すようにさせる
WicketをDEPLOYMENTモードで動作させる
リソースの更新チェックを停止させる

Wicketのデフォルト設定では、セッションオブジェクトをファイルに保存しますが、GAE/Jではファイルの作成が制限されているのでHttpSessionに保存するように設定します。
次にWicketのdevelopmentモードではThreadを生成してリソースの更新チェックを行いますが、GAE/JではThreadの生成を制限しているのでdeploymentモードで動作させる必要があります。これにより、Threadの生成を停止できます。
上記のポイントを踏まえ、実際のソースは以下の通りとなります。

/**
 * アプリケーション設定クラスです。
 * 
 * @author m-namiki
 * 
 */
public class TutorialApplication extends WebApplication {

	// ------------------------------------------------------------- [Constants]

	/** リクエスト・レスポンスおよびHTMLテンプレートファイルのエンコードです。 */
	private static final String CHAR_ENCODE = "UTF-8";

	/** HTMLテンプレートファイルのベースディレクトリです。 */
	private static final String PAGES_BASE_DIR = "WEB-INF";

	/** HTMLテンプレートファイルの本来の階層から除去するパスです。 */
	private static final String PAGES_PATH = "net/masa/tutorial";

	// -------------------------------------------------------- [Public methods]

	/**
	 * コンストラクタです。
	 */
	public TutorialApplication() {
	}

	/**
	 * 動作モードを設定します。<br>
	 * WicketをGAE/Jで動作させる場合、{@link org.apache.wicket.Application.DEPLOYMENT}
	 * モードで動作させなければなりません。
	 * 
	 * @see org.apache.wicket.protocol.http.WebApplication#getConfigurationType()
	 */
	@Override
	public String getConfigurationType() {
		return Application.DEPLOYMENT;
	}

	/**
	 * 初期画面のページクラスを返却します。
	 * 
	 * @see org.apache.wicket.Application#getHomePage()
	 */
	@Override
	public Class<? extends Page> getHomePage() {
		return HelloWorldPage.class;
	}

	// ----------------------------------------------------- [Protected methods]

	/**
	 * アプリケーションの初期化処理です。<br>
	 * 文字コードの設定、テンプレートファイルの場所の指定を行います。また、WicketをGAE/Jで動作させる場合、
	 * リソースの更新チェックは停止させる必要があります。
	 * 
	 * @see org.apache.wicket.protocol.http.WebApplication#init()
	 */
	@Override
	protected void init() {
		super.init();
		// リクエスト・レスポンスの文字コード設定
		getRequestCycleSettings().setResponseRequestEncoding(CHAR_ENCODE);
		// HTMLテンプレートの文字コード設定
		getMarkupSettings().setDefaultMarkupEncoding(CHAR_ENCODE);
		// リソースの更新チェックを停止
		getResourceSettings().setResourcePollFrequency(null);
		// テンプレートファイルの場所を指定
		getResourceSettings().addResourceFolder(PAGES_BASE_DIR);
		getResourceSettings().setResourceStreamLocator(
				new ResourceStreamLocator() {
					@SuppressWarnings("unchecked")
					@Override
					public IResourceStream locate(Class clazz, String path) {
						if (path.indexOf(PAGES_PATH, 0) != -1) {
							IResourceStream located = super.locate(clazz, path
									.substring(PAGES_PATH.length() + 1));
							if (located != null) {
								return located;
							}
						}
						return super.locate(clazz, path);
					}
				});

	}

	/**
	 * セッションストアを作成します。
	 * 
	 * WicketをGAE/Jで動作させる場合、セッション情報を{@link javax.servlet.http.HttpSession}
	 * に格納する必要があります。
	 */
	@Override
	protected ISessionStore newSessionStore() {
		return new HttpSessionStore(this);
	}

}

上記init()内でHTMLテンプレートファイルの場所を指定してしました。Wicketは通常の場合ページクラスと同じ階層からHTMLテンプレートファイルを探しますが、PAGES_BASE_DIR(=WEB-INF)から探すようにして、また、PAGES_PATH(=net/masa/tutorial)は、パッケージ名から除去すべきパスを定義しています。上記の設定からWicketはHTMLテンプレートファイルをWEB-INF/pagesから探すようになります。

HelloWorldPageの作成

アプリケーションが最初に呼び出すページはApplication#getHomePage()で指定しますが、TutorialApplicationではHelloWorldPage.classを指定しているので、そのPageクラスを作成します。パッケージはnet.masa.tutorial.pagesです。

/**
 * HelloWorld画面クラスです。
 * 
 * @author m-namiki
 * 
 */
public class HelloWorldPage extends WebPage {
	public HelloWorldPage() {
		add(new Label("message", "Hello, World."));
	}
}

ここではHTMLテンプレートファイルに表示するメッセージを指定しました。次にこのメッセージを表示するためのHTMLテンプレートファイルを作成します。パスはgaej-tutorial/war/WEB-INF/pages/HelloWorldPage.htmlです。

<html>
<head>
  <title>Hello World</title>
</head>
<body>
  <span wicket:id="message">Dummy text.</span>
</body>
</html>

動作確認

これですべての準備が整ったので、実際に動作させてみます。プロジェクトのコンテキストメニューから[Run As]->[Web Application]をクリックするとサーバが立ち上がります。
…はずですが、以下のようなエラーが表示されて起動に失敗しました。

Missing required argument 'module[s]'
Google Web Toolkit 2.3.0
DevMode [-noserver] [-port port-number | "auto"] [-whitelist whitelist-string] [-blacklist blacklist-string] [-logdir directory] [-logLevel level] [-gen dir] [-bindAddress host-name-or-address] [-codeServerPort port-number | "auto"] [-server servletContainerLauncher[:args]] [-startupUrl url] [-war dir] [-deploy dir] [-extra dir] [-workDir dir] module[s]

調べてみたところ、GWTのモジュールが存在しないとダメらしいので、プロジェクトのコンテキストメニューから[New]->[Module]をクリックし、ダイアログでは「Package:net.masa.tutorial.module」、「Module name:GwtModule」と入力して「Finish」ボタンをクリックしました。次に再度プロジェクトのコンテキストメニューから[Run As]->[Run Configurations...]をクリックし、右ペインで[GWT]タブを選択して作成したモジュールを[Add]してから「Run」ボタンをクリックすると今度は無事にサーバが起動しましたので、Webブラウザでhttp://localhost:8888/にアクセスしてみるとHelloWorldPage.javaで指定したメッセージが画面に表示されました。

一応GAE/J上でWicketの動作を確認したので、次回はSlim3との連携を試してみようと思います。

デプロイエラーで参考にしたのはこちらのページです。
http://d.hatena.ne.jp/reppets/20110403/1301788939