ラベル Wicket の投稿を表示しています。 すべての投稿を表示
ラベル Wicket の投稿を表示しています。 すべての投稿を表示

2009年7月30日木曜日

#appengine で ArrayStoreException

昨晩、Google App Engineにデプロイした環境でjavax.servlet.ServletException:... Caused by: java.lang.ArrayStoreException: [Ljava.lang.String;とかいうExceptionがずーっと出ていて、困ってた(詳細はこのエントリの最後に貼付けてある)。

Wicket を使っているし、頻繁にアプリケーションをデプロイし直していた途中だったので、セッション周りだろーなー、とか色々考えてたり。

で、今日はその問題の解決を!と思っていたワケですが、現象が出なくなっているんだな…。何もしてないのに。誰か同じ現象に出くわした方はおられませんか?そして、何か具体的に対処を施して解決したなら、教えてもらえませんか?ML に投げたいところなんだけど、如何せん現象がもう出ないので投げづらいw

ちなみに、ぐぐったら以下のふたつがヒントになりそーなんだけども…。

問題のException

javax.servlet.ServletException: java.lang.ArrayStoreException: [Ljava.lang.String;
    at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:239)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
    at org.mortbay.jetty.Server.handle(Server.java:313)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:506)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:830)
    at com.google.apphosting.runtime.jetty.RpcRequestParser.parseAvailable(RpcRequestParser.java:76)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:381)
    at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:139)
    at com.google.apphosting.runtime.JavaRuntime.handleRequest(JavaRuntime.java:235)
    at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4823)
    at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4821)
    at com.google.net.rpc.impl.BlockingApplicationHandler.handleRequest(BlockingApplicationHandler.java:24)
    at com.google.net.rpc.impl.RpcUtil.runRpcInApplication(RpcUtil.java:359)
    at com.google.net.rpc.impl.Server$2.run(Server.java:820)
    at com.google.tracing.LocalTraceSpanRunnable.run(LocalTraceSpanRunnable.java:56)
    at com.google.tracing.LocalTraceSpanBuilder.internalContinueSpan(LocalTraceSpanBuilder.java:516)
    at com.google.net.rpc.impl.Server.startRpc(Server.java:775)
    at com.google.net.rpc.impl.Server.processRequest(Server.java:348)
    at com.google.net.rpc.impl.ServerConnection.messageReceived(ServerConnection.java:436)
    at com.google.net.rpc.impl.RpcConnection.parseMessages(RpcConnection.java:319)
    at com.google.net.rpc.impl.RpcConnection.dataReceived(RpcConnection.java:290)
    at com.google.net.async.Connection.handleReadEvent(Connection.java:428)
    at com.google.net.async.EventDispatcher.processNetworkEvents(EventDispatcher.java:762)
    at com.google.net.async.EventDispatcher.internalLoop(EventDispatcher.java:207)
    at com.google.net.async.EventDispatcher.loop(EventDispatcher.java:101)
    at com.google.net.rpc.RpcService.runUntilServerShutdown(RpcService.java:251)
    at com.google.apphosting.runtime.JavaRuntime$RpcRunnable.run(JavaRuntime.java:374)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ArrayStoreException: [Ljava.lang.String;
    at java.io.ObjectInputStream.readArray(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.defaultReadObject(Unknown Source)
    at org.apache.wicket.Component.readObject(Component.java:4465)
    at sun.reflect.GeneratedMethodAccessor6.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readArray(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readArray(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readArray(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at java.util.HashMap.readObject(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.defaultReadFields(Unknown Source)
    at java.io.ObjectInputStream.readSerialData(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at com.google.apphosting.runtime.jetty.SessionManager.deserialize(SessionManager.java:358)
    at com.google.apphosting.runtime.jetty.SessionManager.loadSession(SessionManager.java:280)
    at com.google.apphosting.runtime.jetty.SessionManager.getSession(SessionManager.java:255)
    at org.mortbay.jetty.servlet.AbstractSessionManager.getHttpSession(AbstractSessionManager.java:237)
    at org.mortbay.jetty.Request.getSession(Request.java:998)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:192)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:237)
    ... 27 more

2009年7月27日月曜日

#appengine 用のmavenプラグインをいくつか1.2.2対応した

以下の二つのエントリで紹介したプラグインを1.2.2対応しました。どちらも、1.2.2-SNAPSHOTというバージョンでdeployしてあります。

gae-maven2-plugin
gae-wicket-quickstart

Wicketの方は、以前のエントリで紹介した、自社で作業しているWikiの内容をいくらか反映したものになっています。詳しくしりたい場合は変更内容の確認をどーぞ。

2009年7月22日水曜日

#AppEngine for Javaで全文検索する

Compassを使って全文検索を試していたので、Wikiにまとめました。

AppEngine上ではWicketも使っていて、その場合はWicket標準のFileUpload等でも問題が出るためそのあたりにも触れていたりします。

TOPGATEという文字がありますが、これは自分が勤務している会社です。会社からの情報公開という意味も兼ねて、AppEngine関連で調査した内容等を公開していくのに上記Google Sitesを使っても良いという事になりました。ついでに、AppEngine for Javaが公開されてから自分が行った作業なんかもまだまとめ作業ができてないので、それらも全部上記サイトに整理して書いていこうかと考えてます。とりあえず追記したいのはmaven関連、Datastore関連かなー。

こんなカンジに、仕事として毎日 App Engine, Wicket を触っていられる環境なワケです、羨ましいでしょ〜。

2009年7月5日日曜日

197X第2回 LT大会に参加した #197X

appengine/java用の開発環境を簡単に作れるよ!というLTを行ったが、デモの最後のmaven integration-testの実行中のappengine-java-sdkのzipファイルのダウンロードに時間がかかりすぎてしまって、その間にドラ!となっちゃったw 質疑応答中に完了していたっぽいので、20秒ほど足りなかったよぅだ。

皆のLT

やっぱ197XメンバのLTはどれも面白い。そんな中でLT大賞をあげたい!と思ったのが kousyou さんの暗渠ネタ。"暗渠"という単語は初めて聞いたけど、buzzってたし、今後大流行間違い無し!

ありがとう

参加された皆様、会場を提供してくださったグリー株式会社様、会場の温度調整をがんばったいちいくん、「作る」「営む」に励んでおられるござ先輩、2日後くらいに筋肉痛が来るはずでお気の毒ですなyamashiroさん、ドラで多数の心臓を止めかけたドラ娘、みんなありがとうございました!

あと、Jiemamyメンバがいつも下品なワケじゃないよ!普段はちゃんとマジメな話してますから!

2009年6月13日土曜日

GAE/J+Wicket用のarchetypeプラグインを作成した

2009-07-27追記

SDKのバージョンに合わせてバージョンが変わってますので更新のエントリも参照する事!

久々にWicketを触っていると、"やっぱJava屋にはWicketが一番気持ちいいな…"となったので、前回のものに加えてWicket+GAE用のquickstartも作った。

使い方

maven2がインストールされてパスも通った状態で、以下のコマンドを実行してください。

$ mvn archetype:generate -DarchetypeGroupId=com.shin1ogawa -DarchetypeArtifactId=gae-wicket-quickstart -DarchetypeVersion=1.0-SNAPSHOT -DarchetypeRepository=http://gae-j-samples.sourceforge.jp/maven/repository

改行を入れてはイケマセン。それと、archetype:createではなくarchetype:generateですので注意。うっかりcreateゴールを使うと、テンプレートのhtmlが思わぬ場所にコピーされてしまいます。

プロジェクトが生成できたら、生成されたプロジェクトフォルダに移動して以下のコマンドを。

$ mvn eclipse:eclipse dependency:copy-dependencies

詳しい説明は以前のエントリを見てください。eclipseのGAESDKプラグインを1.2.0のままの人とかも要注意。

公開してる場所

リポジトリ
http://gae-j-samples.sourceforge.jp/gae-wicket-quickstart/source-repository.html
プロジェクトの説明
http://gae-j-samples.sourceforge.jp/gae-wicket-quickstart/ja/

archetype:generateうぜぇ

色々聞いてくんな、一発で作らせろという人はgenerateゴール実行時に、以下のようにgroupId, artifactId, versionを渡してinteractiveMode=falseする事でサクッとプロジェクトができあがります。

mvn archetype:generate -DarchetypeGroupId=com.shin1ogawa -DarchetypeArtifactId=gae-wicket-quickstart -DarchetypeVersion=1.0-SNAPSHOT -DarchetypeRepository=http://gae-j-samples.sourceforge.jp/maven/repository -DgroupId=com.hoge -DartifactId=com.hoge.gae-wicket -Dversion=1 -DinteractiveMode=false

2009年4月9日木曜日

GAE/JavaでWicketを使ってみる - その3

Wicketのセッション情報の書き出し処理中にAccessControlExceptionが発生する件は、以下のようにしてHttpSessionStoreを適用(WebApplicationクラスに記述)する事で回避できた…っぽいw
@Override
protected ISessionStore newSessionStore() {
  return new HttpSessionStore(this);
}
「っぽい」って書いたのは、まだ動作していないから。うっかりJPAの処理も含めてしまってデプロイしているせいか、次は以下のようなExceptionに変わった(長いから適当に抜粋した)w
Caused by: java.io.NotSerializableException: org.datanucleus.store.appengine.query.DatastoreQuery$1
  at java.io.ObjectOutputStream.writeObject0(Unknown Source)
  ...(snip)...
  at java.io.ObjectOutputStream.writeObject0(Unknown Source)
  at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
  at java.io.ObjectOutputStream.defaultWriteObject(Unknown Source)
  at org.apache.wicket.Component.writeObject(Component.java:4395)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
  at java.lang.reflect.Method.invoke(Unknown Source)
  ...(snip)...
  at java.util.HashMap.writeObject(Unknown Source)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
  at java.lang.reflect.Method.invoke(Unknown Source)
  at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
  at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
  at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
  at java.io.ObjectOutputStream.writeObject0(Unknown Source)
  at java.io.ObjectOutputStream.writeObject(Unknown Source)
  at com.google.apphosting.runtime.jetty.SessionManager.serialize(SessionManager.java:331)
  ... 39 more
JPAをはずしたら動作してるんかなー。

GAE/JavaでWicketを使ってみる - その2

昨晩のエントリ後、承認メールが届いたので実際にデプロイを試したところ、デプロイには成功した。が、以下のようなExceptionが出てしまって動作はしなかった、という報告。

java.lang.SecurityException: Unable to create temporary file
  at java.io.File.checkAndCreate(File.java:1753)
  at java.io.File.createTempFile(File.java:1845)
  at java.io.File.createTempFile(File.java:1882)
  at org.apache.wicket.protocol.http.pagestore.DiskPageStore.getDefaultFileStoreFolder(DiskPageStore.java:583)
  at org.apache.wicket.protocol.http.pagestore.DiskPageStore.(DiskPageStore.java:606)
  at org.apache.wicket.protocol.http.pagestore.DiskPageStore.(DiskPageStore.java:615)
  at org.apache.wicket.protocol.http.WebApplication.newSessionStore(WebApplication.java:624)
  at org.apache.wicket.Application.internalInit(Application.java:982)
  at org.apache.wicket.protocol.http.WebApplication.internalInit(WebApplication.java:521)
  at org.apache.wicket.protocol.http.WicketFilter.init(WicketFilter.java:692)

後でSessionStoreをHttpSessionStoreにしてデプロイしてみる!

2009年4月8日水曜日

GAE/JavaでWicketを使ってみる

本日 Google App Engine for Java が提供開始されていて、中身を見ているとどうやらシンプルなWebAppなカンジ。Wicketも動作しそうだなーと思って試してみたメモを書いておく。去年GAEがリリースされた時もその日に色々エントリを書いたのが懐かしい。

ただし、GAEforJavaのアカウント(?)だか承認のメールが来ていないので、デプロイできておらずローカルでの動作確認しかできていない。デプロイしても動作するかどうかが不明だ…。

開発環境の準備

Eclipseを使用している事と、EclipseにGAE用のプラグインをインストールしてある事。

プロジェクトを作る

wicket-quicksartするか、EclipseのGAEプラグインで作るか、で迷うけどGAEプラグインで作る事にする。というのも、GAEプラグインが「${basedir}/war」の直下を固定で見ている気がするので、quickstartしてからフォルダ構成を変えるよりはGAEで作ったプロジェクトにWicket関連のモジュールをコピーする方がラクそうだから。

Eclipse上でプロジェクトを作る
新規作成Wizardで[Goolge/Web Application Project]を選択する。
Wicket関連のモジュールをコピーする
プロジェクト内の war/WEB-INF/lib直下に以下をコピーする。
  • wicket-1.4-rc2.jar
  • slf4j-api-1.4.2.jar
  • log4j-1.2.4.jar
  • slf4j-log4j12-1.4.2.jar
wicket-quickstartを流した事があれば、全部mavenのローカルリポジトリに存在するはずなので、そこからコピーするとかする。コピーした後は、Eclipse上でwar/WEB-INF/lib配下の対象jarを選択して[Build Path][Add to Build path]しておく。
web.xmlを修正する
普段wicket-quickstartで作られるものをそのまま流用。対象ファイルはwar/WEB-INF/web.xml。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <filter>
    <filter-name>wicket.shin1ogawa</filter-name>
     <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
    <init-param>
      <param-name>applicationClassName</param-name>
      <param-value>com.shin1ogawa.WicketApplication</param-value>
     </init-param>
   </filter>
  <filter-mapping>
    <filter-name>wicket.shin1ogawa</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>
com.shin1ogawa.WicketApplicationっていうのが自分のWicketApplicationクラス。ここは適宜変えてくだしあ。
log4.propertiesを修正する
src/log4j.propertiesに以下のWicket用のロガーの設定を追加する。
log4j.logger.org.apache.wicket=INFO, A1
log4j.logger.org.apache.wicket.protocol.http.HttpSessionStore=INFO, A1
log4j.logger.org.apache.wicket.version=INFO, A1
log4j.logger.org.apache.wicket.RequestCycle=INFO, A1
JPAを使う予定の場合は、ついでに以下も追記しておくと良い。
log4j.category.DataNucleus.Connection=DEBUG, A1
log4j.category.DataNucleus.Query=DEBUG, A1
もちろん、自分のアプリケーション用の設定も必要であれば追記する。
Wicketの実行モードをDEPLOYMENTにする
残念ながら、DEVELOPMENTモードでは動作しない。というのも、DEVELOPMENTモードではリソースのチェックか何かにModificationWatcherというクラスが動作して、その先で new Thread() している箇所があり、そこでAccessControlException(java.lang.RuntimePermission modifyThreadGroup)が発生してしまうため。例えば自分ならWicketApplicationを以下のようにした。
package com.shin1ogawa;

import org.apache.wicket.Application;
import org.apache.wicket.protocol.http.WebApplication;

public class WicketApplication extends WebApplication {
  public WicketApplication() {
    System.out.println("WicketApplication constructor()");
  }

  @Override
  public String getConfigurationType() {
    return Application.DEPLOYMENT;
  }

  @Override
  protected void init() {
    super.init();
    mountBookmarkablePage("/home", HomePage.class);
  }

  public Class<HomePage> getHomePage() {
    return HomePage.class;
  }
}
ローカル環境のjava.policyを設定すれば良いと思うが、Macの場合にコイツをどこに配置すればいいのかわからんし、とりあえずWicketが動いてりゃそれでいいし、あんまり困らんので放置しておく。
セッションを有効にする
GAEの設定ファイルである、war/WEB-INF/appengine-web.xml内のappengine-web-app要素の直下に以下を追加する。
<sessions-enabled>true</sessions-enabled>
アプリケーションを実行する
GAEプラグインでプロジェクトを作った場合は、プロジェクトと同時にWeb Applicationの実行の構成も作られているのでそれを実行し、http://localhost:8080/を開けばおk!自分の場合は以下のようにQuickstartで作ったHomePage.javaを少し変えた程度。
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
if (user != null) {
 add(new Label("message", "Hello, " + user.getNickname()));
} else {
 getRequestCycle().setRequestTarget(
   new RedirectRequestTarget(userService.createLoginURL("/home")));
}
未ログイン時にGoogleのログインページへリダイレクトするため(ログインして返ってくるため)に、WicketApplication中で mountBookmarkablePage("/home", HomePage.class); しています。

困っている

データベースも使いたい&JDOよりもJPAでしょ!と思うのでJPAを使っているが、JPQL中でのEntityクラス名をFQCNにしないと org.datanucleus.exceptions.ClassNotResolvedException とか出てしまう。persistence.xml中でclass要素の指定をしてもダメ、DataNucleusのサイトにあった orm.xml を追加しないとダメか?と試したがそれだともっとダメなカンジ。FQCNで指定すればorm.xmlが無くても動作するし、なんだろなー。

2009年3月14日土曜日

Wicket本が届いた!

一冊はAmazonで予約注文していたもの。ビニ本の方はレビューに参加した事によって秀和システム様よりいただいたもの。

2008年11月29日土曜日

PaaSのmorph LabsにJavaアプリケーションをデプロイしてみる

めづらしくJavaのWEBアプリケーションをデプロイする事ができるmorph labsというPaaSを提供しているサービスがある。自作のJavaWEBアプリを動作させたい時はいつもAWSのEC2を使っていたけど、EC2は課金もあるし、morphでも試し見ようかなー、と。。。随分昔にアカウントを作ってアプリケーション用のスペースも確保していたものの、放置して忘れていた。すると最近morphから「放置し過ぎだからアプリケーションのスペースを削除したぞゴルァ!」のようなメールが来たので、そろそろ試してみることにしたw ダメな性格ですね!

まずはmorph labsにアカウントとアプリケーションのスペースを作る

  • 「Products」タブをクリック。
  • 左下の「Signup for FREE」をクリック。
  • 登録情報を入力してSubmitしたら確認メールが来るので、そのメールに書かれている「To activate your account, please visit the following URL」のURLを開く。
  • 「Moprh Control Panel」のページに飛び、「Subscriptions」というタブが開いた状態になっているので、「Subscription name」「Morph Domain」等アプリケーションの名前やらURLやらを入力する。
  • 「Type」項目で"Java"を選択する。
  • 「Database」項目では"PostgreSQL"と"MySQL"のどちらかを選択する。
  • 「Size」項目ではデフォルトの"Developers(FREE)"を選択する。"Developer FiveRuns TuneUp BETA(FREE)"ってのもあって、何なのか気になるが、このエントリで書く手順は"Developers(FREE)"を選択して進めたもの。 最後に「Create Subscription」をClickする。
  • すると次のような管理画面へのリンクがあるControl Panelが開くはず。
  • 図の(1)と(2)のリンクから「moprh_deploy.proeperties」「morh-deploy.jar」のふたつのファイルをダウンロードしておく。デプロイ時にこれらのファイルが必要になる。

WEBアプリを作って、warを作成し、デプロイする

  • 自分の場合はmavenとwicketのquickstart pluginを使って最小限のアプリケーションを作成して、そのまま「mvn war:war」してwarを作った。
  • 「moprh_deploy.proeperties」「morh-deploy.jar」のふたつのファイルをダウンロードしたディレクトリで、以下のコマンドを実行する。
    $ java -jar morph-deploy.jar --user ${username} --password ${password} --config morph_deploy.properties ${war-file}
    ちなみに、公式ページからダウンロードできるデプロイのためのpdfのマニュアルにはjarファイル名が「morph_deploy」と間違えているので注意。ダウンロードしたデプロイ用ツールのjar名である「morh-deploy.jar」が正解。
  • すると、以下のようなログが出力されてあっさりデプロイが完了するはず。
    Uploading the code...
    Creating new appspace version...
    Deploying the application...
    Deploy Done.

    For more information on the status of this deployment, you
    can view the Deployment Logs by clicking 'Manage' located
    on your subscription widget and by clicking the Logs tab.

    In this same page, you can also view your Production logs
    and Scheduled task logs.

    ** transaction commit **
  • 実は、これだけでデプロイは完了。
  • Control Panel
  • にある歯車のようなアイコンの「Manage」をClickして、「Logs」というタブを開き「Deployment Logs」にあるリンクをクリックするとアプリケーションが起動する際のログを見る事ができる。うっかりDEVELOPMENT MODEでデプロイしてしまっているのはご愛嬌。
  • アプリケーションが起動した事を確認できたら、自分のアプリケーションのURLをブラウザで開いて確認しよう。

かなーりお手軽に使えて嬉しいですね!コンテナはJettyだそうです。また、今回はDatabaseを使ってないけど、デプロイマニュアルを見てみるとDatabaseも簡単に使えそう(一般的なXADatasourceとして使うみたい)。さらに、Emailを送信する事もできるみたい。

2008年9月21日日曜日

wicket-seamの連携…

前回のエントリの手順をちょっとだけ変えたのでWikiの方も更新しておいた。
しかし、Wicket-Seam連携と書いているけど、Seam側で用意してくれた(2.1.0-BETAでの話)連携機能で、あくまで「Seam-Wicket連携」なワケです。「Wicket-Seam連携」ではない。Seamもまた鈍重なカンジなんだよな。もっとダイエットした、薄いSeamが現れてくれない物だろうか?

2008年9月9日火曜日

wicket-seam連携をjetty上で!(暫定版)

前回のwicket-seam関連エントリで書いた手順は忘れて下さい、無かったことにして下さい

あの方法ではSeam自体は起動しているモノの、WicketのWebPage等がSeamの管理下に入っていません。そのあたりを見ていて、自分なりにわかってきた事をまず書き留めておくとします。
  • SeamがWicketのComponentを自動登録する際には、「WEB-INF/wicket」直下の*.classをscanする。パス固定。めっちゃ固定。ちなみに、この「WEB-INF/wicket」については「org.jboss.seam.wicket.ioc.JavassistInstrumentor」で「public static String DEFAULT_WICKET_COMPONENT_DIRECTORY_PATH = "WEB-INF/wicket";」というカンジで定義されている。
  • 起動がめっちゃ遅いJBossASを使わずとも、Eclipse+run-Jetty-run pluginでイケる。
  • つまり、Eclipse+WTPを使う必要はなく、Eclipse for JEEでなくても良い。上記と会わせたこの2点で相当軽量化した環境で開発できる。

JBossASとEclipseWTP+αで満足な人

上に書いたように、Wicket用のPageクラス等の出力先をWEB-INF/wicketにしておけば、すんなり動作すると思います。ここから下は見る必要がありません。ややこしぃだけなので、むしろ見てはいけません。

なんとか動かしてみた

しかし、上記に書いた問題をなんとかクリアすればSeamによるWicket用Componentの読み込み等の動作も確認できました。SeamビルトインのComponentのInjectも正しく動作します。
まずはwicket-archetype-quickstartでprojectを作成
ここはいつも通り。
pom.xml
一気に以下に書き換える。Jettyで実行するので、JavaEE系を追加するためJBossのモジュールを借りまくる。この例の場合はjetty-maven-pluginのclassesDirectory,webAppSourceDirectoryをtarget/classesに向けている。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.shin1o</groupId>
  <artifactId>com.shin1o.wicketSeam01</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>wicketSeam01</name>
  <description></description>
  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
  <dependencies>
    <!--  WICKET DEPENDENCIES -->
    <dependency>
      <groupId>org.apache.wicket</groupId>
      <artifactId>wicket</artifactId>
      <version>${wicket.version}</version>
    </dependency>

    <!-- LOGGING DEPENDENCIES - LOG4J -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.4.2</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
    </dependency>

    <!--  JUNIT DEPENDENCY FOR TESTING -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.2</version>
      <scope>test</scope>
    </dependency>

    <!--  JETTY DEPENDENCIES FOR TESTING  -->
    <dependency>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>jetty</artifactId>
      <version>${jetty.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>jetty-util</artifactId>
      <version>${jetty.version}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>jetty-management</artifactId>
      <version>${jetty.version}</version>
      <scope>provided</scope>
    </dependency>

    <!-- seam-wicket -->
    <dependency>
      <groupId>org.jboss.seam</groupId>
      <artifactId>jboss-seam</artifactId>
      <version>2.1.0.BETA1</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.seam</groupId>
      <artifactId>jboss-seam-wicket</artifactId>
      <version>2.1.0.BETA1</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.seam</groupId>
      <artifactId>jboss-seam-debug</artifactId>
      <version>2.1.0.BETA1</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.seam</groupId>
      <artifactId>jboss-seam-ioc</artifactId>
      <version>2.1.0.BETA1</version>
    </dependency>
    <!-- JSF(使わないけどSeamの起動に必要) -->
    <dependency>
      <groupId>javax.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>1.2_02</version>
    </dependency>
    <!-- EJB3 -->
    <dependency>
      <groupId>javax.ejb</groupId>
      <artifactId>ejb-api</artifactId>
      <version>3.0</version>
    </dependency>
    <!-- JPA -->
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
      <version>1.0</version>
    </dependency>
    <!-- JTA -->
    <dependency>
      <groupId>javax.transaction</groupId>
      <artifactId>jta</artifactId>
      <version>1.0.1B</version>
    </dependency>
    <!-- hibernate validator(使わないけどSeamの起動に必要?) -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>3.0.0.ga</version>
    </dependency>
  </dependencies>
  <build>
    <resources>
      <resource>
        <filtering>false</filtering>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <filtering>false</filtering>
        <directory>src/main/java</directory>
        <includes>
          <include>**</include>
        </includes>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <filtering>false</filtering>
        <directory>src/test/java</directory>
        <includes>
          <include>**</include>
        </includes>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </testResource>
    </testResources>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <configuration>
          <classesDirectory>${project.build.directory}/classes</classesDirectory>
          <webAppSourceDirectory>${project.build.directory}/classes</webAppSourceDirectory>
          <contextPath>/${artifactId}</contextPath>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>
          <downloadSources>true</downloadSources>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <properties>
    <wicket.version>1.3.4</wicket.version>
    <jetty.version>6.1.4</jetty.version>
  </properties>
  <repositories>
    <repository>
      <id>repository.jboss.org</id>
      <name>JBoss Repository</name>
      <url>http://repository.jboss.org/maven2</url>
    </repository>
  </repositories>
</project>
Seam用の各種xmlの準備と「WEB-INF/wicket」の問題
単純に考えると、みっつの解決策があります。
  1. 素直に、src/main/webapp/WEB-INF/wicketを出力フォルダにする。
  2. Seamが起動するよりも速いタイミングで、org.jboss.seam.wicket.ioc.JavassistInstrumentor#DEFAULT_WICKET_COMPONENT_DIRECTORY_PATHの値を書き換える。
  3. 最も美しく無いなやり方だが、とりあえず簡単な方法が、org.jboss.seam.wicket.ioc.JavassistInstrumentorをコピーして自分のソースとして配置。DEFAULT_WICKET_COMPONENT_DIRECTORY_PATHの値を"target/classes"にしてしまう。
自分の場合は、出力フォルダはtarget配下じゃないとイヤだし、最も早いタイミング=ServletContextListenerを実装して登録する?くらいしか思いつかないし、余計なもんも作りたく無い。なので、とりあえず最後の方法で動作させています。それに伴い「WEB-INF」フォルダは「src/main/webapp」から「src/main/resources」の下に移動する必要があります(後の手順でrun-jetty-runプラグインのwebapp dirでtarget/classesを指定するため、その直下にWEB-INFが配置される必要がある)。
WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app id="WebApp_ID" 
  version="2.4" 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">
 <display-name>
 com.shin1o.wicketSeam01</display-name>
 <listener>
  <listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
 </listener>
 <filter>
  <filter-name>Seam Filter</filter-name>
  <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>Seam Filter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
</web-app>
component.xml
WEB-INFフォルダ直下に以下のようなcomponent.xmlを作成しておく必要があります。
<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:wicket="http://jboss.com/products/seam/wicket"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd
                 http://jboss.com/products/seam/wicket http://jboss.com/products/seam/wicket-2.1.xsd
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">
 <core:init jndi-pattern="#{ejbName}/local" debug="true" />
    <wicket:web-application application-class="com.shin1o.WicketApplication" />
</components>
Eclipseのプロジェクトを作成、インポート
mvn eclipse:eclipse -DdownloadSources=trueで。私と同じ対応の場合はJavassistInstrumentorをコピーして上書きしちゃうので、ソースは必須です。project生成後はEclipseからインポート。
JavassistInstrumentorを書き換え
ソースフォルダに「org.jboss.seam.wicket.ioc」パッケージを作成し、JavassistInstrumentorをjboss-seam-wicket-2.1.0-BETA.jarに関連づけられたソースからコピーします。で、私の場合はDEFAULT_WICKET_COMPONENT_DIRECTORY_PATHの値を""、つまり空文字=webappとして認識するフォルダのルート、としました。
run-jetty-runによる実行の構成
自分の場合は実行の構成で指定する「webapp dir」をデフォルトの"src/main/webapp"から"target/classes"にしました。
実行
run-jetty-runから実行させても良いし、mvn jetty:runしてもおk。上にある手順やpom.xmlは全て「target/classes」をwebappのフォルダ&SeamのWicket用Component読み込みフォルダ、とした場合の話です。このあたりのフォルダの構成を変えるのであれば色々触る必要があります。
ビルトインのComponentをInjectしてみる
HomePage.java等に、以下のようなコードを書いてみる。
  @In
  private Identity identity;

  @Logger
  private Log log;
コンストラクタあたりでこいつらの値を参照すると、ちゃんとSeamからInjectされるのが確認できるはずです。

さらなる課題

  • 現状は、ビルトインのComponentは利用できているが、WicketのWebPageやWebPanel(Wicket用Componentと総称する)ではない自前のComponent(Logic用Componentと総称する)は、Wicket用Componentとは別の出力フォルダに出力されてくれないと困る。これら2種類をごっちゃに出力していると、SeamがなんでもかんでもWicket用Componentとしてドンドン登録してしまうよぅな動作に見える。
    • Wicket用ComponentとLogic用Componentでソースフォルダ、出力フォルダを分ける?mavenでそんな事できるっけ?(特にmaven-jetty-pluginの動作が気になる)
    • Wicket用ComponentとLogic用Componentでプロジェクトを分ける?その場合はSeamがどんな動作をするだろぅか?
    これはどーしたもんかなぁ?
そんなこんなで、この手順も暫定版です。だから、後で「無かった事に…」とするかもしれませぬ。

2008年9月5日金曜日

wicket-seam連携最初の一歩

2008/09/09追記: この手順はよろしくありません。新しいエントリを参考にして下さい。

前提とする環境

  • Maven2がインストール済みである事。
  • JBossAS 4.2.3GAをインストール済みである事。
  • Eclipse Ganymede for JavaEE Developersがインストール済みである事。
    • さらに、このEclipseに上記JBossASのServer構成を追加済みである事。

まずはWicketのProjectを作成する

  1. http://wicket.apache.org/quickstart.html
    にある、wicket-archetype-quickstartプラグインを使ったmavenコマンドの自動生成機能を使ってwicketのprojectを生成する。
  2. 生成されたprojectフォルダに移動し、pom.xmlに修正を加える。
    1. dependencies要素に以下を追加。
      <!-- seam-wicket -->
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam</artifactId>
        <version>2.1.0.BETA1</version>
      </dependency>
      <dependency>
        <groupId>org.jboss.seam</groupId>
        <artifactId>jboss-seam-wicket</artifactId>
        <version>2.1.0.BETA1</version>
      </dependency>
      <!-- EJB3 -->
      <dependency>
        <groupId>javax.ejb</groupId>
        <artifactId>ejb-api</artifactId>
        <version>3.0</version>
      </dependency>
      <!-- JPA -->
      <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
        <version>1.0</version>
      </dependency>
    2. build/plugins要素に以下を追加。
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <configuration>
          <downloadSources>true</downloadSources>
          <downloadJavadocs>true</downloadJavadocs>
          <wtpversion>2.0</wtpversion>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
          <webResources>
            <resource>
              <directory>${basedir}/src/main/webapp/WEB-INF</directory>
              <targetPath>WEB-INF</targetPath>
              <filtering>true</filtering>
            </resource>
          </webResources>
        </configuration>
      </plugin>
    3. project要素直下に以下を追加。
      <repositories>
        <repository>
          <id>repository.jboss.org</id>
          <name>JBoss Repository</name>
          <url>http://repository.jboss.org/maven2</url>
        </repository>
      </repositories>
  3. 生成されたprojectフォルダで、$ mvn eclipse:eclipse -DdownloadSources=true を実行し、EclipseからImportする。EclipseのDynamic Web ProjectとしてImportされるはず。
  4. src/main/webapp/WEB-INFにcomponents.xmlを作成する。中身は以下のように記述する。
    <?xml version="1.0" encoding="UTF-8"?>
    <components xmlns="http://jboss.com/products/seam/components"
                xmlns:wicket="http://jboss.com/products/seam/wicket"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation=
                    "http://jboss.com/products/seam/wicket http://jboss.com/products/seam/wicket-2.1.xsd
                     http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">
        <wicket:web-application application-class="com.shin1o.WicketApplication" />
    </components>
  5. src/main/webapp/WEB-INF/web.xmlの、display-name要素より下を以下に置き換える。
    <listener>
      <listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
    </listener>
    <filter>
      <filter-name>Seam Filter</filter-name>
      <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>Seam Filter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
  6. WicketApplication.javaのSuperClassをorg.apache.wicket.protocol.http.WebApplicationからorg.jboss.seam.wicket.SeamWebApplicationに変更する。ここがミソ。これはSeamと連携するための入り口となる部分なのだ。
    1. そのままだとprotected Class getLoginPage()の実装が必要になるので、中身をreturn getHomePage();とかで実装しておく。
  7. Projectの「Run As - Run on Server」で、JBoss4.2用のサーバ構成にデプロイし、起動する。
  8. コンソールにだらだらーっとログが表示されて、最後の方にいつもの「WARNING: Wicket is running in DEVELOPMENT mode.」みたいなのが見えるはず。
  9. ブラウザでhttp://localhost:8080/プロジェクト名、で動作を確認する。

課題

  • とりあえず、ここで書いた状態だと全然Seamを使ってないので、ちょっとくらい使ってみるエントリも書く。
  • Eclipse JEEもJBossASも重いから、こいつら使わずになんとならんものか。Jettyみたいなカンジでサクサク、といけばいいんだけど。
  • このシンプルな何も無い状態で、WicketTesterが動作しない。
    ERROR - RequestCycle - Can't instantiate page using constructor public com.shin1o.HomePage(org.apache.wicket.PageParameters) and argument
    org.apache.wicket.WicketRuntimeException: Can't instantiate page using constructor public com.shin1o.HomePage(org.apache.wicket.PageParameters) and argument
    at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:168)
    at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:66)
    at org.apache.wicket.request.target.component.BookmarkablePageRequestTarget.newPage(BookmarkablePageRequestTarget.java:262)
    仕組み的に、何か工夫する必要があるのは当然だとは思うが、今はちょっとわからない。WicketTesterがあってこそ、ブラウザでの動作確認とか手間な事せずにサクサク開発できるのに、これはないだろー。WicketTesterはどーすりゃ動くんだ?もしも、どーあがいてもWicketTesterが使えないという事になったなら、SeamWicket連携なんてうんこ。カス。価値がない。絶対に使う事は無い。

とにかく、WicketTesterが動作するにはどうすりゃいいのかを見つけないと話にならんが、しばらくは触ってみる。また、このページの内容は、今後はWikiにてメンテしていきます。

追記

WicketTesterが実行できない件で、なんでWicket単独で落ちてるんだろう?と思ったら、Caused Byが下に出てました。
Caused by: java.lang.NullPointerException
at org.jboss.seam.wicket.WicketComponent.getInstance(WicketComponent.java:109)
  at org.jboss.seam.wicket.SeamAuthorizationStrategy.isInstantiationAuthorized(SeamAuthorizationStrategy.java:49)
  at org.apache.wicket.Application$1.onInstantiation(Application.java:276)
  at org.apache.wicket.Application.notifyComponentInstantiationListeners(Application.java:974)
  at org.apache.wicket.Component.(Component.java:874)
  at org.apache.wicket.MarkupContainer.(MarkupContainer.java:105)
  at org.apache.wicket.Page.(Page.java:236)
  at org.apache.wicket.markup.html.WebPage.(WebPage.java:184)
  at com.shin1o.HomePage.(HomePage.java:21)
  ... 36 more

2008年8月3日日曜日

第1回Wicket-ja勉強会

金曜日に開催された第1回Wicket-ja勉強会に参加してきた。自宅の近所でめっさ便利であった。
まずは、素晴らしい会場を提供して下さったエクスアーツ株式会社様、ありがとうございました!広かったなー。あと、オフィスにあったフラフープが気になった。普段からどなたか勤務中に使われているのか?ダイエット用なのか?罰ゲーム用なのか?…気になりました。

t_yanoさん

Statefulの話
何度聞いても好きな話。
メモリ大丈夫?的なよくある質問への回答方法
ダイジョブ、セカンドレベルキャッシュがあるとの事。このあたり詳しくないから勉強になった。。。が、ちょっと理解できなかった点もあったので、動かして確認できないもんかな…。
AJAXの話。
ビヘイビアーはAJAXのためのモノだと思ってたけど、違ったらしい。AJAXとの親和性が高いけどねーと。自分はAJAXにしか使った事がねぇ。あと、DOMのIDとの相性の悪さ。確かに、勝手に新しいIDが振られたりで違和感がある事もある。が、回避方法もあるとの事(面倒らしい)。
皆の好きなアレを作ってみる
こんな事を書いたら怒られるかもしれないが、NetBeansを使っているt_yanoを見て「NeetBeans--」と思ったのは自分だけではあるまい。たまたま調子が悪かったのもあるんだろうけど、Wicket+WicketTesterを、Eclipse+QuickJUnitでサックサク!に慣れてる自分からしたら少し歯がゆかった。
自分が「Aha!」となったのは、「アレからhtmlをそのまま保存します。これを”デザイナさんが渡してくれたHTMLと考えて作業します”」というトコ!これはWicketのデモとしては、かなり良い見せ方に思えた。POHP使うなんてダサイよ派の自分がいうのはアレだが、WicketのPOHPを魅力的に見せるアプローチだったと思う。ウマイなぁ〜。自分が社内でWicket勉強をやった時に使えば良かった。次に社内でやる時は絶対ぱくる。

たけうちさん

ComponentResolver
確かに、Componentのadd()とかは勝手にやって欲しいが、自分的には実行時にやるなら「そういうライブラリを明示的に呼び出して」やるか、「IDEのPluginがジェネレート」してくれりゃそれがいいかな。しかし、いくつかの拡張ポイントの紹介があって、「ほほぅ、おもしろい事ができそぅだ」ってなってしまうのはやっぱり仕方が無い事ですね。
この時、受講者の方で「それをやると結局XMLに回帰してプログラマが面白くない世界がやってくるのでは」というのがあったが、自分的には「モノを作るのがラクになるならいいと思うなー」というカンジかな。ソースを書きたいんじゃなくて、モノを作りたいなら、そんなに拒否する事でもないと思う。ラクにするためのアプローチの違いだけじゃないかなぁー。あ、HTMLに特殊なタグを埋め込むとかは絶対反対派ですけれども。そもそもHTMLテンプレートなんて無い方が良ry

kishiさん

「我流ですが」の断り付きで、実際構築したアプリのデモと、それを構築した時に苦労した点。ちなみに構築したアプリは”予測コミュニティ「こうなる。」”。参加してみたが、プロフィールのアイコンのデフォルトがなんだかいいカンジ。サイトのデザインにはiKnowに似た雰囲気を感じた。

gishiさん

個人的に最も興味を持ったかも。大学の職員としての立場で、学内のシステムを構築されておられる。チームメンバとして「毎年ゼロにリセットされる学生」を抱えておられるようだ。その教育やら、実際のシステム構築、保守等での苦労話いろいろ。
  • JSFと比較した学習コスト
  • JSFと比較した生産性
  • 何より幸せになれた!(残業時間が半減したそぅだ)
そこそこの規模で、ちゃんと使えてますよ!との心強いお話。

keisukenさん

Scala+Wicketの説明。Scalaは興味がある物の、Tutorialというか、PDFのアレを試しただけでしかないからScalaに偏った話は理解できなかった…申し訳ございません。GroovyのWicketを一度試そぅとしたものの、ちっともメンテナンスされている雰囲気がなくガッカリしていたが、Scalaという選択肢もあるのだなぁ、と。

u1tnkさん

なんとWicket1.2からのWicket大先輩の方で、実際にASP業務に使用しているとの実績を見せていただけた。Wicket1.2は暴走した先輩が導入した、との話ですが、それがあったからこそWicketだったワケで!先輩はすぐにいなくなってしまったそぅですが、この業界そんな事もよくあるって事で、とにかく良かったじゃぁないですか!
ここでも、実際に業務に使用しているよ!という心強いお話。
t_yanoが電話で関を離れた瞬間に、t_yanoへの応援メッセージのスライドを持ってくるとかマジうまい。ツボを心得たプレゼンだ、と多いに感心した!

懇親会

参加せずw
飛び入りで参加しようとも思ったが、大人数で予約との誤差とか考えて自重した。水道橋に行くと家から離れる事になるのが面倒だった、というのもあるw
皆さん、ご苦労様でした!&ありがとぅございました〜。

2008年6月2日月曜日

[解決]AjaxRequestTarget#addComponent()

以前のエントリ、AjaxRequestTarget#addComponent()で以下のような事を書いた。
llegalArgumentExceptionが投げられるようになっている。 結局、ListViewをdiv wicket:id="ListViewContainer"みたいな要素の内側に配置して、 ajaxRequestTarget#addComponent()にこのContainerを渡す、みたいな方法をとった。で、このContainer の実装として「org.apache.wicket.markup.html.border.BoxBorder」を使用した。 これでちゃんと動作しているから、困ってるという訳でも無いかもしれんけど、気分的には、こういう時に使うPanelとかBoxみたいなContainerとしてしか機能がないContainerを使いたい、あんまキレイじゃない、という気分。
が、WicketのWikiにこの件について「Working With AjaxRequestTarget」というページがあり、そのものズバリだった!
  1. Do not add the view to a target. Use a parent component instead.
  2. If the view is within a Panel do not add the panel to the target. Use a WebMarkupContainer (or similar component) instead.
WebMarkupContainerというclassがそういった用途に使えるとの事。ちなみに、このclassはしょっちゅう出てくるので知ってはいたが、自分でインスタンスを作れるclassだとはちっとも思っていなかったw 基本的な事だろぅに、ちゃんと読めよ、俺。
  • Html
    <html>
      <head>
        <title>Wicket Quickstart Archetype Homepage</title>
      </head>
      <body>
        <div><form wicket:id="myform">
        <div><input type="text" wicket:id="text"/></div>
        <!-- Containerが必要。 -->
        <div wicket:id="listViewContainer"><ul>
        <li wicket:id="listView"><a href="#" wicket:id="label"></a></li>
        </ul></div></form></div>
      </body>
    </html>
  • Page
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.wicket.PageParameters;
    import org.apache.wicket.ajax.AjaxRequestTarget;
    import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
    import org.apache.wicket.ajax.markup.html.AjaxLink;
    import org.apache.wicket.behavior.IBehavior;
    import org.apache.wicket.markup.ComponentTag;
    import org.apache.wicket.markup.MarkupStream;
    import org.apache.wicket.markup.html.WebMarkupContainer;
    import org.apache.wicket.markup.html.WebPage;
    import org.apache.wicket.markup.html.form.Form;
    import org.apache.wicket.markup.html.form.TextField;
    import org.apache.wicket.markup.html.list.ListItem;
    import org.apache.wicket.markup.html.list.ListView;
    import org.apache.wicket.model.Model;
    
    public class HomePage extends WebPage {
      @SuppressWarnings("serial")
      public HomePage(final PageParameters parameters) {
        // myform
        final Form form = new Form("myform");
        // myform/text
        final TextField text = new TextField("text", new Model(""));
        text.setOutputMarkupId(true);
        form.add(text);
        // myform/listViewContainer: WebMarkupContainerでListViewを包む。
        WebMarkupContainer listViewContainer = new WebMarkupContainer("listViewContainer");
        listViewContainer.setOutputMarkupId(true);
        form.add(listViewContainer);
        // myform/listViewContainer/listView
        listViewContainer.add(new ListView("listView", getLabelStrings()) {
          @Override
          protected void populateItem(ListItem item) {
            String labelString = item.getModelObjectAsString();
            item.add(new AjaxLink("label", new Model(labelString)) {
              @Override
              public void onClick(AjaxRequestTarget target) {
                text.setModel(new Model(this.getModelObjectAsString()));
                target.addComponent(form.get("text"));
              }
              @Override
              protected void onComponentTagBody(MarkupStream markupStream,
                  ComponentTag openTag) {
                super.replaceComponentTagBody(markupStream, openTag, this
                    .getModelObjectAsString());
              }
            });
          }
        });
        add(form);
        // 
        text.add(newOnChangeAjaxBehavior(form));
      }
    
      @SuppressWarnings("serial")
      private IBehavior newOnChangeAjaxBehavior(final Form form) {
        return new OnChangeAjaxBehavior() {
          @Override
          protected void onUpdate(AjaxRequestTarget target) {
            String textString = form.get("text").getModelObjectAsString();
            ListView listView = (ListView) form.get("listViewContainer:listView");
            List<String> filteredStrings = getLabelStrings();
            if (textString != "") {
              filteredStrings.clear();
              for (String label : getLabelStrings()) {
                if (label.indexOf(textString) >= 0) {
                  filteredStrings.add(label);
                }
              }
            }
            listView.setList(filteredStrings);
            target.addComponent(form.get("listViewContainer"));
          }
        };
      }
    
      private List<String> getLabelStrings() {
        List<String> labelStrings = new ArrayList<String>();
        labelStrings.add("apple");
        labelStrings.add("orange");
        labelStrings.add("lemon");
        labelStrings.add("banana");
        labelStrings.add("grape");
        return labelStrings;
      }
    }
  • TestCase
    import static org.junit.Assert.assertArrayEquals;
    import static org.junit.Assert.assertEquals;
    
    import org.apache.wicket.ajax.markup.html.AjaxLink;
    import org.apache.wicket.markup.html.WebMarkupContainer;
    import org.apache.wicket.markup.html.form.Form;
    import org.apache.wicket.markup.html.form.TextField;
    import org.apache.wicket.markup.html.list.ListView;
    import org.apache.wicket.util.tester.FormTester;
    import org.apache.wicket.util.tester.WicketTester;
    import org.junit.Before;
    import org.junit.Test;
    
    public class HomePageTest {
      private WicketTester tester;
    
      @Before public void setUp() {
        tester = new WicketTester();
        tester.startPage(HomePage.class);
        tester.assertRenderedPage(HomePage.class);
        tester.assertNoErrorMessage();
      }
    
      @Test public void testRenderMyPage() {
        tester.assertComponent("myform", Form.class);
        tester.assertComponent("myform:text", TextField.class);
        tester.assertComponent("myform:listViewContainer", WebMarkupContainer.class);
        tester.assertComponent("myform:listViewContainer:listView", ListView.class);
        tester.assertComponent("myform:listViewContainer:listView:0:label", AjaxLink.class);
      }
    
      @Test public void testAjaxLink() {
        FormTester formTester = tester.newFormTester("myform");
        formTester.setValue("text", "");
        // 0:"apple", 1:"orange", 2:"lemon", 3:"banana", 4:"grape"
        tester.clickLink("myform:listViewContainer:listView:0:label");
        assertEquals("apple", formTester.getTextComponentValue("text"));
        tester.clickLink("myform:listViewContainer:listView:1:label");
        assertEquals("orange", formTester.getTextComponentValue("text"));
      }
    
      @Test public void testAjaxEvent() {
        FormTester formTester = tester.newFormTester("myform");
        final String textPath = "myform:text";
        final String listViewPath = "myform:listViewContainer:listView";
        ListView listView = (ListView) tester.getComponentFromLastRenderedPage(listViewPath);
    
        // 空文字の時はフィルタが働かない。全部表示される。
        formTester.setValue("text", "");
        tester.executeAjaxEvent(textPath, "onchange");
        assertArrayEquals(new String[] { "apple", "orange", "lemon", "banana", "grape" }, 
            listView.getList().toArray());
        // "a"を含まない"lemon"が消える。
        formTester.setValue("text", "a");
        tester.executeAjaxEvent(textPath, "onchange");
        assertArrayEquals(new String[] { "apple", "orange", "banana", "grape" }, 
            listView.getList().toArray());
        // "ap"を含む"apple","grape"が残る。
        formTester.setValue("text", "ap");
        tester.executeAjaxEvent(textPath, "onchange");
        assertArrayEquals(new String[] { "apple", "grape" }, listView.getList().toArray());
      }
    }

2008年5月27日火曜日

Eclipse上だけでWicketの環境を構築するメモ

q4eとrun-jetty-runを使う。WTPは使わない。Jettyのinstallもたぶん必要ない。

EclipsePluginの準備

  1. Eclipseにq4eとrun-jetty-runのふたつのpluginをinstallする(update siteからinstall)。
    1. http://q4e.googlecode.com/svn/trunk/updatesite/
    2. http://run-jetty-run.googlecode.com/svn/trunk/updatesite

Projcetを作成

  1. package explorerから[New]-[Other]でNewダイアログを開く。
  2. [Maven 2 Project Createion Wizard]を選択して「Next」。
  3. 「Project Name」を入力して「Next」。例えば「mywicketsample」とか。
  4. 「Select a maven archetype to use」と出てきて、archetypeArtifactの一覧が表示される。ここから「wicket-archetype-quickstart」を選択して「Next」。
  5. GroupId、PackageName、Versionなどarchetypeプラグイン用のオプションを入力できるので、必要があれば入力して「Finish」
  6. Wicketのバージョンなどを変更したければ、pom.xmlを編集すればOKOK。

実行の構成を作成

  1. [Run][Open Run Dialog]を開く。
  2. 実行構成のtypeとして[Jetty Webapp]が存在するはずなので、選択してツールバーの「新規」ボタンをClick。
  3. 実 行構成の名称を入力し(例えばmywicketsample-jetty)、「Project」で対象のProjectを選択する。Maven2で作った プロジェクトであれば、「WebApplication/Jetty」配下の「Context」「Webapp dir」はそれぞれ「/」「/src/main/webapp」のままで問題ないはず。必要に応じて、以下を設定しておく。
    1. [Common]タブで「Display in favorite menu」を選択しておくと、EclipseToolbar上のDropDownするButtonに常に表示されてくれるから便利かも。
    2. [Common]タブの「Save as」で「Shared file」で実行の構成をファイルとしてProject内に保存しておくと、皆で同じ構成を共有できて便利かも。
  4. 上記手順で完了。実行してブラウザ上から「http://localhost:8080/」を開けば動作を確認できる。

2008年5月25日日曜日

AjaxRequestTarget#addComponent()

要求としては、del.icio.usのブックマーク登録時に使用するような「タグの入力補完」の機能を実現するために、「ListViewで表現しているタグのList」を「特定のTextFieldの値でフィルタする」、それをAjaxで更新したい、という単純なもの。Wicketは1.3.3。
Modelを作成するLogicはいいとして、それをAjaxRequestTargetにaddComponent()する時に困った。AjaxRequestTarget#addComponent()でListViewを渡すと以下のようなExceptionが発生する。
This component is a repeater and cannot be repainted via ajax directly. Instead add its parent or another markup container higher in the hierarchy.
となる。AbstractRepeaterのインスタンスをaddComponent()するとAjaxRequestTarget#addComponent(Component component, String markupId)内で上記のIllegalArgumentExceptionが投げられるようになっている。
結局、ListViewをdiv wicket:id="ListViewContainer"みたいな要素の内側に配置して、ajaxRequestTarget#addComponent()にこのContainerを渡す、みたいな方法をとった。で、このContainerの実装として「org.apache.wicket.markup.html.border.BoxBorder」を使用した。
これでちゃんと動作しているから、困ってるという訳でも無いかもしれんけど、気分的には、こういう時に使うPanelとかBoxみたいなContainerとしてしか機能がないContainerを使いたい、あんまキレイじゃない、という気分。
それか、独自のListViewを使って解決するのがいいのか。Wicket使ってる他のみんなはこういぅ時(RepeaterをaddComponent()したいとき)にどーしてるんだろ?

2008年2月17日日曜日

つぶやき

wicket-ja

wicket-jaドメインでオープンですー。t_yanoさん、色々忙しいであろぅ中、超超乙ですた!

アジャイルプラクティス

先週のデブサミ会場で初日に並んでいるのを見たから、嫁と相談して買うことに…と思いきや、2日目にはすでに売り切れ!なんでまた追加購入するかというと、以前書いた、我ながら後輩への貸し方がみっともないというエントリが、木下氏のはてぶに登録されたという恥ずかしい事態になっている事が判明。とりあえず、後輩の分と、嫁の分を追加で2冊購入することにする!俺が折り目つけたり線を引いたりして滅茶苦茶汚れてるヤツを後輩にあげるのも何だしね。俺は俺で、自分が色々考えてメモした(汚した)本が大事だし、いつも手元に置いときたいしw…だって後輩のヤツ、全然読み終わりやがらん!さっさと読まんかいボケ!

バナナ

今月に入って、3本パック*5、2本パック*2を消化。うち一本は嫁に喰われたから今のところ16本/17日のペース。

2008年2月11日月曜日

Grailsを触る その3

前回までに加えてちょっと調べた。

  • 初期データが無いのは不便すぐる。
    • 「conf/BootStrap.groovy」に記述する。def init = { servletContext-> } 内にdomail classを作成→save(),をずらずら書けば毎回同じデータが読み込まれる。
  • Databaseの設定は「conf/DataSource.groovy」に記述されている。defaultでは3種類の実行モード「development」「test」「production」ごとに違うDatabase設定がされている。ここでdevelopmentの設定をmemoryモードではなくfileモードにすれば動作確認中に入力したデータを保持できるはずだ。たぶん、そぅなるはず。
  • かなり愕然とした発見。Groovyではセミコロンがあってもエラーにならない。色んなサンプルをみるとセミコロンが入ったソースが無かったため、てっきりセミコロンはダメなんだと思ってた。つい癖で入力してしまったセミコロンを消す作業をものすごいたくさんやったのに。それこそ、書いたソースコードの行数以上のセミコロンを消したのに。
  • 「$ grails test-app」でtest配下にあるTestを実行する事ができる。
  • 「$ grails install-plugin webtest」とすると、Canoo WebTestを使用することができる。Seleniumではないのね。
  • Canoo WebTestのTestCaseを実行するには「$ grails run-webtest」とする。
これらを見た後、grails-wicketプラグインを試そうと思ったけど、動作させることができなかった。GORMとgrailsのrailsっぽいジェネレータを使いつつWicket、とか素敵だな!と思うけど、あきらめる。grailsのバージョンを下げたらイケるとの情報があったけど、そこまでしてGrails+Wicketをやる気は出なかった。じっくりと、grailsの(といぅかRailsの)何に興味を持ったのか?と考えると、たぶん以下の点。
  • ActiveRecord。まずはコレ。
  • 各種Classの「ジェネレート」機能。
これだけ…かな?Railsを使ったことが無いから、RoRの利点は体感していないからこれくらいしか浮かばない。Test云々はRoRに限ったものではない、Javaでも同じ事だし。 このふたつのうち後者の方はEclipseのPluginを作れば実現できるだろぅ。前者はjPersistかactiveobjectsといぅ事になるか。後ろはこれらでやって、Presentation層にWicketを使う。これが自分の理想だろぅか?たぶん違う。Presentation層がwicketでは不十分。wicketの利点としてよく唄われるPOHPが邪魔だ。自分の理想では、Temaplteがないこと。管理するリソースは少ない方が良いし。 JFrameを作るようにWebPageをサーバ側で構築し、デザインはCSSとして完全に外出しする。CSSとComponentの接続はid値とclass値のみ。これがたぶん理想。だがしかし、それができるPresentation層のFrameworkが見つからん!Wicketが一番近いのは間違い無さそうなんだが…。あぁ、GWTも近いといえば近いのか?