Memcache のサンプル利用例

利用する箇所は、Controller でも Service でもよい。
下記は Controller での利用した場合のサンプル。

import org.slim3.memcache.Memcache;

public class MyAppController extends Controller {
    private MyAppService service = new MyAppService();
    private static final Integer 30_SECONDS = 30;

    @Override
    public Navigation run() throws Exception {
        List<MyModel> myModelList;

        String parameterHoge = (null == request.getParameter("a")) ? "N/A" : request.getParameter("a");
        String parameterFuga = (null == request.getParameter("b")) ? "N/A" : request.getParameter("b");
        String cacheKey = Hoge + ":" + Fuga;

        if (Memcache.contains(cacheKey)) {
            myModelList = articleList = Memcache.get(cacheKey);
        } else {
            myModelList = service.getAll(Hoge, Fuga);
            Memcache.put(cacheKey, myModelList, Expiration.byDeltaSeconds(30_SECONDS), SetPolicy.SET_ALWAYS);
        }

        requestScope("myModelList", myModelList);
        return forward("index.jsp");
    }
}

すごく簡単。

Slim3 の Timezone の取り扱いについて

Slim3タイムゾーンの挙動が複雑なことに気が付いた。

  1. FrontController では、タイムゾーンUTC に設定されている。
  2. TestCase から FrontController で設定されているタイムゾーンはローカル時間。
    この場合、自分は日本時間に設定しているので、JST で設定されている。
  3. TestCase で使う Controller のタイムゾーンはローカル時間。
    この場合も JST で設定されている。しかし、org.slim3.util.DateUtil 利用した際のタイムゾーンUTC になる。
  4. org.slim3.util.DateUtil を利用すると、web.xml で設定している時間帯に自動的に変換される。

FrontController のタイムゾーンの挙動

コントローラー側の記述。

public class TimezoneController extends Controller {
    @Override
    public Navigation run() throws Exception {
        TimeZone tz = TimeZone.getDefault();
        requestScope("timeId", tz.getID());
        requestScope("timeZoneName", tz.getDisplayName());
        requestScope("offset", tz.getRawOffset());
        return forward("timezone.jsp");
    }
}

JSP の記述。

<%@page pageEncoding="UTF-8" isELIgnored="false" session="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="f" uri="http://www.slim3.org/functions"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムゾーン</title>
</head>
<body>
    <p>${f:h(timeId)}</p>
    <p>${f:h(timeZoneName)}</p>
    <p>${f:h(offset)}</p>
</body>
</html>

表示結果

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムゾーン</title>
</head>
<body>
    <p>UTC</p>
    <p>協定世界時</p>
    <p>0</p>
</body>
</html>

コントローラーでローカルのタイムゾーンを設定した場合。

public class TimezoneController extends Controller {
    @Override
    public Navigation run() throws Exception {
        TimeZone tz = TimeZone.getTimeZone("Asia/Tokyo");
        requestScope("timeId", tz.getID());
        requestScope("timeZoneName", tz.getDisplayName());
        requestScope("offset", tz.getRawOffset());
        return forward("timezone.jsp");
    }
}

実行結果。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムゾーン</title>
</head>
<body>
    <p>Asia/Tokyo</p>
    <p>日本標準時</p>
    <p>32400000</p>
</body>
</html>

FrontController のタイムゾーンの挙動と org.slim3.util.DateUtil 利用後のタイムゾーン

TimeZone.getTimeZone("Asia/Tokyo") の場合

public class TimezoneController extends Controller {
    @Override
    public Navigation run() throws Exception {
        TimeZone tz = TimeZone.getTimeZone("Asia/Tokyo");
        Calendar cal = Calendar.getInstance(tz);
        String datePattern = "yyyy-MM-dd HH:mm:ss z";
        requestScope("timeId", tz.getID());
        requestScope("timeZoneName", tz.getDisplayName());
        requestScope("offset", tz.getRawOffset());
        requestScope("calendarDateTime", cal.getTime());
        requestScope("dateDateTime", new Date());
        requestScope("dateUtilzedDateTimeOfCalendar", DateUtil.toString(cal.getTime(), datePattern));
        requestScope("dateUtilzedDateTimeOfDate", DateUtil.toString(new Date(), datePattern));
    }
}

