メモ代わり。てきとーに。 いや、ですからてきとーですって。 2年前ぐらいにPythonあたりでメールくれた方、ごめんなさい。メール紛失してしまい無視した形になってしまいました。。。

2009年7月25日土曜日

[OpenSocial][その他] gooホームでディベロッパー申請承認された

数日かかるものかと思いきや、ほぼ1日で承認いただけた

とりあえず、ほっとしたー。

.

[Apache Shindig][お勉強][OpenSocial] メモ92 shindig開発のメーリングリストのアーカイブ

http://www.mail-archive.com/shindig-dev@incubator.apache.org/

.

[Apache Shindig][お勉強][OpenSocial] メモ91 gadgets.rpc(3)

JavaScript難しい・・。

features/rpc/rpc.jsをざっと眺めてみた。

あっているかどうかしらないけど、多分流れ的にはこんな感じだと思う。

gadgets.rpcの流れ
1) gadgets.rpc.callが呼ばれる
2) gadgets.rpc.callの中で、postMessageなるものを使って別ドメインのiframeな感じのJavaScriptへメッセージ送信
3) rpc_relay.htm内でgadgets.rpc.receiveをコールし、postMessageで送信されたメッセージを受信し、該当コールバックを実行。
メッセージの内容はJSON形式の文字列で、


s: Service Name
f: From
c: The callback ID or 0 if none.
a: The arguments for this RPC call.
t: The authentication token.

といった内容。


gadgets.rpc.receive
postMessageで送られてくるJSONを元に、実際に処理をコールするところっぽい。
やっていることは以下の感じ。
1) securityTokenの値をチェック。だめならException
2) AsyncなRPCのためにcallbackをrpcコンテキストにくっつけておく
3) すでに登録されているサービスを実行
4) コールバックをコール。(gadgets.rpc.call経由で)

多分こんな感じ。
すでに登録されているサービスというのは、
gadgets.rpc.registerメソッドで登録される。
gadgets.rpc.registerの定義は、

register: function(serviceName, handler) {
if (serviceName === CALLBACK_NAME) {
throw new Error("Cannot overwrite callback service");
}

if (serviceName === DEFAULT_NAME) {
throw new Error("Cannot overwrite default service: use registerDefault");
}

services[serviceName] = handler;
},

となっていて、registerがコールされるとservicesメンバ変数(呼び方がわからん!)に
function(上記だとhandlerがそれにあたる)が保存される。




ところで、

===

って'='が3つ続いているけど、これは何????


--
大体分かったつもりだけど、どうしてrequestSendMessageはgadgets.rpc経由にするんだろう。。
そんな必要ない気がするんだけど。。

requestSendMessage should use the RPC mechanism as similar features do

って言ってる。。

ってことは、親なり別frameなりでダイアログでも表示させるってことかしらー。

--
ははぁーん。

What we want to do is just send a message as soon as a gadget asks us with
the requestSendMessage method.

という投稿発見。
それに対して、

I think, though, the default implementation should really be to send
an RPC to the containing page

だって。

これでRPC経由になったのかしら。
ところで、何て仰っているのかな?


.

[Apache Shindig][お勉強][OpenSocial] メモ90 gadgets.rpc(2)

gadgets.rpcの本体は、


features/rpc/rpc.js

にて定義されている模様。

そんだけ。
.

[その他] 死語ランキング

http://ranking.goo.ne.jp/ranking/999/dead_language/

うそピョーン

.

[その他] 週間新記録

週間POST数新記録。

.

[その他] フレームワーク思考がはやっているんですか?

徘徊しているとフレームワーク思考なるものを見かける。

はやっているのかなー。

みっしーとか7つの習慣とかですかね。



ここは、みっしーとは程遠いブログということぐらいは知っているよ、多分。

.

[Apache Shindig][お勉強][OpenSocial] メモ89 gadgets.rpc(1)

gadgets.rpcを理解していない。。
ので、ちゃんとコード読んでみようっと。

requestSendMessageに限らず、requestNavicateToもgadgets.rpc.callって呼ばれているのよねー。

なんだろかー。

.

[Apache Shindig][お勉強][OpenSocial] メモ88 opensocial.requestSendMessageするガジェット

とりあえず、簡単なガジェットを作成し、Shindig上で実行してみる。

作成したガジェットは以下のとおり。


<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="opensocial.Message">
<Require feature="opensocial-0.8" />
</ModulePrefs>
<Content type="html" view="home,profile,canvas">
<![CDATA[
<script type="text/javascript">
function sendMessage() {
var opt_params = [];
opt_params[opensocial.Message.Field.TITLE] = 'タイトルだよーん';
opt_params[opensocial.Message.Field.TYPE] = opensocial.Message.Type.PRIVATE_MESSAGE;
var body = 'テストメッセージだよーん';
var msg = opensocial.newMessage(body, opt_params);
opensocial.requestSendMessage(opensocial.IdSpec.PersonId.OWNER, msg, call_back);
}

function call_back(status) {
if (status.hadError()) {
document.getElementById('result').innerHTML = '失敗したよ:' + status.getErrorCode();
} else {
document.getElementById('result').innerHTML = '送ったよ';
}
}

</script>
<div id="result"></div>
<input type="button" value="メッセージ送信" onclick="sendMessage();" /><br />
]]>
</Content>
</Module>



でMy Shindig環境で表示させて、メッセージ送信ボタンを押下してみる。

すると、ありゃ?

opensocial is not defined
(?)()()gadgets.js (line 244)
[Break on this error] opt_callback(new opensocial.ResponseItem(

とFirebugのコンソールに表示されて動かない。

Shindigのサンプル実装では実装されていないのか、
それともMy Shindig環境がおかしいのかは、
これから調べる。

.

[Apache Shindig][お勉強][OpenSocial] メモ87 opensocial.Message

opensocial.Message周りについて実装しながら理解する。

多分友達やある特定ユーザに対してメッセージを送るような仕組みを提供するもの
ではないか、と思う。

さて、
opensocial.Messageそのもののサーバ側の仕組みを作る。
多分普通は既存のSNSなんかを使用するのだろうが、面白くないので、
最初から実装する。

opensocial.Messageの項目

Google Codeのopensocial 0.8.1のドキュメントを見ると、


<static> object BODY
メッセージのメイン テキストです。
<static> object BODY_ID
メッセージ テンプレートとしてのメッセージのメイン テキストです。
<static> object TITLE
メッセージのタイトルです。
<static> object TITLE_ID
メッセージ テンプレートとしてのメッセージのタイトルです。
<static> object TYPE
メッセージのタイトルです。opensocial.Message.Type として指定します。

なんてなふうに書かれている。

opensocial.requestSendMessageのAPIリファレンスなんかを見ながら、
必要そうなテーブルを作ってみた。

CREATE TABLE gms_message (
id VARCHAR(64) not null,
from_person_id VARCHAR(64) not null,
to_person_id VARCHAR(64) not null,
body text not null,
body_id VARCHAR(256),
title text not null,
title_id VARCHAR(256),
type VARCHAR(32) not null, /* EMAIL, NOTIFICATION, PRIVATE_MESSAGE, PUBLIC_MESSAGE */
created_at DATETIME not null,
PRIMARY KEY(id)
)
TYPE=innoDB;


良い悪いは別として、大体こんな感じですかねー。

DAOも用意して、準備完了。

さて、次からrequestSendMessageあたりから実装してみる。
Shindigデフォルトで動作するならそのまま。

.

[myminicity] int使い切った?

myminicityに行ってみたら、MySQLのエラー。
思いっきりint使い切ったって感じのエラー。

うーむ。
.

[YUI][JavaScript] やべ

YUI使ってdialog出して見た。

かっこいいー

今さらだけど。


My Shindig コンテナで、requestPermissionのときとか、sendMessageのときとかに
使おうっと。
.

[YUI][JavaScript] YUIダウンロード中

YUIをダウンロード中。

GoogleにしろYahooにしろ、すごいねー。

そんだけ。

--
ダウンロード終わった。
何をすればよいのやら。。
.

[OpenSocial][その他]とりあえず、gooホームでディベロッパー申請してきた

これから審査するんだそうで。

審査きらい。


mixiは、ユーザ名、パスワードともに忘れた・・。
.

[Apache Shindig][お勉強][OpenSocial] メモ86 DataRequest.newFetchActivitiesRequestのサーバ側実装をしてみる(4)

DataRequest.newFetchActivitiesRequestのactivityId指定しない版できた。

まず、GadgetXMLのJavaScriptは、


function getData() {
/*===================================================================*/
/* OWNER IdSpec */
/*===================================================================*/
var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.OWNER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);
/*===================================================================*/
/* DataRequestオブジェクトを作成し、 */
/* そこにActivity取得リクエストを追加. */
/* レスポンスを取得するときのキーは'get_activity' */
/*===================================================================*/

var req = opensocial.newDataRequest();
req.add(req.newFetchActivitiesRequest(idspec), 'get_activity');
/*===================================================================*/
/* 送信! */
/*===================================================================*/
req.send(function(response){
var activity = response.get('get_activity')
if (activity.hadError()) {
document.getElementById('result').innerHTML
= 'エラーだったよ:' + activity.getErrorMessage();
return;
}
var out = document.createElement('ul');
if (activity.getData().size() == 0) {
var li = document.createElement('li');
li.innerHTML = 'Activity0件';
out.appendChild(li);
}
activity.getData().each(function(act) {
var li = document.createElement('li');
var title = act.getField(opensocial.Activity.Field.TITLE);
var media = act.getField(opensocial.Activity.Field.MEDIA_ITEMS);
if (media != undefined) {
for (var ii=0, len = media.length; ii < len; ii++) {
if (media[ii].getField(opensocial.MediaItem.Field.TYPE) == opensocial.MediaItem.Type.IMAGE) {
title +=
'<br /><img src="'
+ media[ii].getField(opensocial.MediaItem.Field.URL);
+ '/>';
}
}
}
li.innerHTML = title;
out.appendChild(li);
});

document.getElementById('result').appendChild(out);

});
}


と、こんな感じ。
newFetchActivitiesRequestの第二引数はactivityIdを指定しないので、必要なし。

で、このリクエストに対応するサーバ側の処理は、
まずAcitivityServiceのjava、

public Future<RestfulCollection<Activity>> getActivities(Set<UserId> userIds,
GroupId groupId, String appId, Set<String> fields, CollectionOptions options, SecurityToken token)
throws ProtocolException {
return ImmediateFuture.newInstance(
new RestfulCollection<Activity>(logic.getActivities(userIds,groupId,appId,fields,options,token)));

}


な感じ。
とりあえずパラメータはそのままlogicの方に渡す。
で、そのlogic。

public List<Activity> getActivities(Set<UserId> userIds, GroupId groupId, String appId, Set<String> fields, CollectionOptions options, SecurityToken token)
throws ProtocolException {
GmsPerson viewer = getUser();
List<Activity> activities = new ArrayList<Activity>();
logger.info("アクティビティ取得(userId複数指定)開始:");
if (viewer == null) {
if (logger.isDebugEnabled()) {
logger.debug("Guestは取得できない:");
}
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestのため、取得できない:loginid:[" + viewer.getLoginId() + "]");
}
if (! isInstalledGadget(viewer, token.getModuleId())) {
if (logger.isDebugEnabled()) {
logger.debug("ガジェット未インストール:"
+ "viewerId:[" + viewer.getLoginId() + "]:"
+ "ガジェットID:[" + token.getModuleId() + "]"
);
}
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"未インストールのため、取得できない:loginid:[" + viewer.getLoginId() + "]");
}
List<String> userLoginIds = getUserList(userIds, token);
ExtendedGmsPerson[] gmsPersons = null;
List<String> personIds = new ArrayList<String>();
int first = 0;
int max = 20;
for (;;) {
gmsPersons = gmsPersonDao.peopleGet(
userLoginIds,
groupId.getType().toString(),
groupId.getGroupId(),
null,
null,
null,
null,
null,
first,
max,
null);
if (logger.isDebugEnabled()) {
logger.debug("count:[" + gmsPersons.length + "]");
}
int length = gmsPersons.length;
for (int ii=0; ii<length; ii++) {
personIds.add(gmsPersons[ii].getId());
}
if (length < max) {
break;
}
first += max;
}
ExtendedGmsActivity[] gmsActivities = gmsActivityDao.selectByGmsPersonIds(personIds, options.getFirst(), options.getMax());
for (int ii=0,len = gmsActivities.length; ii<len; ii++) {
activities.add(mapToActivity(gmsActivities[ii], gmsActivities[ii].getGmsPerson().getLoginId()));
}
logger.info("アクティビティ取得(userId複数指定)終了:");
return activities;
}


