RestletでJSONを扱う
GAE/JでRestlet(server-side)を使い、JSONをやり取りするようにしてみました。使用したRestletのバージョンは、Version 2.1 Snapshot (testing)です。
jarファイル
RestletにはGAE/J版が用意されているので、それをダウンロードし、配備します。配布されているアーカイブには、Restlet本体の他にorg.restlet.ext.*パッケージが含まれているのですが、今回はその中から以下のjarファイルを使用しています。
web.xml
ServletはRestletのServerServletを使うようにします。RestletにはApplicationのサブクラスを作ってルーティングを設定し、各ServerResourceを呼び出すようにするのが基本的な流れになります。
<servlet> <servlet-name>RestletServlet</servlet-name> <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class> <init-param> <param-name>org.restlet.application</param-name> <param-value>net.wrap_trap.lifecycle.rest.LifecycleRestApplication</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>RestletServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
Application
ルーティング等を設定します。
public class LifecycleRestApplication extends Application { @Override public Restlet createInboundRoot() { Router router = new Router(getContext()); getConnectorService().getClientProtocols().add(Protocol.FILE); Directory dir = new Directory(getContext(), LocalReference.createFileReference(new File("war/"))); router.attachDefault(dir); router.attach("/events/{year}/{month}/{day}", DailyLogResource.class); return router; } }
上記コードは以下の2点を設定しています。
- ルーティングのデフォルト(どのルーティング定義にも合致しない場合)の振る舞いを設定
- 例えば、http://example.com/index.htmlというURIが指定された場合、($PROJECT_ROOT)/war/index.htmlの内容がレスポンスに設定される
- リクエストURIが「/events/yyyy/MM/dd」であれば、DailyLogResourceにルーティングする
- URIに指定された{year},{month},{day}の各値は、UniformResource#getRequestAttributesで取得することができる
ServerResource
ServerResourceのサブクラスを作成し、任意のリソースを管理します。今回は以下の振る舞いを行うServerResourceを作成しました。
- GET
- PUT
public class DailyLogResource extends ServerResource { public DailyLogResource() { Set<Method> allowedMethods = new HashSet<Method>(); allowedMethods.add(Method.GET); allowedMethods.add(Method.PUT); setAllowedMethods(allowedMethods); } @Get @Transaction public JsonRepresentation getDailyLog(){ Date date = getDate(); Collection<TimeLog> timeLogList = null; DailyLog dailyLog = DailyLog.getDailyLog(date); if(dailyLog != null && dailyLog.getTimeLogs() != null && dailyLog.getTimeLogs().size() > 0){ timeLogList = DailyLog.detachCopyAll(dailyLog.getTimeLogs()); }else{ timeLogList = Collections.EMPTY_LIST; } JSONArray jsonArray = new JSONArray(); for(TimeLog timeLog : timeLogList){ jsonArray.put(timeLog.toJSON()); } return new JsonRepresentation(jsonArray); } @Put("json") @Transaction public JsonRepresentation saveOrUpdateDailyLog(Representation rep) throws JSONException, IOException{ JsonRepresentation jsonRep = new JsonRepresentation(rep.getText()); Date date = getDate(); DailyLog dailyLog = DailyLog.getDailyLog(date); if(dailyLog == null){ dailyLog = DailyLog.createDailyLog(date); dailyLog.save(); } List<TimeLog> timeLogs = dailyLog.getTimeLogs(); JSONArray jsonArray = jsonRep.getJsonArray(); for(int i = 0; i < jsonArray.length(); i++){ JSONObject calEvent = jsonArray.getJSONObject(i); TimeLog timeLog = null; if(calEvent.isNull("encodedKey")){ // insert timeLog = new TimeLog(); timeLog.setDailyLog(dailyLog); timeLogs.add(timeLog); }else{ // update timeLog = TimeLog.getObjectById(TimeLog.class, calEvent.get("encodedKey")); } timeLog.setStart(calEvent.getLong("startLong")); timeLog.setEnd(calEvent.getLong("endLong")); timeLog.setName(calEvent.getString("title")); timeLog.setOrder(0); timeLog.save(); calEvent.put("encodedKey", timeLog.getEncodedKey()); } return new JsonRepresentation(jsonArray); } protected Date getDate(){ Map<String, Object> attrMap = getRequestAttributes(); String year = (String)attrMap.get("year"); String month = (String)attrMap.get("month"); String day = (String)attrMap.get("day"); Calendar cal = Calendar.getInstance(); cal.set(Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); return cal.getTime(); } }
ServerResourceのサブクラスのインスタンスはリクエスト毎に作成されるので、インスタンスフィールドを使うことができます。GET/PUTに関わらず、クライアントにデータを返す場合は、各メソッドの戻り値をRepresentationにします。本来であればMediaTypeを見て適切なRepresentationのサブクラスを選ぶのが正しいのですが、今回はJSONしか扱わないので所々でJSON決め打ちのコードとなっています。
ちょっと惜しいのが、JsonRepresentationを生成する際に複数のオブジェクトが格納されたListを指定すると、JSONとして出力されるのはListのプロパティ(empty)のみになってしまうこと。今回はListに対応するJSONArrayを作成し、Listに格納されているJavaオブジェクトを自前でJSONObjectに変換してから詰めるようにすることで、JSONで返せるようにしています。RestletでガッツリJSONを扱うのであれば、指定したJavaオブジェクトを再帰的にJSONObjectに変換してくれるようなRepresentationのサブクラスを作った方が良いでしょう。
まとめ
RestletでJSONを扱ってみました。Restletは思ったよりも情報が少なく、目的どおりに動かすのが少し難しいかもしれません。しかし、ルーティングの設定やRestletの導入自体は簡単なので、一般的なWebアプリケーションで使うというのも面白いと思います。