実行結果。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムゾーン</title>
</head>
<body>
    <p>Asia/Tokyo</p>
    <p>日本標準時</p>
    <p>32400000</p>
    <!-- Calendar から作成した日時 -->
    <p>Fri Jul 22 04:42:53 UTC 2011</p>
    <!-- Date を利用した日付 -->
    <p>Fri Jul 22 04:42:53 UTC 2011</p>
    <!-- org.slim3.util.DateUtil を利用した Calendar の日付 -->
    <p>2011-07-22 13:42:53 JST</p>
    <!-- org.slim3.util.DateUtil を利用した Date の日付 -->
    <p>2011-07-22 13:42:53 JST</p>
</body>
</html>

TimeZoneLocator.get() の場合。

public class TimezoneController extends Controller {
    @Override
    public Navigation run() throws Exception {
        TimeZone tz = TimeZoneLocator.get();
        Calendar cal = Calendar.getInstance(tz);
        String datePattern = "yyyy-MM-dd HH:mm:ss z";
        requestScope("timeId", tz.getID());
        requestScope("timeZoneName", tz.getDisplayName());
        requestScope("offset", tz.getRawOffset());
        requestScope("calendarDateTime", cal.getTime());
        requestScope("dateDateTime", new Date());
        requestScope("dateUtilzedDateTimeOfCalendar", DateUtil.toString(cal.getTime(), datePattern));
        requestScope("dateUtilzedDateTimeOfDate", DateUtil.toString(new Date(), datePattern));
    }
}

実行結果。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムゾーン</title>
</head>
<body>
    <p>Asia/Tokyo</p>
    <p>日本標準時</p>
    <p>32400000</p>
    <!-- Calendar から作成した日時 -->
    <p>Fri Jul 22 04:46:15 UTC 2011</p>
    <!-- Date を利用した日付 -->
    <p>Fri Jul 22 04:46:15 UTC 2011</p>
    <!-- org.slim3.util.DateUtil を利用した Calendar の日付 -->
    <p>2011-07-22 13:46:15 JST</p>
    <!-- org.slim3.util.DateUtil を利用した Date の日付 -->
    <p>2011-07-22 13:46:15 JST</p>
</body>
</html>

Oh...
予想では、TimeZone.getTimeZone("Asia/Tokyo") と TimeZoneLocator.get() で明示して設定した cal.getTime() から日本時間(Fri Jul 22 13:46:15 JST 2011)が帰ってくると思っていたが、UTC の時間が表示されている。
また、TimeZone.getTimeZone("Asia/Tokyo") と TimeZoneLocator.get() で明示して設定した後に、

        TimeZone tz = TimeZone.getTimeZone("Asia/Tokyo");
        Calendar cal = Calendar.getInstance(tz);
        cal.setTimeZone(tz);
        cal.setTime(new Date());
        requestScope("calendarDateTime", cal.getTime());

とCalendar クラスにタイムゾーンを設定しても、

    <!-- Calendar から作成した日時 -->
    <p>Fri Jul 22 04:46:15 UTC 2011</p>
    <!-- Date を利用した日付 -->
    <p>Fri Jul 22 04:46:15 UTC 2011</p>

タイムゾーンは変わらなかった。

DateFormat クラスと SimpleDateFormat クラス

日付を Date クラスから出力する場合は、DateFormat クラスか SimpleDateFormat クラスを経由して任意のタイムゾーンの日付に変換する必要があるのが、(やっと)わかった。
上記の Controller の場合、

public class TimezoneController extends Controller {
    @Override
    public Navigation run() throws Exception {
        TimeZone tz = TimeZone.getTimeZone("Asia/Tokyo");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
        sdf.setTimeZone(tz);
        DateFormat df = DateFormat.getInstance();
        df.setTimeZone(tz);
        Calendar cal = Calendar.getInstance();
        String datePattern = "yyyy-MM-dd HH:mm:ss z";
        requestScope("timeId", tz.getID());
        requestScope("timeZoneName", tz.getDisplayName());
        requestScope("offset", tz.getRawOffset());
        requestScope("calendarDateTime", cal.getTime());
        requestScope("dateFormat", df.format(cal.getTime()));
        requestScope("simpleDateFormat", sdf.format(cal.getTime()));
        requestScope("dateUtilzedDateTimeOfCalendar", DateUtil.toString(cal.getTime(), datePattern));
        requestScope("dateUtilzedDateTimeOfDate", DateUtil.toString(new Date(), datePattern));
    }
}

