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

2009年6月14日日曜日

GAE/J+T2(+Guice)用のarchetypeプラグインを作成した

昨日のWicket+GAE用のarchetypeプラグインに続けて、t2-framework(+guice Adapter)をGAE/J上で動作させるプロジェクトを作るarchetypeプラグインも作成した。本日S2Conでt2の話を聞いてわくわくしたし。

使い方

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

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

もちろん、途中で改行してはイケマセン。archetype:createではなくarchetype:generateです。ただWicketの時とは違い、間違えてcreateしてもarchetype:createは失敗するのですぐ気づくと思います。

ostypeというパラメータを設定していますが、macの時だけ指定してください。windowsとlinuxはJava6の時にJava Buildpathにどういう値を設定すれば良いのかわからずまだ作っていないのですが、もしJava6が設定できなかった場合はEclipseに取り込み後にDataNucleusがEclipse内でエンハンスに失敗するとか、Classのバージョンが云々というエラーが見れるはずです。その場合はプロジェクトの設定で「JavaBuildPath」で使用されるJDKのバージョンをJava6に設定してやれば大丈夫です。。。が、明日にでもLinuxとWindowsで試す事にします。

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

$ mvn eclipse:eclipse dependency:copy-dependencies

後はEclipseに取り込めばOKOKです。もちろんEclipse上、Maven上、双方で単体テストが可能です。

他にも細かい説明は昨日のエントリや、下に示すドキュメントを参照してください。

公開してる場所

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

2009年5月11日月曜日

GAE for Java用のサンプルプロジェクトを公開しておきます

シンプルな構成

GAE for Javaでテンプレートとして使えるサンプルを作っていたが、おおよそできた。

  • ORMにはJDOを使う
  • EclipseでのJDOエンハンス対象クラスを設定する
  • Eclipse、MavenのどちらからもJDO関連のモジュールの単体テストが動作する
  • Javaのロガーのフォーマッタを入れ替える(1ログ1行で表示する)
  • Presentation層にはjspとjstlを使う

主にMavenでの単体テストが可能な事がメインで、これができるとHudsonでMavenプロジェクトを定義してのCIが可能となるので必須の機能なはず。みんなCIしてるもんね?CIを想定しなくて許されるのは小学生までだよね?
極力シンプルにしたので、ここから色々派生する事ができると思う。

こいつはシンプルなのでarchetype-pluginを作ってもいいんだけど、要望はなさげ。というのも、$ mvn eclipse:eclipseしてもEclipse側でうまく扱えない→結局ローカルでの実行にantかeclipseが必要になるので、maven単体でIntegrationTestが実行できそーにない。ここまでできればmaven一本で環境構築できて嬉しいんだけどな。cargo-pluginを使ってtomcatにデプロイして動作させる事もできるのかもしれんけど、gaeのsdkとの振る舞いの違いが気になって何かイヤだ。
ちなみにeclipseとmavenをうまく連動する事ができない問題は、eclipse側でmavenでいう依存性のスコープの概念が無いという問題と、jdoのdatanucleusのエンハンスの問題。GoogleAppEnginePlug-inによるエンハンス実行時の環境でのシステムプロパティを触る事ができれば結構うまく連動できそうなんだけども、GoogleAppEnginePlug-inのソースが見当たらないのでなんともならない。

  • https://svn.sourceforge.jp/svnroot/gae-j-samples/gae-jdo-simple-sample/trunk/gae-jdo-simple-sample

t2-frameworkの構成

t2-frameworkを使ったエントリも書いていたが、そちらのサンプルは主に以下のようにしている。

  • ORMにはJDOを使う。
  • EclipseでのJDOエンハンス対象クラスを設定する
  • Eclipse、MavenのどちらからもJDO関連のモジュールの単体テストが動作する
  • Javaのロガーのフォーマッタを入れ替える(1ログ1行で表示する)
  • Presentation層には何も使っていない。jQueryからt2-frameworkの@Ajaxなインターフェースを呼び出して描画してるだけ
  • t2はGuiceアダプタを使用している

