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 の取り扱いについて
- FrontController では、タイムゾーンは UTC に設定されている。
- TestCase から FrontController で設定されているタイムゾーンはローカル時間。
この場合、自分は日本時間に設定しているので、JST で設定されている。 - TestCase で使う Controller のタイムゾーンはローカル時間。
この場合も JST で設定されている。しかし、org.slim3.util.DateUtil 利用した際のタイムゾーンは UTC になる。 - 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 になる。
参考
- Unicode の文字列をエスケープする JavaScript
- Text Escaping and Unescaping in JavaScript
- Unicode の文字列をソースコードに埋め込む方法
- 2進数、8進数、16進数での数値表現
- PerlでUTF-16LE + BOM
正直、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
</system-properties> <sessions-enabled>false</sessions-enabled> <!-- <threadsafe>true</threadsafe> --> </appengine-web-app>