開発環境の準備
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を作成する。
- T2に読ませるServiceProviderの設定ファイルを作成する
- src/META-INFにservicesフォルダを作成し、com.google.inject.Moduleというファイルを作成する。内容は以下の一行で、自分のアプリに合わせて適宜修正する事。
com.shin1ogawa.ApplicationModule
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クラスなので、自分のアプリに合わせて適宜修正する事。
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
実装クラス中のJPQLでクラス名をFQCNで指定ている。"Select m from Messages m"とか短いクラス名で記述したいのだけど、そうするとORMの実装であるDataNucleusがクラス名を解決できないというエラーを出してくる。oem.xmlを作ってやってもダメ。なのでとりあえずFQCNで記述しておいた。誰か解決方法がわかる人は教えてください!resultList = em.createQuery( "Select m from com.shin1ogawa.entity.Messages m").getResultList(); return resultList; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } } - 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
表示がめっちゃくちゃ適当だけど、自分はWebのUIをメインに使う気がない(だからこそT2っていう選択肢でもある)のでこんなもんで十分なのだ。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); } } }
アプリケーションを実行する
GAEプラグインでプロジェクトを作った場合は、プロジェクトと同時にWeb Applicationの実行の構成も作られているのでそれを実行し、「http://localhost:8080/messageList」を開けばおk!最初は空っぽだが、「http://localhost:8080/addMessage?message=hoge」とかやるとデータが増えていくはずだ。データはアプリケーションを終了しても消えない模様。
1 件のコメント:
素敵です!
コメントを投稿