t2-frameworkはシンプルさのバランスが絶妙なので、上記のシンプルなサンプルがあればt2はすぐ動作するし、サンプルなんて必要無いくらい。Viewに何のフレームワークも使っていないのは、はt2の@AjaxでWebAPIドリブンぽくGAE/Jを使い、WebブラウザでのViewにはjQueryで描画するだけにしておきたいため。
自分が作るサービスではWebのインターフェースはおまけ程度にして、AIRを正式なクライアント側プラットフォームとして使おうと考えている。なので、shotたん、new AMF()したいです…とかつぶやいておく事にする。

  • https://svn.sourceforge.jp/svnroot/gae-j-samples/gae-jdo-simple-sample/trunk/gae-jdo-simple-sample

Wicketの構成

Wicket on GAE/Jについての最後のエントリ以降、何も触ってませんw

t2のトコでも書いた通り、自分はWebでのインターフェース自体を重視していないためWicketにはあんまりこだわっていない。テンプレートとPageクラスの両方でコンポーネントの構造を意識する必要がある時点でDRYじゃないなーとかマイナスな思考も強くなって来た。Javaからのアプローチをする時はPageクラスさえ構築すれば後はよしなにやってくれろや、とか。
とはいえWicketは大好きなんで、ちょっとしたサービスをGAE/Jにデプロイする際はWicketを使う可能性が一番高いと思う。シンプルなサンプルを作成するためにjstlとか触ったら、htmlに式を書くとかありえなく思えて気絶しそうになったし。

2009年4月9日木曜日

GAE/Javaでt2-frameworkを使ってみる

Wicketを試す合間にT2(with Guice+JPA)を試していたのでその手順を。

開発環境の準備

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

プロジェクトを作ってT2用に設定する

Eclipse上でプロジェクトを作る
新規作成Wizardで[Goolge/Web Application Project]を選択する。なお、JDKは1.6に変更しておく事!
T2関連のモジュールをコピーする
t2-frameworkのサイトから「Core/T2-${version}-ga」と「Core/Lucy-${version}-ga」をダウンロードする。また、今回はついでにGuiceも使いたいので「Samples/T2 + Guice + JPA sample application」もダウンロードしておく。以下をダウンロードしたモジュールから抜き出して${baseDir}/war/WEB-INF/libにコピーして、[Build path][Add to Build Path]しておく。他にはcommons-langも適当に拾って来て同様に配置、ビルドパスに含めておこう。
  • commons-0.5.1-ga.jar
  • guice-1.0.jar
  • guice-servlet-1.0.jar
  • guiceadapter-0.5.1-ga.jar
  • logback-classic-0.9.15.jar
  • logback-core-0.9.15.jar
  • slf4j-api-1.5.6.jar
  • t2-0.5.1-ga.jar

各種設定ファイルの準備をする

web.xmlを修正する
対象ファイルは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">
  <context-param>
    <param-name>t2.encoding</param-name>
    <param-value>UTF-8</param-value>
  </context-param>

  <filter>
    <filter-name>Guice Servlet Filter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
  </filter>
  <filter>
    <filter-name>t2</filter-name>
    <filter-class>org.t2framework.t2.filter.T2Filter</filter-class>
    <init-param>
      <param-name>t2.rootpackage</param-name>
      <param-value>com.shin1ogawa.page</param-value>
    </init-param>
    <init-param>
      <param-name>t2.container.adapter</param-name>
      <param-value>org.t2framework.t2.adapter.GuiceAdapter</param-value>
    </init-param>
    <!--
    <init-param>
      <param-name>t2.eagerload</param-name>
      <param-value>true</param-value>
    </init-param>
    -->
    <init-param>
      <param-name>t2.exclude-resources</param-name>
      <param-value>css, js</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>Guice Servlet Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>  
  <filter-mapping>
    <filter-name>t2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>
