Slim3 で Text 型で保存したい文字列を取り扱う場合

先日、

というエントリを書いたが、親切に現在の Slim3 で Text 型を扱う場合の方法を教えてもらうことができた。

Slim3 で Text 型を扱う際に何が問題だったのか

Text 型で保存した文字列を View 側で表示させる際に、Slim3の JSP Function で Text 型から String 型に変換して全文を表示させられないことだった。
実際にやろうとしていたことは、Text 型で保存したと仮定する値 body を View で表示させようとした場合、通常の h でそのまま表示させようとすると、

    ${f:h(e.body)}

Text#toString() では、Text 型で保存した文字列の「基になる文字列の最初の 70 文字を返します」という処理になり、文字列すべてが表示されるわけではない。

そのため、文字列すべてを表示させるために、Text 型で保存した値 body を String 型に変換して表示させる必要がある。そのメソッドは Text#getValue() を利用する必要がある。しかし、

    ${f:h(e.body.getValue())}

と書くと実行時にエラーが発生して、正常に動作しない。
そのため、Controller か ModelService か、それとも Model 内で Text 型の値を一度 String 型に変換して、エラーを回避する方法しかないと考えたのだ。

実際の対応方法

Model クラス側で Text 型で保存したいと考えている値に対して、「@Attribute(lob = true)」を宣言するだけである。
例えば、上記の値 body の場合は、Model クラス内で

    private Text body;

と記述していた箇所を

    @Attribute(lob = true)
    private String body;

と変更するだけでいい。

なぜこれだけの変更でいいのか

Text 型の宣言を、String 型の宣言 + 「@Attribute(lob = true)」に変更するだけで、ModelMeta クラス内で型変換をしてくれる実装を自動で記述してくれるからだ。
実際に変更された箇所としては、変更前は、

    (前略)
    public final org.slim3.datastore.UnindexedAttributeMeta<news.model.Article, com.google.appengine.api.datastore.Text> body = new org.slim3.datastore.UnindexedAttributeMeta<news.model.Article, com.google.appengine.api.datastore.Text>(this, "body", "body", com.google.appengine.api.datastore.body.class);
    (中略)
    model.setbody((com.google.appengine.api.datastore.Text) entity.getProperty("body"));
    (中略)
    entity.setUnindexedProperty("body", m.getBody());
    (後略)

となっていたのが、

    (前略)
    public final org.slim3.datastore.StringUnindexedAttributeMeta<news.model.Article> body = new org.slim3.datastore.StringUnindexedAttributeMeta<news.model.Article>(this, "body", "body");
    (中略)
    model.setBody(textToString((com.google.appengine.api.datastore.Text) entity.getProperty("body")));
    (中略)
    entity.setUnindexedProperty("body", stringToText(m.getBody()));
    (後略)

と内部処理が変更されている。

最後に

の変更が、さらに修整されたのはこの頃なのだろうか。

Slim3 JSP で f:h Text#getValue() は使えない

Text型で保存されたデータをいつ String 型に変換するかを悩み、他の人はどうしているのか調べてみたところ、

higayasuo 2009/06/28 10:06
後、r297でf:hでもText#getValue()を呼び出すように修正しました。

http://d.hatena.ne.jp/suzune64/20090627/1246110579#c1246151209

と書かれていたので、試してみたところ、エラーが発生する。*1
確かに、Javadoc にも

Encodes the input object. If the object is a string, it is escaped as HTML. If the object is a key, it is encoded as Base64. Anything else is converted to a string using toString() method.

http://slim3.googlecode.com/svn/trunk/slim3/javadoc/

と書かれているだけで、Text#getValue() については触れられていない。
他の人の解決方法では、Model クラスで型変換を実装している人が多いようだ。
Text 型のプロパティを返すための便宜上の String 型のプロパティを用意して、getter で値を取得する際に Text 型に変換して値を渡すという方法だ。

    private Text text;
    private String string;

    public void setText(Text text) {
        this.text = text
    }

    public Text getText() {
        return text;
    }

    public String getString() {
        return (null == text) ? null : text.getValue();
    }

この JSP の Text#getValue() は一度実装されたけれども、実装を削除されたのだと思うが、その改訂履歴が見つからなかったのと、他に f:h(Text#getValue()) でコードを書いているサンプルが見つからなかったので、随分前に実装が変更されていのだろうと思う。

追記

ここまで書いて思ったのが、仮に Text#getValue() の実装は、

${f:h(text.getValue())}

だったのだろうか。それとも

${f:h(text).getValue()}

だったのだろうか。

*1:上記の文を見つける前にダメもとで既に試していたので、エラーが発生することは予想済みだった。

Slim 3 で日付の書式を整える