大したことはしていない。
まずパーミッションをチェックして、
userIdとgroupIdからUserIdのリストを取得と。
そしたら、そのUserIdとひもづくactivityを取得する。

そんだけ。

.

2009年7月24日金曜日

[Apache Shindig][お勉強][OpenSocial] メモ85 DataRequest.newFetchActivitiesRequestのサーバ側実装をしてみる(3)

複数件activityIdを指定したnewFetchActivitiesRequestできた。

GadgetXMLで、表示のところのJavaScriptで間違えがあったので、ちょっとだけ修正。
修正したJavaScriptは、以下な感じ。


function getTwoData() {
/*===================================================================*/
/* OWNER IdSpec */
/*===================================================================*/
var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.OWNER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);
var params = {};
params['activityId'] = [
'tjt0tt30k9f0hje01eu0emq0zr505bd0',
'w920slc0ze20nmf0diq0cnk0mj71kbz0'
];

/*===================================================================*/
/* DataRequestオブジェクトを作成し、 */
/* そこにActivity取得リクエストを追加. */
/* レスポンスを取得するときのキーは'get_activity' */
/*===================================================================*/

var req = opensocial.newDataRequest();
req.add(req.newFetchActivitiesRequest(idspec, params), 'get_activity');
/*===================================================================*/
/* 送信! */
/*===================================================================*/
req.send(function(response){
var activity = response.get('get_activity')
if (activity.hadError()) {
document.getElementById('result').innerHTML
= 'エラーだったよ:' + activity.getErrorMessage();
return;
}
var out = document.createElement('ul');
if (activity.getData().size() == 0) {
var li = document.createElement('li');
li.innerHTML = 'Activity0件';
out.appendChild(li);
}
activity.getData().each(function(act) {
var li = document.createElement('li');
var title = act.getField(opensocial.Activity.Field.TITLE);
var media = act.getField(opensocial.Activity.Field.MEDIA_ITEMS);
if (media != undefined) {
for (var ii=0, len = media.length; ii < len; ii++) {
if (media[ii].getField(opensocial.MediaItem.Field.TYPE) == opensocial.MediaItem.Type.IMAGE) {
title +=
'<br /><img src="'
+ media[ii].getField(opensocial.MediaItem.Field.URL);
+ '/>';
}
}
}
li.innerHTML = title;
out.appendChild(li);
});

document.getElementById('result').appendChild(out);

});
}



で、jsonrpccontainer.jsは修正済みのものを使用っと。

で、メインのサーバ側ActivityServiceのgetActivities(activityId複数版)。

public Future<RestfulCollection<Activity>> getActivities(UserId userId, GroupId groupId,
String appId, Set<String> fields, CollectionOptions options, Set<String> activityIds, SecurityToken token)
throws ProtocolException {
if (logger.isDebugEnabled()) {
logger.debug("userId:[" + userId + "]:");
logger.debug("groupId:[" + groupId + "]:");
logger.debug("appId:[" + appId + "]:");
for (String field: fields) {
logger.debug("field:[" + field + "]");
}
logger.debug("options:sortBy:[" + options.getSortBy() + "]");
logger.debug("options:sortOrder:[" + options.getSortOrder() + "]");
logger.debug("options:filter:[" + options.getFilter() + "]");
logger.debug("options:filterOperation:[" + options.getFilterOperation() + "]");
logger.debug("options:filterValue:[" + options.getFilterValue() + "]");
logger.debug("options:first:[" + options.getFirst() + "]");
logger.debug("options:max:[" + options.getMax() + "]");
logger.debug("options:updatedSince:[" + options.getUpdatedSince() + "]");
for (String activityId: activityIds) {
logger.debug("activityId:[" + activityId + "]");
}
}
return ImmediateFuture.newInstance(
new RestfulCollection<Activity>(
logic.getActivities(userId, activityIds, token, options.getFirst(), options.getMax())));
}


logic.getActivitiesは、userIdとactivityIdsをキーにテーブルからデータを持ってくるだけ。


そんだけ。

次は最後のactivityIds指定なし版のnewFetchActivitiesRequestのサーバ側実装をやってみる。

.

[Apache Shindig][お勉強][OpenSocial] メモ84 DataRequest.newFetchActivitiesRequestのサーバ側実装をしてみる(3)

次は、複数activityId指定で、newFetchActivitiesRequestしてみる。

対応するActivityServiceのメソッドは、


public Future<RestfulCollection<Activity>> getActivities(UserId userId, GroupId groupId,
String appId, Set<String> fields, CollectionOptions options, Set<String> activityIds, SecurityToken token)
throws ProtocolException;

これ。

GadgetのXMLに書くJavaScriptは、

function getTwoData() {
/*===================================================================*/
/* OWNER IdSpec */
/*===================================================================*/
var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.OWNER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);
var params = {};
params['activityId'] = [
'tjt0tt30k9f0hje01eu0emq0zr505bd0',
'w920slc0ze20nmf0diq0cnk0mj71kbz0'
];

/*===================================================================*/
/* DataRequestオブジェクトを作成し、 */
/* そこにActivity取得リクエストを追加. */
/* レスポンスを取得するときのキーは'get_activity' */
/*===================================================================*/

var req = opensocial.newDataRequest();
req.add(req.newFetchActivitiesRequest(idspec, params), 'get_activity');
/*===================================================================*/
/* 送信! */
/*===================================================================*/
req.send(function(response){
var activity = response.get('get_activity')
if (activity.hadError()) {
document.getElementById('result').innerHTML
= 'エラーだったよ:' + activity.getErrorMessage();
return;
}
var out = document.createElement('ul');
var li = document.createElement('li');
if (activity.getData().size() == 0) {
li.innerHTML = 'Activity0件';
out.appendChild(li);
}
activity.getData().each(function(act) {
var title = act.getField(opensocial.Activity.Field.TITLE);
var media = act.getField(opensocial.Activity.Field.MEDIA_ITEMS);
if (media != undefined) {
for (var ii=0, len = media.length; ii < len; ii++) {
if (media[ii].getField(opensocial.MediaItem.Field.TYPE) == opensocial.MediaItem.Type.IMAGE) {
title +=
'<br /><img src="'
+ media[ii].getField(opensocial.MediaItem.Field.URL);
+ '/>';
}
}
}
li.innerHTML = title;
out.appendChild(li);
});

document.getElementById('result').appendChild(out);

});
}


こんな感じ。

params['activityId'] に今度は配列(というんですかね?)をセット。
jsonrpccontainer.jsのnewFetchActivitiesRequestもいい感じに修正し、
リクエストしてみると、、、

ちゃんとサーバ側に全てのパラメータがわたる。

わたっている様はこんな感じ。