log4.propertiesを修正する
src/log4j.propertiesに以下を追記しておく。
log4j.category.DataNucleus.Connection=DEBUG, A1
log4j.category.DataNucleus.Query=DEBUG, A1
log4j.logger.org.t2framework=TRACE, A1
log4j.logger.com.shin1ogawa=TRACE, A1
最後の行は自分のアプリに合わせて適宜変更する事。
セッションを有効にする
GAEの設定ファイルである、war/WEB-INF/appengine-web.xml内のappengine-web-app要素の直下に以下を追加する。
<sessions-enabled>true</sessions-enabled>
persistence.xmlを作成する。

src/META-INFにpersistence.xmlを作成する。内容は以下のとおりで、これはGAE/JavaでJPAを使う時に必要なもの。JDOを使わずJPAを使うのであればGAEプラグインがデフォルトでsrc/META-INF直下に作成するjdoconfig.xmlは削除してもかまわない、と思う。

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

  <persistence-unit name="transactions-optional">
    <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
    <class>com.shin1ogawa.entity.Messages</class>
    <properties>
      <property name="datanucleus.NontransactionalRead" value="true"/>
      <property name="datanucleus.NontransactionalWrite" value="true"/>
      <property name="datanucleus.ConnectionURL" value="appengine"/>
    </properties>
  </persistence-unit>
</persistence>
class要素に記述するのはJPAのEntityクラスなので、自分のアプリに合わせて適宜修正する事。
T2に読ませるServiceProviderの設定ファイルを作成する
src/META-INFにservicesフォルダを作成し、com.google.inject.Moduleというファイルを作成する。内容は以下の一行で、自分のアプリに合わせて適宜修正する事。
com.shin1ogawa.ApplicationModule

Javaのクラスを作成する

JPA用のModule
package com.shin1ogawa;
import javax.persistence.*;
import com.google.inject.*;
public class JpaModule extends AbstractModule {
  @Override protected void configure() {
    bind(EntityManager.class).toProvider(new Provider() {
      public EntityManager get() {
        EntityManager manager = Persistence.createEntityManagerFactory(
            "transactions-optional").createEntityManager();
        manager.setFlushMode(FlushModeType.COMMIT);
        return manager;
      }
    }).in(Singleton.class);
  }
}
JPA用のEntity
今回は超単純なEntityをひとつだけ用意してみる。persistence.xmlのclass要素でJPA的に使えるように登録する必要がある(上記手順で設定済み)。
package com.shin1ogawa.entity;
import java.util.Date;
import javax.persistence.*;
@Entity
public class Messages {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
  private String userName;
  private String message;
  private Date updated;
  public Long getId() { return id; }
  public void setId(Long id) { this.id = id; }
  public String getUserName() { return userName; }
  public void setUserName(String userName) { this.userName = userName; }
  public String getMessage() { return message; }
  public void setMessage(String message) { this.message = message; }
  public void setUpdated(Date updated) { this.updated = updated; }
  public Date getUpdated() { return updated; }
}
処理モジュールを作成する
Guiceを使って管理する対象にしたいので、インターフェースと実装クラスを分離している。
package com.shin1ogawa.logic;

import java.util.List;
import com.google.appengine.api.users.User;
import com.shin1ogawa.entity.Messages;

public interface IMessagesLogic {
  public Messages newMessage(User user, String messageBody);
  public List<Messages> selectAll();
}
package com.shin1ogawa.logic;

import java.util.*;
import javax.persistence.*;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.slf4j.*;
import com.google.appengine.api.users.User;
import com.google.inject.Inject;
import com.shin1ogawa.entity.Messages;

public class MessagesLogic implements IMessagesLogic {
  static final Logger logger = LoggerFactory.getLogger(MessagesLogic.class);

  @Inject EntityManager em;

  public Messages newMessage(User user, String messageBody) {
    Messages message = new Messages();
    message.setUserName(user != null ? user.getNickname() : "unknown");
    message.setMessage(messageBody);
    message.setUpdated(new Date(System.currentTimeMillis()));

    EntityTransaction transaction = em.getTransaction();
    transaction.begin();
    em.persist(message);
    transaction.commit();
    return message;
  }

  public List<Messages> selectAll() {
    @SuppressWarnings("unchecked")
    List resultList = em.createQuery(
        "Select m from com.shin1ogawa.entity.Messages m").getResultList();
    return resultList;
  }