(略)
import java.util.Date;
import org.slim3.util.DateUtil;
(略)
    public void run(String args[]) {
        Date today = new Date();
        // 日付の書式は SimpleDateFormat を参照
        String datePattern = "yyyy-MM-dd";
        System.out.println(DateUtil.toString(today, datePattern));
    }

出力結果。

2011-04-13

Slim3 での No API environment is registered for this thread.

Service クラスの Test Case を作成していてテストを実行するとエラーが発生する。

java.lang.NullPointerException: No API environment is registered for this thread.

調べてみると、

ただ1点だけ、テスト用に Key を生成する方法がわかりません。
無理やり Key を生成しようとすると、以下のエラーが出てしまいます。

http://d.hatena.ne.jp/hageyahhoo/20100206/1265449051

という記述をみつけた。
確かにテストケース内で、

public class ItemServiceTest extends AppEngineTestCase {
    private ItemService service = new ItemService();
    private Key key = Datastore.createKey(Item.class, "4873114926");

と key を生成していた。
解決方法も引用先に記載されていたが、1年前以上の記載だったため、今度はメソッド内で key を記述してみた。*1

public class ItemServiceTest extends AppEngineTestCase {
    private ItemService service = new ItemService();
    @Test
    public void test() throws Exception {
        Key key = Datastore.createKey(Item.class, "4873114926");

すると今度は、「java.lang.NullPointerException: No API environment is registered for this thread.」が発生せずに、テストを完了できた。

*1:実は最初は Test Case のメソッド内に記述をしており、その際は今回のエラーが発生しないことに思い出して解決することができた

Slim3 で「Could not initialize class org.slim3.datastore.Datastore」が発生する

このサンプルチュートリアルを作成中に、index.jsp にアクセスをすると下記のエラーが発生する。

HTTP ERROR 500

Problem accessing /bbs/. Reason:

    Could not initialize class org.slim3.datastore.Datastore
Caused by:

java.lang.NoClassDefFoundError: Could not initialize class org.slim3.datastore.Datastore
	at org.slim3.datastore.DatastoreFilter.doFilter(DatastoreFilter.java:68)
	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
	at org.slim3.controller.HotReloadingFilter.doHotReloading(HotReloadingFilter.java:223)
	at org.slim3.controller.HotReloadingFilter.doFilter(HotReloadingFilter.java:187)
	at org.slim3.controller.HotReloadingFilter.doFilter(HotReloadingFilter.java:157)
	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
	at com.google.appengine.api.blobstore.dev.ServeBlobFilter.doFilter(ServeBlobFilter.java:58)
	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
	at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
	at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:122)
	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
	at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
	at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
	at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
	at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
	at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
	at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:70)
	at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
	at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:351)
	at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
	at org.mortbay.jetty.Server.handle(Server.java:326)
	at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
	at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
	at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:547)
	at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
	at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
	at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
	at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Powered by Jetty://

必要な jar ファイルがプロジェクトにコピーされていないことが原因

ググって調べてみたところ、同じようなエラーが発生している人がいた。

war/WEB-INF/lib 以下を調べてみれば、google app engineに必要と思われるjarが空っぽである

http://otsubo.info/blog/2011/03/slim3-on-macjavalangnoclassdeffounderror.html

確かに作成中のプロジェクトにある「war/WEB-INF/lib」下にある jar ファイルが下記のものだけしかない。
ファイル名に記述されているバージョン番号は適宜読み替える。

そして、不足している jar ファイルは下記のファイル。

  • appengine-api-1.0-sdk-1.4.2.jar
  • appengine-api-labs-1.4.2.jar
  • appengine-jsr107cache-1.4.2.jar
  • datanucleus-appengine-1.0.8.final.jar
  • datanucleus-core-1.1.5.jar
  • datanucleus-jpa-1.1.5.jar
  • geronimo-jpa_3.0_spec-1.1.1.jar
  • geronimo-jta_1.1_spec-1.1.1.jar
  • jdo2-api-2.3-eb.jar
  • jsr107cache-1.1.jar

解決方法

新規に slim3 のプロジェクトを作成して、不足しているな jar ファイルを既存のプロジェクトの「war/WEB-INF/lib」にコピーする。
解決後の「war/WEB-INF/lib」下にある jar ファイルは下記のようになる。

  • appengine-api-1.0-sdk-1.4.2.jar
  • appengine-api-labs-1.4.2.jar
  • appengine-jsr107cache-1.4.2.jar
  • datanucleus-appengine-1.0.8.final.jar
  • datanucleus-core-1.1.5.jar
  • datanucleus-jpa-1.1.5.jar
  • geronimo-jpa_3.0_spec-1.1.1.jar
  • geronimo-jta_1.1_spec-1.1.1.jar
  • gwt-servlet.jar
  • jdo2-api-2.3-eb.jar
  • jsr107cache-1.1.jar
  • junit-4.7.jar
  • ktrwjr.jar
  • slim3-1.0.9.jar