そして JSP は、

<%@page pageEncoding="UTF-8" isELIgnored="false" session="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="f" uri="http://www.slim3.org/functions"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムゾーン</title>
</head>
<body>
    <p>${f:h(timeId)}</p>
    <p>${f:h(timeZoneName)}</p>
    <p>${f:h(offset)}</p>
    <!-- Calendar から作成した日時 -->
    <p>${f:h(calendarDateTime)}</p>
    <!-- DateFormat を利用した日付 -->
    <p>${f:h(dateFormat)}</p>
    <!-- SimpeDateFormat を利用した日付 -->
    <p>${f:h(simpleDateFormat)}</p>
    <!-- org.slim3.util.DateUtil を利用した Calendar の日付 -->
    <p>${f:h(dateUtilzedDateTimeOfCalendar)}</p>
    <!-- org.slim3.util.DateUtil を利用した Date の日付 -->
    <p>${f:h(dateUtilzedDateTimeOfDate)}</p>
</body>
</html>

そして結果は、

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイムスタンプ</title>
</head>
<body>
    <p>Asia/Tokyo</p>
    <p>日本標準時</p>
    <p>32400000</p>
    <!-- Calendar から作成した日時 -->
    <p>Fri Jul 22 07:28:40 UTC 2011</p>
    <!-- DateFormat を利用した日付 -->
    <p>11/07/22 16:28</p>
    <!-- SimpeDateFormat を利用した日付 -->
    <p>2011-07-22 16:28:40 JST</p>
    <!-- org.slim3.util.DateUtil を利用した Calendar の日付 -->
    <p>2011-07-22 16:28:40 JST</p>
    <!-- org.slim3.util.DateUtil を利用した Date の日付 -->
    <p>2011-07-22 16:28:40 JST</p>
</body>
</html>

これで意図通りの結果が得られるようになった…。

Slim3 で JSON を利用する

Model での記述例。

基本的には JSON を利用するのに既存の Model をそのまま使える。
しかし、JSON アノテーションで Model が保持するエンティティ内のプロパティを操作したい場合は、下記のように「org.slim3.datastore.json.Json」が必要になる。

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;
// JSON アノテーションを利用する場合に import する
import org.slim3.datastore.json.Json;
@Model(schemaVersion = 1)
public class Article implements Serializable {

    private static final long serialVersionUID = 1L;
    // Key を JSON に出力しない
    @Attribute(primaryKey = true, json=@Json(ignore=true))
    private Key key;
    // Entity のバージョン情報も JSON に出力しない
    @Attribute(version = true, json=@Json(ignore=true))
    private Long version;

Service での記述例

public class PostService {
    private PostMeta meta = new PostMeta();

    /* 投稿日時から降順で10件の投稿を取得する既存のメソッド */
    public List<Post> getLatestPostIn10() {
        return Datastore.query(meta).sort(meta.publishedDate.desc).offset(0).limit(10).asList();
    }

    /* 追記した JSON 変換用メソッド */
    public String getLatestPostIn10AsJson() {
        List<Post> posts = getLatestPostIn10();
        // List 型の結果をオブジェクト型の配列に変換する
        Object[] entityArray = posts.toArray();
        // 「modelsToJson」と「modelToJson」の違いに注意
        // 配列を JSON に変換する場合「models」と複数形になっている
        return ArticleMeta.get().modelsToJson(entityArray);
    }

Controller の記述例

package com.example.www.controller.api.json.posts;

import com.example.www.service.PostService;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

public class LatestController extends Controller {

    private PostService service = new PostService();