[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - userId:[OWNER]:
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - groupId:[SELF]:
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - appId:[http://localhost/opensocial/hello.xml]:
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:sortBy:[topFriends]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:sortOrder:[ascending]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:filter:[null]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:filterOperation:[contains]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:filterValue:[]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:first:[0]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:max:[20]
[リクエスト:[20481986]] 2009-07-24 22:35:34,328 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - options:updatedSince:[null]
[リクエスト:[20481986]] 2009-07-24 22:35:34,329 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - activityId:[tjt0tt30k9f0hje01eu0emq0zr505bd0]
[リクエスト:[20481986]] 2009-07-24 22:35:34,329 DEBUG jp.qsdn.gms.social.service.ActivityServiceImpl - activityId:[w920slc0ze20nmf0diq0cnk0mj71kbz0]



ということで、次はgetActivitiesの中身を実装する。

.

[Apache Shindig][お勉強][OpenSocial] メモ83 DataRequest.newFetchActivitiesRequestのサーバ側実装をしてみる(2)

jsonrpccontainer.jsを元にコンテナを実装した場合、activityIdが渡せないので、


JsonRpcContainer.prototype.newFetchActivitiesRequest = function(idSpec,
opt_params) {
var rpc = { method : "activities.get" };
rpc.params = this.translateIdSpec(idSpec);
rpc.params.appId = "@app";
/* ここから */
if (opt_params['activityId']) {
rpc.params.activityId = opt_params['activityId'];
}
/* ここまで */
FieldTranslations.translateStandardArguments(opt_params, rpc.params);
FieldTranslations.translateNetworkDistance(idSpec, rpc.params);

return new JsonRpcRequestItem(rpc,
function(rawJson) {
/* さらに修正 ここから */
if (rawJson['list']) {
rawJson = rawJson['list'];
}
else {
rawJson = [rawJson];
}
/* さらに修正 ここまで */
var activities = [];
for (var i = 0; i < rawJson.length; i++) {
activities.push(new JsonActivity(rawJson[i]));
}
return new opensocial.Collection(activities);
});
};


とした。

んで、ActivityServiceの方は、

public Future<Activity> getActivity(UserId userId, GroupId groupId, String appId,
Set<String> fields, String activityId, SecurityToken token)
throws ProtocolException {
if (logger.isDebugEnabled()) {
logger.debug("userId:[" + userId + "]:");
logger.debug("groupId:[" + groupId + "]:");
logger.debug("appId:[" + appId + "]:");
for (String field: fields) {
logger.debug("field:[" + field + "]");
}
logger.debug("activityId:[" + activityId + "]");
}
Activity activity = logic.getActivity(userId, activityId, token);
if ( activity != null ) {
return ImmediateFuture.newInstance(activity);
}
throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST,"Cant find activity");
}


とした。
logic.getActivityは、ただ単にテーブルからactivityをひっぱってくるだけ。

一応これでactivityId指定の1件取得newFetchActivitiesRequest完成。

次はgetActivities()の2つ。
.

[Apache Shindig][お勉強][OpenSocial] メモ82 DataRequest.newFetchActivitiesRequest時のopt_paramに指定できるもの

newFetchActivitiesRequest発行時のサーバ側の処理は3のメソッドにそれぞれ分岐するところ
までは分かった。

さて、分岐させるべく、Gadget側でいろいろなパラメータでnewFetchActivitiesRequestをコールしてみたいよ。

OpenSocial0.8.1のAPIリファレンスを見ると、


Object newFetchActivitiesRequest(idSpec, opt_params)
サーバーからアクティビティ ストリームを要求するアイテムを作成します。

と書いてある。
他のnewFetchシリーズと同様に第二引数でいろいろと指定できそう。

このopt_paramsに何を指定することができるかってーと、

features/opensocial-reference/datarequest.js

を見てみると書いてあった。

opensocial.DataRequest.ActivityRequestFields = {
/**
* {String} If provided will filter all activities by this app Id.
* @private - at the moment you can only request activities for your own app
*/
APP_ID : 'appId',
/**
* When paginating, the index of the first item to fetch.
* Specified as a <code>Number</code>.
*
* @member opensocial.DataRequest.ActivityRequestFields
*/
FIRST : 'first',

/**
* The maximum number of items to fetch; defaults to 20. If set to a larger
* number, a container may honor the request, or may limit the number to a
* container-specified limit of at least 20.
* Specified as a <code>Number</code>.
*
* @member opensocial.DataRequest.ActivityRequestFields
*/
MAX : 'max'
};


だって。
maxとfirstとappIdと。


maxとfirstは指定しないと、firstには0が、maxには20がセットされる。

opensocial.DataRequest.prototype.newFetchActivitiesRequest = function(idSpec,
opt_params) {
opt_params = opt_params || {};

var fields = opensocial.DataRequest.ActivityRequestFields;

this.addDefaultParam(opt_params, fields.FIRST, 0);
this.addDefaultParam(opt_params, fields.MAX, 20);

return opensocial.Container.get().newFetchActivitiesRequest(idSpec,
opt_params);
};

ほら。


あれ?activityIdは???
activityIdはjsonrpccontainer.jsの方で指定しているのか、
と思いきや、書いてない。。
どうも、Shindigのサンプル実装だとactivityIdは渡せないっぽい。

ということで、features/opensocial-jsonrpc/jsonrpccontainer..jsを修正。

JsonRpcContainer.prototype.newFetchActivitiesRequest = function(idSpec,
opt_params) {
var rpc = { method : "activities.get" };
rpc.params = this.translateIdSpec(idSpec);
rpc.params.appId = "@app";
FieldTranslations.translateStandardArguments(opt_params, rpc.params);
FieldTranslations.translateNetworkDistance(idSpec, rpc.params);

return new JsonRpcRequestItem(rpc,
function(rawJson) {
rawJson = rawJson['list'];
var activities = [];
for (var i = 0; i < rawJson.length; i++) {
activities.push(new JsonActivity(rawJson[i]));
}
return new opensocial.Collection(activities);
});
};




JsonRpcContainer.prototype.newFetchActivitiesRequest = function(idSpec,
opt_params) {
var rpc = { method : "activities.get" };
rpc.params = this.translateIdSpec(idSpec);
rpc.params.appId = "@app";
/* ここから */
if (opt_params['activityId']) {
rpc.params.activityId = opt_params['activityId'];
}
/* ここまで */
FieldTranslations.translateStandardArguments(opt_params, rpc.params);
FieldTranslations.translateNetworkDistance(idSpec, rpc.params);

return new JsonRpcRequestItem(rpc,
function(rawJson) {
/* さらに修正 ここから */
if (rawJson['list']) {
rawJson = rawJson['list'];
}
else {
rawJson = [rawJson];
}
/* さらに修正 ここまで */
var activities = [];
for (var i = 0; i < rawJson.length; i++) {
activities.push(new JsonActivity(rawJson[i]));
}
return new opensocial.Collection(activities);
});
};


に修正。


さらに、GadgetXMLも

var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.OWNER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);
var params = {};
params['activityId'] = '6ng0wgx0lgb0nxc0t9t02ur03bw0hcx0';

/*===================================================================*/
/* DataRequestオブジェクトを作成し、 */
/* そこにActivity取得リクエストを追加. */
/* レスポンスを取得するときのキーは'get_activity' */
/*===================================================================*/

var req = opensocial.newDataRequest();
req.add(req.newFetchActivitiesRequest(idspec, params), 'get_activity');

として実行。

ばっちりサーバ側でactivityIdを受け取れる。
これでサーバ側3処理にそれぞれ処理を投げることができるようになった。

あと、サーバ側インタフェースを見ると、fieldsも指定できるようなシグネチャに
なっているんだけど、今のところ意図的に無視している。
なので、そこはそのまま。

さて、つづき。

--
レスポンスが返ってくるようになったけど、JsonRpcRequestItemの第二引数が
だめだったので追加で修正。

--
結局のところ、newFetchActivitiesRequestの第二引数で指定できるものは、
Shindigそのままのjsonrpccontainer.jsのサンプル実装だと、
* appId
* max
* first
サーバ側のサンプル実装だと、上記に加えて
* activityId
ぐらいか。な?




.

[Apache Shindig][お勉強][OpenSocial] メモ81 DataRequest.newFetchActivitiesRequestのサーバ側実装をしてみる(1)

DataRequest.newFetchActivitiesRequestに対応するサーバ側処理を実装してみる。

ActivityServiceインタフェースに定義されているメソッドを実装すればよいんだけど、
newFetchActivitiesRequestに対応しそうなメソッドが3つある。


Future<Activity> getActivity(UserId userId, GroupId groupId, String appId,
Set<String> fields, String activityId, SecurityToken token)
throws ProtocolException;
Future<RestfulCollection<Activity>> getActivities(UserId userId, GroupId groupId,
String appId, Set<String> fields, CollectionOptions options, Set<String> activityIds, SecurityToken token)
throws ProtocolException;
Future<RestfulCollection<Activity>> getActivities(Set<UserId> userIds,
GroupId groupId, String appId, Set<String> fields, CollectionOptions options, SecurityToken token)
throws ProtocolException;


の3つ。

最初のgetActivityはactivityId指定の一件のActivityを取得するメソッドっぽい。
で、次の2つは、、UserIdが複数指定できるどうかとactivityIdsが指定できるかどうか。

newFetchActivitiesRequestでのJSON-RPC自体にいろいろなバリエーションがありそう。
ということで、

./java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java

のActivityHandler.javaを見てみる。


@Operation(httpMethods="GET")
public Future<?> get(SocialRequestItem request)
throws ProtocolException {
Set<UserId> userIds = request.getUsers();
Set<String> optionalActivityIds = ImmutableSet.copyOf(request.getListParameter("activityId"));

CollectionOptions options = new CollectionOptions(request);

// Preconditions
HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
if (userIds.size() > 1 && !optionalActivityIds.isEmpty()) {
throw new IllegalArgumentException("Cannot fetch same activityIds for multiple userIds");
}

if (!optionalActivityIds.isEmpty()) {
if (optionalActivityIds.size() == 1) {
return service.getActivity(userIds.iterator().next(), request.getGroup(),
request.getAppId(), request.getFields(), optionalActivityIds.iterator().next(),
request.getToken());
} else {
return service.getActivities(userIds.iterator().next(), request.getGroup(),
request.getAppId(), request.getFields(), options, optionalActivityIds, request.getToken());
}
}

return service.getActivities(userIds, request.getGroup(),
request.getAppId(),
// TODO: add pagination and sorting support
// getSortBy(params), getFilterBy(params), getStartIndex(params), getCount(params),
request.getFields(), options, request.getToken());
}


activityIdパラメータがあるかどうか、あるなら1件かどうかで処理が分岐するらしい。

最後のreturn文にTODO:と書いてあるのが気になるが、、
これで実装できる、かな。

.

[Apache Shindig][お勉強][OpenSocial] メモ80 opensocial.requestCreateActivityのサーバ側実装をしてみる(3)

とりあえず、requestCreateActivityに対応するサーバ側処理完成。

くそソースだけど以下な感じ。