  @Override public String toString() {
    return ToStringBuilder.reflectionToString(this);
  }
}
実装クラス中のJPQLでクラス名をFQCNで指定ている。"Select m from Messages m"とか短いクラス名で記述したいのだけど、そうするとORMの実装であるDataNucleusがクラス名を解決できないというエラーを出してくる。oem.xmlを作ってやってもダメ。なのでとりあえずFQCNで記述しておいた。誰か解決方法がわかる人は教えてください!
T2のアプリケーション用のModule
package com.shin1ogawa;
import org.slf4j.*;
import com.google.inject.*;
import com.shin1ogawa.logic.IMessagesLogic;
import com.shin1ogawa.logic.MessagesLogic;
public class ApplicationModule extends AbstractModule {
  @Override protected void configure() {
    install(new ServletModule());
    install(new JpaModule());
    bind(IMessagesLogic.class).to(MessagesLogic.class).in(Scopes.SINGLETON);
  }
}
T2のページクラス
ちなみに、jspは好きではないのでjspは使ってない。データの投稿もGetで行う事が前提で書いた。データの一覧表示用とデータの投稿用のページクラス。
package com.shin1ogawa.page;

import java.util.*;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.t2framework.t2.annotation.core.Default;
import org.t2framework.t2.annotation.core.Page;
import org.t2framework.t2.contexts.WebContext;
import org.t2framework.t2.navigation.SimpleText;
import org.t2framework.t2.spi.Navigation;

import com.google.inject.Inject;
import com.google.inject.servlet.RequestParameters;
import com.google.inject.servlet.RequestScoped;
import com.shin1ogawa.entity.Messages;
import com.shin1ogawa.logic.IMessagesLogic;

@RequestScoped
@Page("messageList")
public class MessageList {
  @Inject @RequestParameters Map params;

  @Inject IMessagesLogic logic;
  
  @Default public Navigation index(WebContext context) {
    StringBuilder b = new StringBuilder();
    b.append(ToStringBuilder.reflectionToString(this));
    
    List<Messages> messageList = logic.selectAll();
    if (messageList != null) {
      b.append("messages count=").append(messageList.size()).append("\n");
      for (Messages messages : messageList) {
        b.append(ToStringBuilder.reflectionToString(messages)).append("\n");
      }
    } else {
      b.append("messages count=0").append("\n");
    }
    return new SimpleText(b.toString());
  }
}
package com.shin1ogawa.page;

import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.t2framework.t2.annotation.core.Default;
import org.t2framework.t2.annotation.core.Page;
import org.t2framework.t2.contexts.WebContext;
import org.t2framework.t2.navigation.Redirect;
import org.t2framework.t2.navigation.SimpleText;
import org.t2framework.t2.spi.Navigation;

import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.inject.Inject;
import com.google.inject.servlet.RequestParameters;
import com.google.inject.servlet.RequestScoped;
import com.shin1ogawa.logic.IMessagesLogic;

@RequestScoped
@Page("addMessage")
public class AddMessage {
  @Inject @RequestParameters Map params;

  @Inject IMessagesLogic logic;

  @Default public Navigation index(WebContext context) {
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();

    final String message = params.get("message") != null ? params.get("message")[0] : null;
    if (StringUtils.isEmpty(message)) {
      return new SimpleText("メッセージが入力されていません。");
    } else {
      logic.newMessage(user, message);
      return new Redirect(MessageList.class);
    }
  }
}
表示がめっちゃくちゃ適当だけど、自分はWebのUIをメインに使う気がない(だからこそT2っていう選択肢でもある)のでこんなもんで十分なのだ。

アプリケーションを実行する

GAEプラグインでプロジェクトを作った場合は、プロジェクトと同時にWeb Applicationの実行の構成も作られているのでそれを実行し、「http://localhost:8080/messageList」を開けばおk!最初は空っぽだが、「http://localhost:8080/addMessage?message=hoge」とかやるとデータが増えていくはずだ。データはアプリケーションを終了しても消えない模様。