    @Override
    public Navigation run() throws Exception {
        String json = service.getLatestPostIn10AsJson();
        requestScope("json", json);
        // 単なる JSON ではなく JSONP として利用したいので、JSP 有の Controller にしている
        return forward("latest.jsp");
    }
}

JSP の記述例

<%@page contentType="application/json;charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" session="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="f" uri="http://www.slim3.org/functions"%>
callback(${json});

任意の日のタイムスタンプの範囲を取得する

結局使わなかったけれど。
日付のパースには Apache のライブラリを利用。

import java.util.Date;
import org.apache.commons.lang.time.DateUtils;

public class MySample {
    private Date _startDatetime;
    private Date _endDatetime;

    private void setTimestampInDay() throws Exception {
        Date now = new Date();
        String today = DateUtil.toString(now, "yyyy-MM-dd");
        _startDatetime = DateUtils.parseDate(today + " 00:00:00", new String[]{datetimePattern});
        _endDatetime   = DateUtils.parseDate(today + " 23:59:59", new String[]{datetimePattern});

        Long startTimestamp = _startDatetime.getTime();
        Long endTimestamp = _endDatetime.getTime();
    }
}

Slim3 の application_ja.properties に日本語を書くために UTF-16 LE を16進数で表現する

Slim3 の application_ja.properties にマルチバイトを記述するには、文字列を UTF-16LE に変換して16進数でダンプする必要があることに気が付いた。
この方法を Java でやる方法がわからなかったので、Perl で書いてみた。

#!/usr/bin/perl
use strict;
use warnings;
use Encode;
use utf8;

my $unicodes;
my $string = "タイムスタンプ";
my @chars = split '', $string;
foreach my $char (@chars) {
    $unicodes .= '\\u' . unpack('H*', encode('utf-16-le', decode_utf8($char)));
}
# BOM は付加していない
# 付加する場合は、\ufffe を文字列の先頭に追加する
# \ubf30\ua430\ue030\ub930\ubf30\uf330\ud730 が出力される
print $unicodes;

UTF-16 にはリトルエンディアンの UTF-16 LE とビッグエンディアンUTF-16 BE がある。application_ja.properties の場合は、UTF-16 LE を使うので、BOM には(16進数の) fffe を使う。
UTF-16 LE か UTF-16 BE の区別をするには、文字列の先頭に追加される BOM が fffe の場合は UTF-16 LEであり、feff の場合はUTF-16 BE になる。

参考

正直、pack/unpack 関数には苦手意識があり、上記のスクリプトももっとスマートに書けるだろうし、Text Escaping and Unescaping in JavaScript のように自分でページを用意した方が楽だと思う。

Slim3 の BeanUtil で特定の値を処理対象外にしたい場合

例えば、渡された値が一意でその値を Key として利用したい場合や、タイムスタンプを Java で利用できるミリ秒単位に変換したい場合などで、BeanUtil で処理をする前に一度別の処理を行いたいときに使う。

public class ItemService {
    private ItemMeta a = new ItemMeta();

    public Item post(Map<String, Object> input) {
        Item Item = new Item();
        // exclude に処理対象外にしたい変数名を記入する
        // See at http://slim3.googlecode.com/svn/trunk/slim3/javadoc/org/slim3/util/CopyOptions.html
        BeanUtil.copy(input, Item, new CopyOptions().exclude("jan", "name"));
        key key = Datastore.createKey(Item.class, input.get("jan").toString());
        Item.setKey(key);
        ...

Slim3 をローカルで起動するとエラーが発生する

エラー内容は下記の通り。

2011/06/21 3:03:50 com.google.apphosting.utils.jetty.JettyLogger info
情報: Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLogger
2011/06/21 3:03:50 com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml
致命的: Received exception processing F:\workspace\MyApp\war\WEB-INF/appengine-web.xml
com.google.apphosting.utils.config.AppEngineConfigException: Unrecognized element <threadsafe>

原因は「com.google.apphosting.utils.config.AppEngineConfigException: Unrecognized element 」と書かれている通りで、「WEB-INF/appengine-web.xml」内に記述されている「」要素をコメントしたら、正常に起動した。

    </system-properties>
    <sessions-enabled>false</sessions-enabled>
    <!-- <threadsafe>true</threadsafe> -->
</appengine-web-app>