public void createActivity(UserId userId, GroupId groupId, String appId,
Set<String> fields, Activity activity, SecurityToken token) throws ProtocolException {
GmsPerson viewer = getUser();
logger.info("createActivity開始"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
if (viewer == null) {
logger.info("createActivity終了:Guestは保存できない:"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: 未ログイン");
}

String loginId = userId.getUserId(token);
if ("Guest".equalsIgnoreCase(loginId)) {
logger.info("createActivity終了:Guestは保存できない:"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: loginid:[" + loginId + "]");
}
List<String> userLoginIds = new ArrayList<String>();
userLoginIds.add(loginId);
GmsPerson[] gmsPersons = gmsPersonDao.peopleGet(
userLoginIds,
"self",
null,
null,
null,
null,
null,
null,
0,
1,
null);
if (gmsPersons.length == 0) {
logger.info("createActivity終了:該当Personなし:"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: loginid:[" + loginId + "]");
}
gmsPersonDao.select(gmsPersons[0].getId(), true);
GmsActivity target = mapToGmsActivity(gmsPersons[0], appId, activity, token);
if (supportsField(token.getContainer(), "activity", Activity.Field.MEDIA_ITEMS.toString()) && activity.getMediaItems() != null) {
for(MediaItem m : activity.getMediaItems()) {
GmsMediaItem gmsMediaItem = new GmsMediaItem();
gmsMediaItem.setId(UuidGenerator.compress(UuidGenerator.generate()));
gmsMediaItem.setActivityId(target.getId());
if (supportsField(token.getContainer(), "mediaItem", MediaItem.Field.MIME_TYPE.toString()) && m.getMimeType() != null) {
gmsMediaItem.setMimeType(m.getMimeType());
}
else {
gmsMediaItem.setMimeType("");
}
if (supportsField(token.getContainer(), "mediaItem", MediaItem.Field.TYPE.toString()) && m.getType() != null
&& (MediaItem.Type.AUDIO == m.getType() || MediaItem.Type.IMAGE == m.getType() || MediaItem.Type.VIDEO == m.getType())) {
gmsMediaItem.setMediaType(m.getType().toString());
}
else {
gmsMediaItem.setMediaType("");
}
if (supportsField(token.getContainer(), "mediaItem", MediaItem.Field.URL.toString()) && m.getUrl() != null) {
gmsMediaItem.setUrl(m.getUrl());
}
else {
gmsMediaItem.setUrl("");
}
gmsMediaItem.setAlbumId("");
Timestamp now = new Timestamp(System.currentTimeMillis());
gmsMediaItem.setCreated(now);
gmsMediaItem.setDescription("");
gmsMediaItem.setDuration(-1);
gmsMediaItem.setFileSize(-1);
gmsMediaItem.setLanguage("");
gmsMediaItem.setLastUpdated(now);
gmsMediaItem.setLocationId("");
gmsMediaItem.setNumComments(0);
gmsMediaItem.setNumViews(0);
gmsMediaItem.setNumVotes(0);
gmsMediaItem.setRating(0);
gmsMediaItem.setStartTime(now);
gmsMediaItem.setTaggedPeople("");
gmsMediaItem.setTags("");
gmsMediaItem.setThumbnailUrl("");
gmsMediaItem.setTitle("");
gmsMediaItemDao.insert(gmsMediaItem);
}
}
gmsActivityDao.insert(target);
logger.info("createActivity終了"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
}
public GmsActivity mapToGmsActivity(GmsPerson person, String appId, Activity activity, SecurityToken token) {
GmsActivity result = new GmsActivity();
result.setId(UuidGenerator.compress(UuidGenerator.generate()));
result.setGmsPersonId(person.getId());
result.setAppId(appId);
String appHashId = toStringDigest(getStringDigest(appId));
if (logger.isDebugEnabled()) {
logger.debug("appHashId:[" + appHashId + "]");
}
result.setAppHashId(appHashId);
if (supportsField(token.getContainer(), "activity", Activity.Field.BODY.toString()) && activity.getBody() != null) {
result.setBody(activity.getBody());
}
else {
result.setBody("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.BODY_ID.toString()) && activity.getBodyId() != null) {
result.setBodyId(activity.getBodyId());
}
else {
result.setBodyId("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.EXTERNAL_ID.toString()) && activity.getExternalId() != null) {
result.setExternalId(activity.getExternalId());
}
else {
result.setExternalId("");
}
result.setPostedTime(new Timestamp(System.currentTimeMillis())); /* 取り出すときにはlongに変換すること */
if (supportsField(token.getContainer(), "activity", Activity.Field.PRIORITY.toString()) && activity.getPriority() != null) {
result.setPriority((double)(float)activity.getPriority());
}
else {
result.setPriority(0.0d);
}
if (supportsField(token.getContainer(), "activity", Activity.Field.STREAM_FAVICON_URL.toString()) && activity.getStreamFaviconUrl() != null) {
result.setStreamFaviconUrl(activity.getStreamFaviconUrl());
}
else {
result.setStreamFaviconUrl("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.STREAM_SOURCE_URL.toString()) && activity.getStreamSourceUrl() != null) {
result.setStreamSourceUrl(activity.getStreamSourceUrl());
}
else {
result.setStreamSourceUrl("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.STREAM_TITLE.toString()) && activity.getStreamTitle() != null) {
result.setStreamTitle(activity.getTitle());
}
else {
result.setStreamTitle("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.STREAM_URL.toString()) && activity.getStreamUrl() != null) {
result.setStreamUrl(activity.getUrl());
}
else {
result.setStreamUrl("");
}
Map<String, String> params = activity.getTemplateParams();
if (supportsField(token.getContainer(), "activity", Activity.Field.TEMPLATE_PARAMS.toString()) && params != null) {
result.setTemplateParams(beanJsonConverter.convertToString(params));
}
else {
result.setTemplateParams("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.TITLE.toString()) && activity.getTitle() != null) {
result.setTitle(activity.getTitle());
}
else {
result.setTitle("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.TITLE_ID.toString()) && activity.getTitleId() != null) {
result.setTitleId(activity.getTitleId());
}
else {
result.setTitleId("");
}
if (supportsField(token.getContainer(), "activity", Activity.Field.URL.toString()) && activity.getUrl() != null) {
result.setUrl(activity.getUrl());
}
else {
result.setUrl("");
}
return result;
}



TEMPLATE_PARAMSはMapをJSON形式の文字列に変換後DBにセット。
container.jsのsupportsFieldと連動させたいのでsupportsFieldコールしまくり。

MediaItemは使用していない項目が大量にあるけど、これは0.9用らしい。


でActivityの保存ができた。

次はDataRequest.newFetchActivitiesRequest(idSpec, opt_params)のサーバ側処理を
実装してみるつもり。



--
というか、入力チェック一切行っていないのを今さら気づいた。

--
というか、hasPermissionをせっかく作ったのに使ってなかった。。

--
というか、サニタイズは?

--
簡易サニタイズつけた。
hasPermission呼ぶようにした。
.

[Apache Shindig][お勉強][OpenSocial] メモ79 opensocial.Activityの全フィールドをsupportsFieldにする

とりあえずopensocial.Activityの全フィールドを
container.js上有効にしてみた。

全フィールドの抽出方法は、


for (xx in opensocial.Activity.Field) {
console.log('"' + opensocial.Activity.Field[xx] + '",');
}

でFirebugに出力して、コピペ。

で、その結果作成されたcontainer.jsのsupportsField。

"activity" : [
"titleId",
"title",
"templateParams",
"url",
"mediaItems",
"bodyId",
"body",
"externalId",
"streamTitle",
"streamUrl",
"streamSourceUrl",
"streamFaviconUrl",
"priority",
"id",
"userId",
"appId",
"postedTime"
]



さらにMediaItemの全項目も。

"mediaItem" : [
"type",
"mimeType",
"url"
]



そんだけ。

.

[Apache Shindig][お勉強][OpenSocial] メモ78 opensocial.requestCreateActivityのサーバ側実装をしてみる(2)

サンプルのjsonrpccontainer.jsを見ると、requestCreateActivityメソッドは、


JsonRpcContainer.prototype.requestCreateActivity = function(activity, priority,
opt_callback) {
opt_callback = opt_callback || function(){};

var req = opensocial.newDataRequest();
var viewer = new opensocial.IdSpec({'userId' : 'VIEWER'});
req.add(this.newCreateActivityRequest(viewer, activity), 'key');
req.send(function(response) {
opt_callback(response.get('key'));
});
};


となっている。
一応サーバ側の実装ではGroupIdも@selfに限らず受け取れるようになっている
ものの、@self以外は送られてこないと思って良さそう。
UserIdも必ずVIEWERっぽい。

responseからResponseItemを取得するためのキーも必ず'key'となっている模様。
ただ、直接newCreateActivityRequestのような処理を行われると困るので、
サーバ側ではサンプル実装のとおり、GroupIdは無視するのが良いかも。

.

2009年7月23日木曜日

[Apache Shindig][お勉強][OpenSocial] メモ77 opensocial.requestCreateActivityのサーバ側実装をしてみる(1)

requestCreateActivityを使ってサーバにActivityを保存する。
requestCreateActivityに対応するサーバ側の処理を実装してみる。

テストで使用するGadget XMLは、goo ディベロッパーキッチンのサンプルを参考に作成。


<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="Activity">
<Require feature="opensocial-0.8" />
</ModulePrefs>
<Content type="html" view="home,profile,canvas">
<![CDATA[
<script type="text/javascript">
function get() {
/*===================================================================*/
/* OWNER IdSpec */
/*===================================================================*/
var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.OWNER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);

/*===================================================================*/
/* DataRequestオブジェクトを作成し、 */
/* そこにActivity取得リクエストを追加. */
/* レスポンスを取得するときのキーは'get_activity' */
/*===================================================================*/
var req = opensocial.newDataRequest();
req.add(req.newFetchActivitiesRequest(idspec), 'get_activity');
/*===================================================================*/
/* 送信! */
/*===================================================================*/
req.send(function(response){
var activity = response.get('get_activity')
if (activity.hadError()) {
document.getElementById('result').innerHTML
= 'エラーだったよ:' + activity.getErrorMessage();
return;
}
var out = document.createElement('ul');
var li = document.createElement('li');
if (activity.getData().size() == 0) {
li.innerHTML = 'Activity0件';
out.appendChild(li);
}
activity.getData().each(function(act) {
var title = act.getField(opensocial.Activity.Field.TITLE);
var media = act.getField(opensocial.Activity.Field.MEDIA_ITEMS);
if (media != undefined) {
for (var ii=0, len = media.length; ii < len; ii++) {
if (media[ii].getField(opensocial.MediaItem.Field.TYPE) == opensocial.MediaItem.Type.IMAGE) {
title +=
'<br /><img src="'
+ media[ii].getField(opensocial.MediaItem.Field.URL);
+ '/>';
}
}
}
li.innerHTML = title;
out.appendChild(li);
});

document.getElementById('result').appendChild(out);
});
}
function update() {
var params = {};
params[opensocial.Activity.Field.TITLE] = 'アクティビティテストです';
/*===================================================================*/
/* Activity作成 */
/*===================================================================*/
var act = opensocial.newActivity(params);
/*===================================================================*/
/* 送信 */
/*===================================================================*/
opensocial.requestCreateActivity(act, opensocial.CreateActivityPriority.HIGH, callback);

function callback(status){
if (status.hadError()) {
document.getElementById('result').innerHTML = 'Activity更新失敗';
return;
}
document.getElementById('result').innerHTML = 'Activity更新成功';
};
};

function media() {
var opt_params = {};
opt_params[opensocial.MediaItem.Field.TYPE] = opensocial.MediaItem.Type.IMAGE;
var mediaItem = opensocial.newMediaItem(
'image/gif',
'http://www.google.co.jp/intl/ja_jp/images/logo.gif',
opt_params
);
var params = {};
params[opensocial.Activity.Field.TITLE] = 'MediaItemを使ったアクティビティテストです';
params[opensocial.Activity.Field.MEDIA_ITEMS] = [mediaItem];
var act = opensocial.newActivity(params);
opensocial.requestCreateActivity(act, opensocial.CreateActivityPriority.HIGH, callback);

function callback(status){
if (status.hadError()) {
document.getElementById('result').innerHTML = 'Activity更新失敗 With MediaItemType';
return;
}
document.getElementById('result').innerHTML = 'Activity更新成功 With MediaItemType';
};
}
</script>
<div id="result"></div>
<input type="button" value="アクティビティを取得する" onclick="get();" /><br />
<input type="button" value="アクティビティを更新する" onclick="update();" /><br />
<input type="button" value="MediaItemを使ってアクティビティを更新する" onclick="media();" /><br />
]]>
</Content>
</Module>


な感じ。

サンプルによると、「MediaItemを使ったアクティビティ更新」というものも存在するらしい。

で、「アクティビティを更新する」ボタンを押下。
そのときのFirebugの出力は、

[
{
"method":"activities.create",
"params":{
"userId":["@viewer"],
"groupId":"@self",
"appId":"@app",
"activity":{
"title":"アクティビティテストです",
"mediaItems":[]
}
},
"id":"key"
}
]

となっている。
見難いので整形済み。

UserIdは複数指定できる。
GroupIdはサーバ側サンプル実装によると、見ていないので、どうしたものか。
@appはサーバ側でガジェットのURLに変換される。

どうも、requestCreateActivityを使うと、レスポンス取得キーは'key'になるらしい。

気になるところはGroupIdとUserId。
@friendsとか指定されたらどうなるんだろう。。

.

[Apache Shindig][お勉強][OpenSocial] メモ76 ActivityServiceインタフェース

とりあえず、Shindigを使用する際、実装すべきサービスを見る。

インタフェースは、


org.apache.shindig.social.opensocial.spi.ActivityService

に定義されている。

このインタフェースには、

Future<RestfulCollection<Activity>> getActivities(Set<UserId> userIds,
GroupId groupId, String appId, Set<String> fields, CollectionOptions options, SecurityToken token)
throws ProtocolException;

Future<RestfulCollection<Activity>> getActivities(UserId userId, GroupId groupId,
String appId, Set<String> fields, CollectionOptions options, Set<String> activityIds, SecurityToken token)
throws ProtocolException;


Future<Activity> getActivity(UserId userId, GroupId groupId, String appId,
Set<String> fields, String activityId, SecurityToken token)
throws ProtocolException;

Future<Void> deleteActivities(UserId userId, GroupId groupId, String appId,
Set<String> activityIds, SecurityToken token) throws ProtocolException;

Future<Void> createActivity(UserId userId, GroupId groupId, String appId,
Set<String> fields, Activity activity, SecurityToken token) throws ProtocolException;


と5つのメソッドが書いてある。

アクティビティ取得系が3つ、あとは削除と作成。
更新はしないみたい。

実装の際、参考になるソースは、

./java/samples/src/main/java/org/apache/shindig/social/opensocial/jpa/spi/ActivityServiceDb.java

においてある。と。

getActivitiesでは、GroupIdによって処理を変えているみたい。
多分PersonServiceとかAppDataServiceとかと似たような処理な気がする。

とりあえずcreateからやってみよう。
.

[Apache Shindig][お勉強][OpenSocial] メモ75 Activityって何よ

Activityって何よ?

goo ディベロッパーキッチンによると、


アクティビティとは、ユーザーの様々な行動の記録を指しています。

とのこと。

なるへそー。
.

[Apache Shindig][お勉強][OpenSocial] メモ74 DataRequest.newRemovePersonAppDataRequestのサーバ側実装をしてみる(2)

できた。

コードは、


public void deletePersonData(UserId userId, GroupId groupId,
String appId, Set<String> fields, SecurityToken token) throws ProtocolException {
GmsPerson viewer = getUser();
logger.info("deletePersonData開始"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
if (viewer == null) {
logger.info("updatePersonData処理終了:Guestは保存できない:"
+ "userId:[" + userId + "]:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: 未ログイン");
}

String appHashId = toStringDigest(getStringDigest(appId));
if (logger.isDebugEnabled()) {
logger.debug("appHashId:[" + appHashId + "]");
}
String loginId = userId.getUserId(token);
if ("Guest".equalsIgnoreCase(loginId)) {
logger.info("updatePersonData処理終了:Guestは保存できない:"
+ "userId:[" + userId + "]:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: loginid:[" + loginId + "]");
}
List<String> userLoginIds = new ArrayList<String>();
userLoginIds.add(loginId);
ExtendedGmsPerson[] gmsPersons = null;
List<String> personIds = new ArrayList<String>();
int first = 0;
int max = 20;
for (;;) {
gmsPersons = gmsPersonDao.peopleGet(
userLoginIds,
groupId.getType().toString(),
groupId.getGroupId(),
null,
null,
null,
null,
null,
first,
max,
null);
if (logger.isDebugEnabled()) {
logger.debug("count:[" + gmsPersons.length + "]");
}
int length = gmsPersons.length;
for (int ii=0; ii<length; ii++) {
personIds.add(gmsPersons[ii].getId());
}
if (length < max) {
break;
}
first += max;
}
int count = 0;
for (String personId: personIds) {
gmsPersonDao.select(personId, true);
for (String field: fields) {
GmsAppdata[] appdatas = null;
appdatas = gmsAppdataDao.selectByAppHashIdAndGmsPersonIdAndName(appHashId, personId, field);
for (int ii=0,length=appdatas.length; ii<length; ii++) {
if (appId.equalsIgnoreCase(appdatas[ii].getApplicationId())) {
gmsAppdataDao.delete(appdatas[ii]);
count++;
break;
}
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("[" + count + "]件削除しちゃった");
}
logger.info("deletePersonData終了"
+ "viewer:[" + ((viewer == null) ? "Guest" : viewer.getLoginId()) + "]:"
+ "userId:[" + userId + "]:"
+ "appId:[" + appId + "]:"
);
}


な感じ。

これで、AppDataシリーズは完了。


次はアクティビティまわりかな。

調べてみようっと。

.

[Apache Shindig][お勉強][OpenSocial] メモ73 DataRequest.newRemovePersonAppDataRequestのサーバ側実装をしてみる(1)

DataRequest.newRemovePersonAppDataRequestに対応するサーバ側処理を実装してみる。

GadgetのXMLは

と一緒。

で、newRemovePersonAppDataRequestをしている部分だけ抜き出し。


function removeAppData() {
var req = opensocial.newDataRequest();
req.add(
req.newRemovePersonAppDataRequest(opensocial.IdSpec.PersonId.VIEWER, 'key'),
'get_response');
/*=====================================================================*/
/* 送信 */
/*=====================================================================*/
req.send(function(response){
var result = response.get('get_response');
if (result.hadError()) {
document.getElementById('result').innerHTML = 'appDataの削除に失敗したよー:'
+ result.getErrorMessage();
} else {
document.getElementById('result').innerHTML = '削除したよー';
}
});
}


やろうとしていることは、VIEWERのkeyという名のキーのデータを削除すること。

FireBugによるPOSTリクエストのダンプは、

[
{
"method":"appdata.delete",
"params":{
"userId":["@viewer"],
"groupId":"@self",
"appId":"@app",
"fields":"key"
},
"id":"get_response"
}
]

ちょっと見難いので整形。
メソッド名はappdata.delete。

対応するAppDataServiceのメソッドは、

public Future<Void> deletePersonData(UserId userId, GroupId groupId,
String appId, Set<String> fields, SecurityToken token) throws ProtocolException {
if (logger.isDebugEnabled()) {
logger.debug("userId:[" + userId + "]");
logger.debug("userId2:[" + userId.getUserId(token) + "]");
logger.debug("groupId:[" + groupId + "]");
logger.debug("appId:[" + appId + "]");
for (String field: fields) {
logger.debug("field:[" + field + "]");
}
}
throw new UnsupportedOperationException();
}


と。

こいつを実装してやればよい。

.

2009年7月22日水曜日

[Apache Shindig][お勉強][OpenSocial] メモ72 DataRequest.newFetchPersonAppDataRequestのサーバ側実装をしてみる(3)

できた。

コードは、


public Map<String, Map<String, String>> getPersonData(Set<UserId> userIds, GroupId groupId,
String appId, Set<String> fields, SecurityToken token) throws ProtocolException {
logger.info("getPersonData開始:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);

Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();
String appHashId = toStringDigest(getStringDigest(appId));
if (logger.isDebugEnabled()) {
logger.debug("appHashId:[" + appHashId + "]");
}

List<String> userLoginIds = getUserList(userIds, token);
ExtendedGmsPerson[] gmsPersons = null;
List<String> personIds = new ArrayList<String>();
int first = 0;
int max = 20;
for (;;) {
gmsPersons = gmsPersonDao.peopleGet(
userLoginIds,
groupId.getType().toString(),
groupId.getGroupId(),
null,
null,
null,
null,
null,
first,
max,
null);
if (logger.isDebugEnabled()) {
logger.debug("count:[" + gmsPersons.length + "]");
}
int length = gmsPersons.length;
for (int ii=0; ii<length; ii++) {
personIds.add(gmsPersons[ii].getId());
}
if (length < max) {
break;
}
first += max;
}
Timestamp now = new Timestamp(System.currentTimeMillis());
for (String personId: personIds) {
gmsPersonDao.select(personId, true);
Map<String, String> m = Maps.newHashMap();
for (String field: fields) {
GmsAppdata[] appdatas = null;
appdatas = gmsAppdataDao.selectByAppHashIdAndGmsPersonIdAndName(appHashId, personId, field);
GmsAppdata appdata = null;
for (int ii=0,length=appdatas.length; ii<length; ii++) {
if (appId.equalsIgnoreCase(appdatas[ii].getApplicationId())) {
m.put(field, appdatas[ii].getData());
break;
}
}
}
results.put(personId, m);
}

logger.info("getPersonData終了:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);
return results;
}


な感じ。

Service側は、

public Future<DataCollection> getPersonData(Set<UserId> userIds, GroupId groupId,
String appId, Set<String> fields, SecurityToken token) throws ProtocolException {
if (logger.isDebugEnabled()) {
for (UserId userId: userIds) {
logger.debug("userId:[" + userId + "]");
logger.debug("userId2:[" + userId.getUserId(token) + "]");
}
logger.debug("groupId:[" + groupId + "]");
logger.debug("appId:[" + appId + "]");
for (String field: fields) {
logger.debug("field:[" + field + "]");
}
}
Map<String, Map<String, String>> results = logic.getPersonData(userIds, groupId, appId, fields, token);
DataCollection dc = new DataCollection(results);
return ImmediateFuture.newInstance(dc);
}


な感じ。

これで、newFetchPersonAppDataRequestとnewUpdatePersonAppDataRequestに対応できた。
ちなみに、キーごとに保存できるデータの最大長は、システムの許す限り。


で、ちょっとだけ、動くように修正したガジェットXMLは、

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="AppData">
<Require feature="opensocial-0.8" />
</ModulePrefs>
<Content type="html" view="home,profile,canvas">
<![CDATA[
<script type="text/javascript">
function updateAppData() {
/*=====================================================================*/
/* これから保存したいオブジェクト */
/*=====================================================================*/
var obj = {'param1':'データだよーん', 'param2':'だよーんデータ'};
/*=====================================================================*/
/* オブジェクトを文字列に変換(OpenSocial推奨らしい) */
/*=====================================================================*/
var strObj = gadgets.json.stringify(obj);

/*=====================================================================*/
/* DataRequest生成、セットアップ */
/*=====================================================================*/
var req = opensocial.newDataRequest();
req.add(req.newUpdatePersonAppDataRequest(
opensocial.IdSpec.PersonId.VIEWER,
'key',
strObj),
'get_response');
/*=====================================================================*/
/* 送信! */
/*=====================================================================*/
req.send(function(response){
var result = response.get('get_response');
if (result.hadError()) {
document.getElementById('result').innerHTML = '失敗したよー:'
+ result.getErrorMessage();
} else {
document.getElementById('result').innerHTML
= 'newUpdatePersonAppDataRequest成功';
}
});
}
function getAppData() {
var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.VIEWER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);

var req = opensocial.newDataRequest();
/*=====================================================================*/
/* まずVIEWERを取得 */
/*=====================================================================*/
req.add(
req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER),
'get_viewer');
/*=====================================================================*/
/* APPDATA取得のリクエストを生成 */
/*=====================================================================*/
req.add(
req.newFetchPersonAppDataRequest(idspec, 'key'),
'get_appdata');
/*=====================================================================*/
/* 送信 */
/*=====================================================================*/
req.send(function(response){
var viewer = response.get('get_viewer');
if (viewer.hadError()) {
document.getElementById('result').innerHTML = 'Viewerの取得に失敗したよー:'
+ viewer.getErrorMessage();
} else {
var appdata = response.get('get_appdata');
if (appdata.hadError()) {
document.getElementById('result').innerHTML = 'AppDataの取得に失敗したよー:'
+ appdata.getErrorMessage();
} else {
if (appdata.getData()[viewer.getData().getId()]) {
/*=============================================================*/
/* VIEWER固有のAppData取得 */
/*=============================================================*/
var srcObj = appdata.getData()[viewer.getData().getId()]['key'];
/*=============================================================*/
/* unescape */
/*=============================================================*/
console.log(srcObj);
var jsonStr = gadgets.util.unescapeString(srcObj);
var jsonObj = gadgets.json.parse(jsonStr);
document.getElementById('result').innerHTML = jsonObj['param1'] + '<br />' + jsonObj['param2'];
} else {
document.getElementById('result').innerHTML = 'がちょーん';
}
}
}
});
}
function removeAppData() {
var req = opensocial.newDataRequest();
req.add(
req.newRemovePersonAppDataRequest(opensocial.IdSpec.PersonId.VIEWER, 'key'),
'get_response');
/*=====================================================================*/
/* 送信 */
/*=====================================================================*/
req.send(function(response){
var result = response.get('get_response');
if (result.hadError()) {
document.getElementById('result').innerHTML = 'appDataの削除に失敗したよー:'
+ result.getErrorMessage();
} else {
document.getElementById('result').innerHTML = '削除したよー';
}
});
}
</script>
<div id="result"></div>
<input type="button" value="更新" onclick="updateAppData()" /><br />
<input type="button" value="取得" onclick="getAppData()" /><br />
<input type="button" value="削除" onclick="removeAppData()" /><br />
]]>
</Content>
</Module>


な感じー。


まだremoveAppDataは動かないけど。
.

[Apache Shindig][お勉強][OpenSocial] メモ71

うーむ。。

良くわからないけど、
これ
だと動かない気がする。。

うーむ。

My Shindig環境で実行するとI.replace is not functionってJavaScriptエラーになる。


var appdata_param = appdata.getData()[viewer.getData().getId()];

では無くて、

var appdata_param = appdata.getData()[viewer.getData().getId()]['appdata_key'];


とすれば動くんだけど、、

うーむ。。
JavaScriptなんで良くわかんないけど、なんとなく変な気がする。。
わからん。。


まぁいいや。
.

[Apache Shindig][お勉強][OpenSocial] メモ70 DataRequest.newFetchPersonAppDataRequestのサーバ側実装をしてみる(2)

ShindigのAppDataServiceDb.javaを見ると、


// load the map up
List<ApplicationDataMapDb> dataMaps = JPQLUtils.getListQuery(entityManager, sb.toString(),
paramList, null);
Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();

// only add in the fields
if (fields == null || fields.size() == 0) {
for (ApplicationDataMapDb adm : dataMaps) {
results.put(adm.getPersonId(), adm.getValues());
}
} else {
for (ApplicationDataMapDb adm : dataMaps) {
Map<String, String> m = Maps.newHashMap();
for (String f : fields) {
String value = adm.getValues().get(f);
if (null != value) {
m.put(f, value);
}
}
results.put(adm.getPersonId(), m);
}
}
DataCollection dc = new DataCollection(results);
return ImmediateFuture.newInstance(dc);


となっていて、見たこともないDataCollectionというクラスがImmediateFutre.newInstanceされて返されている。。

結局のところ、AppDataService.getPersonDataは、同じように

DataCollection dc = new DataCollection(results);
return ImmediateFuture.newInstance(dc);


としてやればよいと。

resultsは、

Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>();

と。

このresultsをせっせと作って、
org.apache.shindig.protocol.DataCollection
にして返す。

大体わかった。
.

[Apache Shindig][お勉強][OpenSocial] メモ69 DataRequest.newFetchPersonAppDataRequestのサーバ側実装をしてみる(1)

DataRequest.newFetchPersonAppDataRequestに対応するサーバ側の実装をしてみる。

ガジェットXMLは、
ここ
で使ったものをそのまま使う。

とりあえず、どんなリクエストが来るのか見てみる。

FirBugでのPOSTデータは、


[{"method":"people.get","params":{"userId":["@viewer"],"groupId":"@self","fields":["id","name","thumbnailUrl"
,"id","displayName"]},"id":"get_viewer"},{"method":"appdata.get","params":{"userId":["@viewer"],"groupId"
:"@self","appId":"@app","fields":["key"]},"id":"get_appdata"}]

な感じ。

「取得」ボタンを押下すると、people.getでVIEWERは取れるんだけど、
appdata.getでは取れない。もちろん未実装だから。
見難いけど、上記は、people.getとappdata.getの2つのRPCが行われている。

で、上記リクエストを投げたときのサーバ側のログ。

[リクエスト:[7642812]] 2009-07-22 22:31:19,062 INFO jp.qsdn.gms.web.spring.controller.gadget.ApiController - dispatcher:[jp.qsdn.gms.social.protocol.SpringHandlerRegistry@5e7663]
[リクエスト:[7642812]] 2009-07-22 22:31:19,063 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - userId:[VIEWER]
[リクエスト:[7642812]] 2009-07-22 22:31:19,063 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - userId2:[root]
[リクエスト:[7642812]] 2009-07-22 22:31:19,063 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - groupId:[SELF]
[リクエスト:[7642812]] 2009-07-22 22:31:19,063 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - appId:[http://localhost/opensocial/hello.xml]
[リクエスト:[7642812]] 2009-07-22 22:31:19,063 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - field:[key]
[リクエスト:[7642812]] 2009-07-22 22:31:19,063 WARN jp.qsdn.gms.web.spring.controller.gadget.ApiController - Returning a response error as result of an exception
java.lang.UnsupportedOperationException
at jp.qsdn.gms.social.service.AppDataServiceImpl.getPersonData(AppDataServiceImpl.java:60)
at org.apache.shindig.social.opensocial.service.AppDataHandler.get(AppDataHandler.java:122)


な感じ。

appdata.updateとは違って、UserIdが配列で飛んでくる。
そこさえ注意していれば、割と簡単そう。

.

[Apache Shindig][お勉強][OpenSocial] メモ68 DataRequest.newUpdatePersonAppDataRequestのサーバ側実装をしてみる(3)

DataRequest.newUpdatePersonAppDataRequestのサーバ側実装ができた。

へぼいけどざっとこんな感じ。


public void updatePersonData(UserId userId, GroupId groupId, String appId, Set<String> fields, Map<String, String> values, SecurityToken token)
throws ProtocolException {
logger.info("updatePersonData処理開始:"
+ "userId:[" + userId + "]:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);

assert appId != null;

GmsPerson viewer = getUser();
if (viewer == null) {
logger.info("updatePersonData処理終了:Guestは保存できない:"
+ "userId:[" + userId + "]:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: 未ログイン");
}

String appHashId = toStringDigest(getStringDigest(appId));
if (logger.isDebugEnabled()) {
logger.debug("appHashId:[" + appHashId + "]");
}
String loginId = userId.getUserId(token);
if ("Guest".equalsIgnoreCase(loginId)) {
logger.info("updatePersonData処理終了:Guestは保存できない:"
+ "userId:[" + userId + "]:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);
throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN,
"Guestは保存できない: loginid:[" + loginId + "]");
}
List<String> userLoginIds = new ArrayList<String>();
userLoginIds.add(loginId);
ExtendedGmsPerson[] gmsPersons = null;
List<String> personIds = new ArrayList<String>();
int first = 0;
int max = 20;
for (;;) {
gmsPersons = gmsPersonDao.peopleGet(
userLoginIds,
groupId.getType().toString(),
groupId.getGroupId(),
null,
null,
null,
null,
null,
first,
max,
null);
if (logger.isDebugEnabled()) {
logger.debug("count:[" + gmsPersons.length + "]");
}
int length = gmsPersons.length;
for (int ii=0; ii<length; ii++) {
personIds.add(gmsPersons[ii].getId());
}
if (length < max) {
break;
}
first += max;
}
Timestamp now = new Timestamp(System.currentTimeMillis());
for (String personId: personIds) {
gmsPersonDao.select(personId, true);
for (String field: fields) {
GmsAppdata[] appdatas = null;
appdatas = gmsAppdataDao.selectByAppHashIdAndGmsPersonIdAndName(appHashId, personId, field);
GmsAppdata appdata = null;
for (int ii=0,length=appdatas.length; ii<length; ii++) {
if (appId.equalsIgnoreCase(appdatas[ii].getApplicationId())) {
appdata = appdatas[ii];
break;
}
}
if (appdata != null) {
appdata.setData(values.get(field));
appdata.setUpdatedAt(now);
appdata.setUpdator(viewer.getId());
gmsAppdataDao.update(appdata);
}
else {
appdata = new GmsAppdata();
appdata.setId(UuidGenerator.compress(UuidGenerator.generate()));
appdata.setAppHashId(appHashId);
appdata.setApplicationId(appId);
appdata.setGmsPersonId(personId);
appdata.setName(field);
appdata.setData(values.get(field));
appdata.setCreatedAt(now);
appdata.setCreator(viewer.getId());
appdata.setUpdatedAt(now);
appdata.setUpdator(viewer.getId());
gmsAppdataDao.insert(appdata);
}
}
}

logger.info("updatePersonData処理終了:"
+ "userId:[" + userId + "]:"
+ "groupId:[" + groupId + "]:"
+ "appId:[" + appId + "]:"
);
}



Shindigのサンプル実装のAppDataServiceImplをまねして作ったけど、
友達がいっぱいいる人はとっても重い。友達いない人向けということで。
更新すべき人は誰かを決定するのにUserIdとGroupIdを使用するんで、
以前作ったpeople.getの処理をコールしている。
filterとかは一切なし。

GmsAppdataというValueObjectはgms_appdataというテーブルを表しているんだけど、
application_idとgms_person_idを一意のキーにしなかった。
というのは、MySQLって長い文字列をプライマリーキーにできないし、INDEXもはれないから。
(今はDerby使っているけど)
だから、面倒だけど、application_idからHASH値を求めて、そのHASH値でデータを
持ってくるようにした。。

途中で使っているgetUser()は、現在ログイン中のPersonオブジェクトを持ってくる。
ログインしていないとnullが返る。

GuestがVIEWERかOWNERの時は(ありえないけど)、データは保存しない。
そのときはorg.apache.shindig.protocol.ProtocolExceptionを投げる。

org.apache.shindig.protocol.ProtocolExceptionを投げておくと、Handler内で、いい感じのエラーJSONにしてくれる。

で、一応保存できた。


ij> select data from ROOTTEST.gms_appdata;
DATA
--------------------------------------------------------------------------------------------------------------------------------
{"param1":"データだよーん","param2":"だよーんデータ"}

1 row selected


な感じ。

そんだけ。
.

[OpenSocial][その他] iGoogleでcanvas

いつの間にかiGoogleで新スタイルのガジェットが使えるようになったっぽい。

これで"home"とか"canvas"とかが使えるのかな?

.

[Apache Shindig][お勉強][OpenSocial] メモ67 DataRequest.newUpdatePersonAppDataRequestのサーバ側実装をしてみる(2)

Shindigに付属するサンプル実装の


./java/samples/src/main/java/org/apache/shindig/social/opensocial/jpa/spi/AppDataServiceDb.java

を見てみる。

UserIdとGroupIdが指定できるので、
やはり@friendsやら@allやらが指定できる。

update時にGroupIdに@allが指定されると
UserIdに指定された人に関係するすべての人用のapp_dataが保存される。
同様にGroupIdに@friendsが指定されると
UserIdに指定された人の友達全ての人用のapp_dataが保存される。

だもんで、appData使うアプリを作る際は、いろいろと気をつけなきゃならなそう。
mixiやらgooやらではどうしているんだろうか。。

たぶんパーミッションモデルなるもので制限していることとは思うんだけど。



.

[Apache Shindig][お勉強][OpenSocial] メモ66 DataRequest.newUpdatePersonAppDataRequestのサーバ側実装をしてみる(1)

AppDataを扱うメソッドのうち、


Object newUpdatePersonAppDataRequest(id, key, value)
# 指定した個人のアプリケーション フィールドを更新するように要求するアイテムを作成します。

をやってみる。

まずはガジェットXML。
gooディベロッパーキッチンを見ながら作った。

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="AppData">
<Require feature="opensocial-0.8" />
</ModulePrefs>
<Content type="html" view="home,profile,canvas">
<![CDATA[
<script type="text/javascript">
function updateAppData() {
/*====================================================================*/
/* これから保存したいオブジェクト */
/*====================================================================*/
var obj = {'param1':'データだよーん', 'param2':'だよーんデータ'};
/*====================================================================*/
/* オブジェクトを文字列に変換(OpenSocial推奨らしい) */
/*====================================================================*/
var strObj = gadgets.json.stringify(obj);

/*====================================================================*/
/* DataRequest生成、セットアップ */
/*====================================================================*/
var req = opensocial.newDataRequest();
req.add(req.newUpdatePersonAppDataRequest(
opensocial.IdSpec.PersonId.VIEWER,
'key',
strObj),
'get_response');
/*====================================================================*/
/* 送信! */
/*====================================================================*/
req.send(function(response){
var result = response.get('get_response');
if (result.hadError()) {
document.getElementById('result').innerHTML = '失敗したよー:'
+ result.getErrorMessage();
} else {
document.getElementById('result').innerHTML
= 'newUpdatePersonAppDataRequest成功';
}
});
}
function getAppData() {
var idspec = new opensocial.IdSpec();
idspec.setField(opensocial.IdSpec.Field.USER_ID, opensocial.IdSpec.PersonId.VIEWER);
idspec.setField(opensocial.IdSpec.Field.GROUP_ID, opensocial.IdSpec.GroupId.SELF);

var req = opensocial.newDataRequest();
/*====================================================================*/
/* まずVIEWERを取得 */
/*====================================================================*/
req.add(
req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER),
'get_viewer');
/*====================================================================*/
/* APPDATA取得のリクエストを生成 */
/*====================================================================*/
req.add(
req.newFetchPersonAppDataRequest(idspec, 'key'),
'get_appdata');
/*====================================================================*/
/* 送信 */
/*====================================================================*/
req.send(function(response){
var viewer = response.get('get_viewer');
if (viewer.hadError()) {
document.getElementById('result').innerHTML = 'Viewerの取得に失敗したよー:'
+ viewer.getErrorMessage();
} else {
var appdata = response.get('get_appdata');
if (appdata.hadError()) {
document.getElementById('result').innerHTML = 'AppDataの取得に失敗したよー:'
+ appdata.getErrorMessage();
} else {
if (appdata.getData()[viewer.getData().getId()]) {
/*============================================================*/
/* VIEWER固有のAppData取得 */
/*============================================================*/
var srcObj = appdata.getData()[viewer.getData().getId()];
/*============================================================*/
/* unescape */
/*============================================================*/
var jsonStr = gadgets.util.unescapeString(srcObj);
var jsonObj = gadgets.json.parse(jsonStr);
document.getElementById('result').innerHTML = jsonObj['param1'] + '<br />' + jsonObj['param2'];
} else {
document.getElementById('result').innerHTML = 'がちょーん';
}
}
}
});
}
function removeAppData() {
var req = opensocial.newDataRequest();
req.add(
req.newRemovePersonAppDataRequest(opensocial.IdSpec.PersonId.VIEWER, 'key'),
'get_response');
/*====================================================================*/
/* 送信 */
/*====================================================================*/
req.send(function(response){
var result = response.get('get_response');
if (result.hadError()) {
document.getElementById('result').innerHTML = 'appDataの削除に失敗したよー:'
+ result.getErrorMessage();
} else {
document.getElementById('result').innerHTML = '削除したよー';
}
});
}
</script>
<div id="result"></div>
<input type="button" value="更新" onclick="updateAppData()" /><br />
<input type="button" value="取得" onclick="getAppData()" /><br />
<input type="button" value="削除" onclick="removeAppData()" /><br />
]]>
</Content>
</Module>


というか、サンプルのまんま。

で、Gadgetを表示させてみて、表示されたボタンのうち「更新」ボタンを押下してみる。
今のところ、サーバ側では、java.lang.UnsupportedOperationException
を投げるようにしてあるんで、やっぱりjava.lang.UnsupportedOperationExceptionがスローされる。

スタックトレースは、

java.lang.UnsupportedOperationException
at jp.qsdn.gms.social.service.AppDataServiceImpl.updatePersonData(AppDataServiceImpl.java:62)
at org.apache.shindig.social.opensocial.service.AppDataHandler.create(AppDataHandler.java:106)
at org.apache.shindig.social.opensocial.service.AppDataHandler.update(AppDataHandler.java:78)

な感じ。
AppDataHandlerからAppDataServiceをimplementsしたAppDataServiceImplの
updatePersonDataがコールされる模様。

今のところのAppDataServiceImplは以下な感じ。

package jp.qsdn.gms.social.service;

import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.protocol.DataCollection;
import org.apache.shindig.protocol.ProtocolException;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;

import org.apache.shindig.social.opensocial.spi.AppDataService;
import org.apache.shindig.social.opensocial.spi.UserId;
import org.apache.shindig.social.opensocial.spi.GroupId;
import org.apache.shindig.auth.SecurityToken;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* AppDataServiceの実装
*/
public class AppDataServiceImpl implements AppDataService {
protected final Log logger = LogFactory.getLog(AppDataServiceImpl.class);

public Future<DataCollection> getPersonData(Set<UserId> userIds, GroupId groupId,
String appId, Set<String> fields, SecurityToken token) throws ProtocolException {
throw new UnsupportedOperationException();
}

public Future<Void> deletePersonData(UserId userId, GroupId groupId,
String appId, Set<String> fields, SecurityToken token) throws ProtocolException {
throw new UnsupportedOperationException();
}
/**
* Updates app data for the specified user and group with the new values.
*
* @param userId The user
* @param groupId The group
* @param appId The app
* @param fields The fields to filter the data by. Empty set implies all
* @param values The values to set
* @param token The security token
* @return an error if one occurs
*/
public Future<Void> updatePersonData(UserId userId, GroupId groupId,
String appId, Set<String> fields, Map<String, String> values, SecurityToken token)
throws ProtocolException {
if (logger.isDebugEnabled()) {
logger.debug("userId:[" + userId + "]");
logger.debug("userId2:[" + userId.getUserId(token) + "]");
logger.debug("groupId:[" + groupId + "]");
logger.debug("appId:[" + appId + "]");
for (String field: fields) {
logger.debug("field:[" + field + "]");
}
for (String key: values.keySet()) {
logger.debug("key:[" + key + "] value:[" + values.get(key) + "]");
}
}
throw new UnsupportedOperationException();
}
}



で、ログはこんな感じ。

[リクエスト:[22256969]] 2009-07-22 01:40:16,912 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - userId:[VIEWER]
[リクエスト:[22256969]] 2009-07-22 01:40:16,912 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - userId2:[Guest]
[リクエスト:[22256969]] 2009-07-22 01:40:16,914 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - groupId:[SELF]
[リクエスト:[22256969]] 2009-07-22 01:40:16,914 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - appId:[http://localhost/opensocial/hello.xml]
[リクエスト:[22256969]] 2009-07-22 01:40:16,915 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - field:[key]
[リクエスト:[22256969]] 2009-07-22 01:40:16,915 DEBUG jp.qsdn.gms.social.service.AppDataServiceImpl - key:[key] value:[{"param1":"データだよーん","param2":"だよーんデータ"}]



ログインしないで「更新」ボタンを押下したんで、userId2がGuest(Anonymous)になっている。
普通、GuestからのAppData保存はどうするんだろう。。

たぶん、認証してないよエラーを返すのが正解な気がする。

それは置いといて、
fieldとvaluesの関係がよくわからない。

values中のfieldsキーが示すデータを保存すればよいのかなぁ。。

ちなみに、POSTリクエスト時のデータは、

[{"method":"appdata.update","params":{"userId":["@viewer"],"groupId":"@self","appId":"@app","data":{"key"

:"{\"param1\":\"データだよーん\",\"param2\":\"だよーんデータ\"}"},"fields":"key"},"id":"get_response"}]

な感じ。

--
どうも、AppDataServiceDB.javaによると、
values中のfieldsで指定されたキーの値を、fieldsで指定されたキーで保存するみたい。


.

2009年7月21日火曜日

[Apache Shindig][お勉強][OpenSocial] メモ65

次は、
DataRequest.newUpdatePersonAppDataRequest
DataRequest.newFetchPersonAppDataRequest
あたりをやろうっと。

ちょっと検索してみると、MySpaceでは動かないことが良くわかった。。

PersistenceAPI?

うーむ。
.

[Apache Shindig][お勉強][OpenSocial] メモ64 DataRequest.newFetchPeopleRequestのサーバ側実装(3)

とりあえず版だけど、newFetchPeopleRequestのサーバ側実装ができた。

友達一覧取得できた。

一応そんときのGadgetのxml。


<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="友達一覧取得">
<Require feature="opensocial-0.8" />
<Require feature="dynamic-height" />
</ModulePrefs>
<Content type="html" view="home,profile,canvas"><![CDATA[
<div id='friends_list'></div>
<script type="text/javascript">

function requestGetOwnerProfile() {
var params = {};
params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [
opensocial.Person.Field.ID,
opensocial.Person.Field.NICKNAME,
opensocial.Person.Field.THUMBNAIL_URL,
opensocial.Person.Field.PROFILE_URL
];
// params[opensocial.DataRequest.PeopleRequestFields.FILTER] = opensocial.DataRequest.FilterType.HAS_APP;
var req = opensocial.newDataRequest();
var idSpecParam = {};
idSpecParam[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.OWNER;
idSpecParam[opensocial.IdSpec.Field.GROUP_ID] = opensocial.IdSpec.GroupId.FRIENDS;
var idSpec = opensocial.newIdSpec(idSpecParam);

req.add(req.newFetchPeopleRequest(idSpec, params), "get_friends");
req.send(handleRequestGetFriendsProfile);
};

function handleRequestGetFriendsProfile(data) {
var friends = data.get("get_friends");
if (friends.hadError()) {
//Handle error using viewer.getError()...
document.getElementById('friends_list').innerHTML = 'エラーだよーん';
return;
}
var data = friends.getData();
var out = document.createElement('ul');
data.each(function(friend) {
var li = document.createElement('li');
var thumbnailUrl = friend.getField(opensocial.Person.Field.THUMBNAIL_URL);
li.innerHTML = '<img src="'+thumbnailUrl+'" />'+friend.getField(opensocial.Person.Field.NICKNAME);
out.appendChild(li);
});
document.getElementById('friends_list').appendChild(out);

// 自動調節
gadgets.window.adjustHeight();
};

gadgets.util.registerOnLoadHandler(requestGetOwnerProfile);

</script>
]]>
</Content>
</Module>


うーん。すばらしい。

サーバ側のSQLは悲惨な感じ。
以下、PersonServiceとDAOの間のロジック。

public List<Person> getPeople(List<String> userLoginIds, String paramGroupType, String paramGroupId, CollectionOptions collectionOptions, SecurityToken token, Set<String> fields) {
List<Person> result = new ArrayList<Person>();
logger.info("getPeople開始:");
if (logger.isDebugEnabled()) {
logger.debug("collectionOptions:sortBy:[" + collectionOptions.getSortBy() + "]");
logger.debug("collectionOptions:sortOrder:[" + collectionOptions.getSortOrder() + "]");
logger.debug("collectionOptions:filter:[" + collectionOptions.getFilter() + "]");
logger.debug("collectionOptions:filterOperation:[" + collectionOptions.getFilterOperation() + "]");
logger.debug("collectionOptions:filterValue:[" + collectionOptions.getFilterValue() + "]");
logger.debug("collectionOptions:first:[" + collectionOptions.getFirst() + "]");
logger.debug("collectionOptions:max:[" + collectionOptions.getMax() + "]");
logger.debug("collectionOptions:updatedSince:[" + collectionOptions.getUpdatedSince() + "]");
}

String filter = collectionOptions.getFilter();
String filterValue = "";
if ("hasApp".equalsIgnoreCase(filter)) {
filterValue = "" + token.getModuleId();
}
else {
filterValue = collectionOptions.getFilterValue();
}

ExtendedGmsPerson[] gmsPersons = gmsPersonDao.peopleGet(
userLoginIds,
paramGroupType,
paramGroupId,
collectionOptions.getSortBy(),
collectionOptions.getSortOrder().toString(),
filter,
collectionOptions.getFilterOperation().toString(),
filterValue,
collectionOptions.getFirst(),
collectionOptions.getMax(),
collectionOptions.getUpdatedSince());
if (logger.isDebugEnabled()) {
logger.debug("count:[" + gmsPersons.length + "]");
}
int length = gmsPersons.length;
for (int ii=0; ii<length; ii++) {
result.add(mapToPerson(gmsPersons[ii], fields, PERMIT_LEVEL_ANONYMOUS, token));
}

logger.info("getPeople終了:");
return result;
}



mapToPersonはDAOのValueObjectからShindigのPersonオブジェクトにマップするメソッド。
項目はとりあえずANONYMOUSの場合の項目しか返さない。多分あとで公開レベルに応じて
処理をわけなきゃいけなさそう。


filter=hasApp


でリクエストが来たら、検索結果のPersonのうち、同じガジェットをインストールしている
人のみ返す。
また、上記のコードからじゃわからないけど、

filter=isFriendsWith

が来た場合には、filterValueに友達IDが入っているものとして、
検索結果のうち、友達にfilterValueの友達IDの人がいるPersonのみ返す。
(isFriendsWithは良くわからない。。)

filter=all

の場合にはフィルタ処理は何もしない。

filter=topFriends

の場合には、二度手間だけど、検索結果のうち、友達のみ返す。
groupIdがfriends以外のときのみ有効。

で、filterと関連するへぼSQLの一部。

<!-- for Filter -->
<dynamic>
<isEqual property="filter" compareValue="all">
</isEqual>
<isEqual property="filter" compareValue="hasApp" prepend="AND">
exists (
select
'X'
from
gms_user_gadget
where
gms_user_gadget.module_id = #filterValue#
and gms_user_gadget.gms_person_id = friends.id
)
</isEqual>
<isEqual property="filter" compareValue="topFriends" prepend="AND">
<!-- UserIdに指定された人々の友達 -->
exists (
select
'X'
from
gms_friend ff1,
gms_person pp1
where
pp1.id = ff1.gms_person_id
and pp1.login_id IN
<iterate property="loginId"
open="(" close=")" conjunction="," >
#loginId[]#
</iterate>
and ff1.friend_id = friends.id
)
</isEqual>
<isEqual property="filter" compareValue="isFriendsWith">
exists (
select
'X'
from
gms_friend ff2
where
ff2.gms_person_id = friends.id
and ff2.friend_id = #filterValue#
)
</isEqual>
<!-- ここから項目検索 -->
<isEqual property="filter" compareValue="id" prepend="AND">
<isEqual property="filterOperation" compareValue="contains">
friends.id like '%$filterValue$%'
</isEqual>
<isEqual property="filterOperation" compareValue="equals">
friends.id = #filterValue#
</isEqual>
<isEqual property="filterOperation" compareValue="startsWith">
friends.id like '$filterValue$%'
</isEqual>
<isEqual property="filterOperation" compareValue="present">
friends.id = #filterValue#
</isEqual>
</isEqual>
<!-- ここまで。続きは随時追加 -->
</dynamic>


なんだかもう、混乱。。。

ところで、firstとmaxというパラメータが指定できて、
ページングできそうなんだけど、そうすると総件数がほしいような。

どうやって取得するんだろう。
.