2010年1月31日日曜日

LTしてみました(SUZ-NIGHT)

スズキです。

今回は発表必須の制約で、次の5名で行いました。

▼ KUSSY
Twitter: http://twitter.com/KUSSY8077
Blog: http://zassoudamasy.dreamlog.jp/

▼ shouhei
Twitter: http://twitter.com/shouhei

▼ asaism_
Twitter: http://twitter.com/asaism_
Blog: http://takexoff.net/blog/ctoblog/

▼ asaism_さんと同じ会社の方

▼ suz-lab
Twitter: http://twitter.com/suz_lab
Blog: http://blog.suz-lab.com/

実際のLTはこんな感じでした。

▼ TwitterAPIへのアプリケーション登録 (KUSSY)
http://docs.google.com/a/suz-lab.com/present/view?id=dgzv4t9w_2hcdqcfmt
--------
以前から、一度は試しておかないとなー、と思っていたところだったので、
僕的に、タイムリーなトピックでした。

▼ 案件ぶっちゃけ話 (shouhei)
--------
ぶっちゃけ過ぎて詳しいことは書けないのですが...、
ここで出てきた経験則は、Scrumに通じるものがあるなー、
と思った次第です。別途、shouheiとはScrum突っ込んどくか…

▼ 自社運営サイトにまつわる話 (asaism_)
--------
何とか、スキルの時間売りから脱却したいと思っていた矢先での、
貴重なお話しでした。僕も広告収入、欲しい...

▼ SquidとかNutchとかSolrとか (asaism_さんと同じ会社の方)
--------
こちらも、TwitterAPI同様、一度は試しておかないとなー、トピックでした。
NutchはHadoopと絡むようなので、AWSも絡めて、
僕も、なんか試してみたいところです。


▼ SUZ-LAB謹製 CentOS AMI (suz-lab)
http://docs.google.com/present/view?id=dc6m99bz_119cmr2bxhh
--------
僕が公開しているCentOSのAMIの布教です。
使ってもらえたら、嬉しいです。

LTなので、1人3分くらいで、1時間もあれば終わるかなー、と思っていたら、
質疑応答が盛り上がって、3時間くらいになってしまいました...

僕的には毎週やりたいところですが、次回は、2/26です。

みなさん、ありがとうございました。

--------
http://www.suz-lab.com

Skype内蔵デスクトップフォン「TECO XS2008CA」

スズキです。

Skypeの月額プラン(1ヶ国限定プラン)に入って
オンライン番号(SkypeIn)を取得して利用しているのですが、
http://www.skype.com/intl/ja/allfeatures/onlinenumber/
近頃、PCのSkypeではなく、普通の電話っぽく利用したいと思い、
下記の製品を購入してみました。
http://www.amazon.co.jp/gp/product/B002WLNB7Y?tag=iretsuzusblog-22

流石に専用機だけあって、今のところPCのSkypeよりも使い勝手がよく、
安定しています。

この程度なら、もう、固定回線契約するより、、
Skypeの月額プラン(1ヶ国限定プラン)でオンライン番号(SkypeIn)を取得したほうが、
いろんな意味で、いいんじゃないかと思う、今日この頃です…

--------
http://www.suz-lab.com

サクラエディタを1.6.5.0にアップデート

スズキです。

ふと気になったので、確認してみたら、
2009/09/26に1.6.5.0になってました...

とりあえず、アップデートです。

マクロでPerl呼び出して、コードとか整形するってのやりたいなー。

--------
http://www.suz-lab.com

Googleカレンダーで天気予報表示

スズキです。

知らなかったのですが、標準機能でできました。

Googleカレンダーの
「設定」 → 「設定」 → 「全般」
で、"場所"の項目に"東京都渋谷区"などを指定して、
"現在地の天気を表示"の項目を‪"°C‬"にするだけです。

これで、傘を忘れることも減りそうです。

--------
http://www.suz-lab.com

2010年1月28日木曜日

Firefox Portable 3.6 リリース

スズキです。

今さら感はありますが...
http://portableapps.com/news/2010-01-21_-_firefox_portable_3.6

"3.5.x"は勝手にバージョンアップしてくれていたので、
入れ替える必要はなかったのですが、
さすがに、3.6は手動で入れ替えないといけないようです...

ということで、早速アップデート(入れ替え)です。

プラグイン入れないと...

--------
http://www.suz-lab.com

"FlashDevelop"で"AIR 2 beta"

スズキです。

"Flex SDK"の部分は下記の内容に従っています。
http://labs.adobe.com/wiki/index.php/AIR_2:Release_Notes#AIR_2_SDK

まず、下記のように、"FlashDevelop"と"Flex SDK"の環境を準備しておきます。
http://blog.suz-lab.com/2010/01/flashdevelop-306-flex-sdk-35.html

次に、"AIR 2 beta"用のSDKを作るために、
"Flex SDK"を展開したフォルダを適当にコピーします。

そして、下記よりダウンロードして展開したSDKのフォルダで、
http://labs.adobe.com/downloads/air2.html
上記のフォルダを上書きします。

これで、"AIR 2 beta"用のSDKが完成です。

このSDKを下記の要領で、"FlashDevelop"から利用するようにします。

上部メニューの「Tool」 → 「Program Settings」で「AS3Context」を開く。
"Flex SDK Location"の項目に上記、"AIR 2 beta"用のSDKを指定。

最後に、"AIR 2 beta"アプリケーションの作成ですが、
まずは普通に、"FlashDevelop"でAIRプロジェクトを作成します。

そして、自動生成された"application.xml"のネームスペースを下記のように変更します。

<application xmlns="http://ns.adobe.com/air/application/1.5">

<application xmlns="http://ns.adobe.com/air/application/2.0beta">

以上で"AIR 2 beta"として、"FlashDevelop"上で実行までできるようになります。

とりあえず、"Native process API"を試してみよう。

--------
http://www.suz-lab.com

2010年1月27日水曜日

FlashDevelop 3.0.6 & Flex SDK 3.5

スズキです。

mixiアプリとFlash系、試したいので、開発環境を整備しました。

▼ FlashDevelop 3.0.6
http://www.flashdevelop.org/community/viewtopic.php?f=11&t=5669

▼ Flex SDK 3.5
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3

設定としては、まずはFlashDevelopとFlexSDKを連携させるために、
上部メニューの「Tool」 → 「Program Settings」の「AS3Context」で
"Flex SDK Location"の項目に上記SDKを展開したフォルダを指定しました。

次に、同じ画面の「FlashViewer」で、
"External Play Path"の項目に"<SDK_HOME>/runtimes\player\10\win\FlashPlayer.exe"、
"Movie Display Style"に"External"
そ指定しています。

ここまで設定できたら、AS3プロジェクトをコンパイルし、
"Flash Player 10"で確認できるようになります。

でも、これからAS3ってところで、動画合成系に戻ることになってしまった...

--------
http://www.suz-lab.com

"appengine ja night #5"に参加予定

スズキです。

#4に引き続き、#5にも参加申し込みしました。
http://atnd.org/events/2950

今回のセッションは下記の通りです。

> <セッション1>
> 発表者:松尾さん(@tmatsuo)
> テーマ:「App Engine アンチパターンその他」
> 内容:App Engine でやってはいけないパターンを紹介、その他で小技集の紹介をします。
>
> <セッション2>
> 発表者:ひがやすをさん(@higayasuo)
> テーマ:「Global Transaction」
> 内容:Global Transactionを理解するために必要なことを全部お話します。

今回も、前回同様、非常に楽しみです。

無事、出席できるように、仕事かたづけておかないと...

--------
http://www.suz-lab.com

"opensocial-jquery"でフィードの取得

スズキです。

このあたり、試してみました。
http://code.google.com/p/opensocial-jquery/wiki/AjaxFeed

下記のように簡単に取得&処理できます。

--------【mixi.xml】--------
...
$.getFeed("http://feeds.feedburner.com/suz-lab-blog", function(data) {
  alert(data.Title);
});
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml

データは以下のように格納されています。

--------
{
  Title: "suz-lab - blog",
  Description: "IT技術無差別ブログ",
  Link: "http://blog.suz-lab.com/",
  Author: "suz-lab",
  URL: "http://feeds.feedburner.com/suz-lab-blog",
  Entry: [
    {
      Link: "http://feedproxy.google.com/~r/suz-lab-...",
      Date: 1264570807763,
      Title: "\"Low-level API\"でデータストアにオブジェクトを格納",
      Summary: ""
    },
    {
      Link: "http://feedproxy.google.com/~r/suz-lab-...",
      Date: 1264490290092,
      Title: "\"opensocial-jquery\"でアプリケーションのデータ取得と保存",
      Summary: ""
    },
    ...
  ]
}
--------

Ajax系のだいたい確認はできたけど、
OAuthリクエストを認証はしっかりとやっておきたいなー。

--------
http://www.suz-lab.com

"Low-level API"でデータストアにオブジェクトを格納

スズキです。

下記記事を参考にしています。
http://gae.g.hatena.ne.jp/a-know/20091029/1256825590

Blog型として、データストアにオブジェクトを格納するのですが、
シリアライズ/デシリアライズに、
ObjectInput/OutputStream、ByteArrayInput/OutputStream
を利用しています。

▼ オブジェクトのSET
--------【Java】--------
...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.reset();
oos.writeObject(<Serializableなオブジェクト>);
oos.flush();
entity.setProperty("value", new Blob(baos.toByteArray())); // オブジェクトのSET
oos.close();
baos.close();
...
--------

▼ オブジェクトのGET
--------【Java】--------
...
Blob blob = (Blob)entity.getProperty("value");
ByteArrayInputStream bais = new ByteArrayInputStream(blob.getBytes());
ObjectInputStream ois = new ObjectInputStream(bais);
(<Serializableなオブジェクト>)ois.readObject()); // オブジェクトのGET
ois.close();
bais.close();
...
--------

データストアも、結構、こなれてきたぞ...

--------
http://www.suz-lab.com

2010年1月26日火曜日

"opensocial-jquery"でアプリケーションのデータ取得と保存

スズキです。

今回は、こちらを試してみました。
http://code.google.com/p/opensocial-jquery/wiki/AppData

カウンターを例にした形のコードにしています。

--------【mixi.xml】--------
// 変数定義
var owner_id = "";
var counter = 0;

// (1) オーナー情報の取得
$.getData("/people/@owner/@self", function(data) {
  owner_id = data[0].id;
});

// (2) カウンター情報の取得
$.getData("/appdata/@viewer/@self", function(data) {
  counter = data[owner_id].counter;
});

// (3) カウントアップ
$.postData("/appdata/@viewer/@self", {
  counter: counter + 1
}, function() {
  counter++;
});
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=145

(0) "opensocial-jquery-1.3.2.5.min.js"の読み込みは当然として...

(1) まずオーナー情報を取得していますが、
これは、次のアプリケーションのデータを取得するときに、IDが必要になるためです。

(2) 上記のオーナー IDを利用して、アプリケーションのデータを取得しています。

(3) データの保存に関しては、オーナIDは使いません。

最後にパーミッションまわり確認したら、mixiアプリの予習は終了かな?

--------
http://www.suz-lab.com

EC2でOracle

スズキです。

EC2でOracleを試してみました。

AMIを「oracle」で調べると、それっぽいインスタンスがいっぱい出てきます。
今回は、たぶんOracle社が提供してくれてるであろう、下記イメージを利用してみました。
oracle-corporation/database-ami/32-bit/oracle_10g_XE_32Bit-image.manifest.xml

といっても、Elasticfoxでイメージ選択して、インスタンス起動して、
起動したら、sshでログインするだけですが...

最初のログインは下記のように、いろいろと対話的に、入力しなければいけません。

▼ ライセンスに同意して下さい。
To accept the agreements, enter 'y', otherwise enter 'n'.
Do you accept? [y/n]: y

▼ oracleユーザー(Linux)のパスワードを変えて下さい。
Changing password for user oracle.
New UNIX password:
Retype new UNIX password:

▼ SYSユーザー(Oracle)のパスワードを変えて下さい。
SYS (Database Administrative Account) Password:
Confirm SYS password:

▼ SYSTEMユーザー(Oracle)のパスワードを変えて下さい。
SYSTEM (Database Administrative Account) Password:
Confirm SYSTEM password:

とりあえず、どんなOS(Linux)か?

$ cat /etc/redhat-release
--------
Enterprise Linux Enterprise Linux Server release 5.1 (Carthage)

Oracleにログイン!

$ su - oracle
$ sqlplus system/<YOUR_PASSWORD>
--------
SQL*Plus: Release 10.2.0.1.0 - Production on Tue Jan 26 00:35:33 2010
Copyright (c) 1982, 2005, Oracle. All rights reserved.
Connected to:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production

ライセンスに関しては、こちらで簡単に紹介されています。
http://d.hatena.ne.jp/c9katayama/20090107/1231294969

「Oracle Master Platinum (9i)」なのに、ログインのやり方も忘れてた...

--------
http://www.suz-lab.com

"prettyPrint.js"でPHPの"var_dump"みたいなこと

スズキです。

JavaScriptやってると、PHPの"var_dump"のように
オブジェクトの中身がどうなってるか、確認したくなる時が多々あると思います。

そういう場合は、下記の"prettyPrint.js"が使えます。
http://github.com/jamespadolsey/prettyPrint.js


使い方ですが、まず、上記サイトのJavaScriptを自分のプロジェクトにおき、
下記のようにHTML上で読み込ませます。

<script type="text/javascript"
  src="http://feed.suz-lab.com/other/js/ext/prettyprint.js">
</script>

次に、結果を表示する領域を例えば下記のようにHTML上に用意します。

<div id="debug"></div>

最後に、以下のJavaScript(jQueryも利用)で上記の領域に結果を表示するだけです。

$("#debug").html(prettyPrint(data));

※ "data"が対象のオブジェクトです。

「1日2エントリ」ペース、継続中!

--------
http://www.suz-lab.com

2010年1月25日月曜日

"opensocial-jquery.minimessage"を使ってミニメッセージを表示(mixiアプリ)

スズキです。

今度は、こちらを参考にしています。
http://code.google.com/p/opensocial-jquery/wiki/PluginMiniMessage

--------【mixi.xml】--------
...
<ModulePrefs title="Hello, Friends!">
  <Require feature="opensocial-0.8" />
  <Require feature="minimessage" />
</ModulePrefs>
...
<script type="text/javascript"
   src="http://scripts.lrlab.to/opensocial-jquery.minimessage-1.0.0.min.js">
</script>
...
<script type="text/javascript">
...
  $('<span/>').text('Dismissible Message').minimessage();
...
</script>
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=143

ミニメッセージは下記のように非常に簡単です。

(1) ModulePrefs内に下記を追加
<Require feature="minimessage" />

(2) "opensocial-jquery.minimessage-1.0.0.min.js"を読み込み

(3) 作成したタグに対して、"minimessage"メソッドでメッセージを表示

こうすることで、画面上部にミニメッセージが表示されます。


前半戦終了...

--------
http://www.suz-lab.com

"opensocial-jquery"を使ってメッセージの送信(mixiアプリ)

スズキです。

こちらにで紹介されているやり方を、実際に試してみました。
http://code.google.com/p/opensocial-jquery/wiki/Message

--------【mixi.xml】--------
...
<input id="title" type="text"/><br/>
<textarea id="body"></textarea><br/>
<input id="send" type="button" value="送信"/>
...
<script type="text/javascript"
  src="http://scripts.lrlab.to/opensocial-jquery-1.3.2.5.min.js">
</script>
...
// メッセージの送信
$("#send").click(function(){
  $.postData("/messages/@viewer/@outbox", {
    recipients: $("input:radio:checked").val(),
    title: $("#title").val(),
    body: $("#body").val()
  }, function() {
    alert("送信しました。");
  });
});
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=142

まずは準備です。

(1) タイトル、本文を入力するHTMLフォームを用意

(2) "opensocial-jquery-1.3.2.5.min.js"を読み込み

準備ができたら実際に送信です。

(3) "url"に"/messages/@viewer/@outbox"、
パラメータに"recipients"、"title"、"body"を指定して実行します。

実行すると、メッセージ入力ダイアログが出てきて、
そこの送信ボタンを押すことで、最終的にメッセージが送信されます。

ということで、機械的に、繰り返しで、メッセージを送ることは、できないと思います。

次はミニメッセージ...

--------
http://www.suz-lab.com

"opensocial-jquery.mixi"を使ってmixiアプリにマイミクを招待

スズキです。

こちらを参考に試してみました。
http://code.google.com/p/opensocial-jquery/wiki/PluginMixi#%E3%83%9E%E3%82%A4%E3%83%9F%E3%82%AF%E3%82%92%E6%8B%9B%E5%BE%85%E3%81%99%E3%82%8B

--------【mixi.xml】--------
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hello, Friends!">
    <Require feature="opensocial-0.8" />
  </ModulePrefs>
  <Content type="html"><![CDATA[
    <input type="button" value="マイミクを招待" onclick="invite()"/>
    <script type="text/javascript"
      src="http://scripts.lrlab.to/opensocial-jquery-1.3.2.5.min.js">
    </script>
    <script type="text/javascript"
      src="http://scripts.lrlab.to/opensocial-jquery.mixi-1.0.0.min.js">
    </script>
    <script type="text/javascript">
      function invite() {
        // マイミクの招待
        $.invite("@friends", function(ids) {
          $.each(ids, function(i, id) {
            alert("「" + id + "」 を招待しました。");
          });
        });
      }
    </script>
  ]]></Content>
</Module>
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=141

(1) まず、"opensocial-jquery"のmixiプラグインを読み込みます。
<script type="text/javascript"
src="http://scripts.lrlab.to/opensocial-jquery.mixi-1.0.0.min.js"></script>

(2) 次に適当なイベントで、下記のようなコードを実行します。
$.invite("@friends", function(ids) {
  $.each(ids, function(i, id) {
    alert("「" + id + "」 を招待しました。");
  });
});

すると、マイミク一覧画面が表示し、適当なマイミクを選択して招待することができます。
招待が終わると、招待したマイミクのIDが取得できるので、そのIDをalertしています。

※ 予めマイミクを指定してでの招待はできないようです...

次は、メッセージ送信系、やるか。

--------
http://www.suz-lab.com

"opensocial-jquery.templates"でマイミクを表示してみた

スズキです。

こちらを参考にしています。
http://code.google.com/p/opensocial-jquery/wiki/Template

XMLはこんな感じです。

--------【mixi.xml】--------
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hello World!">
    <Require feature="opensocial-0.8" />
  </ModulePrefs>
  <Content type="html"><![CDATA[

    <h1>Hello, Friends!</h1>
    <div id="wrapper" style="display:none;">
      <div repeat="${people}" class="person">
        <img alt="${Cur.displayName}" src="${Cur.thumbnailUrl}"/>
        ${Cur.nickname}
      </div>
    </div>

    <script type="text/javascript"
      src="http://scripts.lrlab.to/opensocial-jquery-1.3.2.5.min.js">
    </script>
    <script type="text/javascript"
      src="http://scripts.lrlab.to/opensocial-jquery.templates-0.1.0.min.js">
    </script>
    <script type="text/javascript">
      jQuery(function($) {
        var url = "/people/@owner/@friends";
        var data = { fields: 'profileUrl' };
        $.getData(url, data, function(data) {
          $("#wrapper").render({ people: data }).show();
        });
      });
    </script>

  ]]></Content>
</Module>
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=140

ちゃんとドキュメントは読んでいませんが、ポイントである繰り返しのやり方は、たぶん、

$("#wrapper").render({ people: data }).show();

のrenderの引数で、名前(people)とデータ(data)を対応付けて、

<div repeat="${people}" class="person">

のrepeatで、その名前を対応付けて、

<img alt="${Cur.displayName}" src="${Cur.thumbnailUrl}"/>

のように、各々のデータはCurでアクセスする感じになるんじゃないかと思います。

次は、「mixiアプリにマイミクを招待」、だ。

--------
http://www.suz-lab.com

2010年1月23日土曜日

神は細部に宿る - God is in the details

スズキです。

誰が最初に言ったのかは、いろいろあるようですが、

> 素晴らしい芸術作品や良い仕事は細かいところをきちんと仕上げており、
> こだわったディテールこそが作品の本質を決定する。
> 何ごとも細部まで心を込めて行わなければならない
--------
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1329600523

といった意味の言葉です。

「充分に発達した科学技術は、魔法と見分けが付かない」
http://blog.suz-lab.com/2009/12/blog-post_30.html

とともに、プログラマーとして「斜め上の向こう側」のパフォーマンスを目指すには、
常に意識しておきたい言葉です。

コスト、納期の壁を乗り越えて、がんばっていこう!

--------
http://www.suz-lab.com

Javaで"var_dump"(PHP)や"Data::Dumper"(Perl)みたいなこと

スズキです。

"Apache Commons Lang"(http://commons.apache.org/lang/)
"ToStringBuilder#reflectionToString(Object object)"で可能です。
http://commons.apache.org/lang/api-release/org/apache/commons/lang/builder/ToStringBuilder.html#reflectionToString%28java.lang.Object%29

以下のように利用してみると、

System.out.println(ToStringBuilder.reflectionToString(this);

フィールドの中身が出力されます。(入れ子もOK)

今までは、場当たりダンプだったけど、そろそろ、ログまわりとからめないとなー。

--------
http://www.suz-lab.com

"opensocial-jquery"を使ってマイミクの表示

スズキです。

jQueryがかなり好きなんですが、mixiアプリは結局JavaScriptなんだから、
jQuery使えないかなー?、と探してみたら、"opensocial-jquery"なんてものがありました。
http://code.google.com/p/opensocial-jquery/

さっそく、これを使って、マイミクを表示(alert)するmixiアプリを作ってみました。

--------【mixi.xml】--------
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hello World!">
    <Require feature="opensocial-0.8" />
  </ModulePrefs>
  <Content type="html"><![CDATA[
    <script type="text/javascript"
      src="http://scripts.lrlab.to/opensocial-jquery-1.3.2.5.min.js"></script>
    <script type="text/javascript">
      jQuery(function($) {
        var url = "/people/@owner/@friends";
        var data = { fields: 'profileUrl' };
        $.getData(url, data, function(data) {
          for(var i = 0; i < data.length; i++) {
            alert(data[i].nickname);
          }
        });
      });
    </script>
    <h1>Hello, world!</h1>
  ]]></Content>
</Module>
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=137

iPhoneやらないと...

--------
http://www.suz-lab.com

mixiアプリでマイミクのマイミクは取得できない

スズキです。

下記のような情報があったので、できるのか?、と試したら、できなかった。
http://kazu-360.cocolog-nifty.com/blog/2009/04/mixi-610a.html

"mixi Developer Center"の情報をよく読むと、以下の記述がありました。

> マイミクの取得について、任意のユーザーに対して
> mixiアプリからマイミクの一覧を取得することを行うことはできません。
> マイミクの取得は、以下の場合にのみ行うことが可能です。
>
> * 実行されているmixiアプリのOwnerのマイミク
> * 実行されているmixiアプリのViewerのマイミク
> (Viewerがそのmixiアプリをインストールしている場合に限る)
>
> 上記以外のユーザーに関しては、
> 例えそのユーザーが対象のmixiアプリをインストールしていたとしても、
> マイミクを取得することはできません。
--------
http://developer.mixi.co.jp/appli/pc/lets_enjoy_making_mixiapp/permission_model

つまり、「マイミクのマイミクは取得できない」ってことですね。

途中で仕様が変わったのかなー?

--------
http://www.suz-lab.com

GEA/J上のトランザクションのお話(リンク集)

スズキです。

下記に予定していた、"appengine ja night #4"に参加してきました。
http://blog.suz-lab.com/2009/12/appengine-ja-night-4.html

特にトランザクションのお話が、
「これから、ちゃんと考えていかないとなー」
と思っていたところなので、とても有意義な時間でした。

ありがとうございます。

ということで、記憶が新鮮なうちに、まとめておきます。
(といっても、関係情報のリンク集としてですが...)

まず、トランザクションのお話の発表資料です。

▼ Transaction Puzzlers
http://www.slideshare.net/ashigeru/ajn4

次に、発表者近辺の参考になるブログのエントリです。

【ひがやすを blog】

▼ App EngineのEntityGroupを理解しよう
http://d.hatena.ne.jp/higayasuo/20091110/1257854009

▼ App Engineのユニーク制限を正しく理解しよう
http://d.hatena.ne.jp/higayasuo/20091111/1257905482

▼ App Engineでバージョンによる楽観的排他制御
http://d.hatena.ne.jp/higayasuo/20091120/1258731010

【Song of Cloud】

▼ 分散トランザクション処理の最適化
http://songofcloud.gluegent.com/2009/11/blog-post_24.html

▼ 分散トランザクション処理の最適化
http://songofcloud.gluegent.com/2009/11/blog-post_24.html

▼ グローバルトランザクション処理のパターン
http://songofcloud.gluegent.com/2010/01/blog-post.html

発表聞いた後、上記のエントリを読みましたが、
非常に効率よく、理解できたような気がします...

--------
http://www.suz-lab.com

2010年1月22日金曜日

(FFmpegで)動画の結合

スズキです。

MPEG-1ファイルの場合、以下のように、"cat"でファイル結合するだけで、
動画を結合することができます。

$ cat input1.mpg input2.mpg > output.mpg

他のフォーマットの場合は、下記のようにFFmpegで
上記のフォーマットにしてからの話になります。
(他にも、そのまま結合できるフォーマットはあるようですが...)

$ ffmpeg -i input1.mp4 input1.mpg
$ ffmpeg -i input2.mp4 input2.mpg

なかなか、iPhoneに進めない...

--------
http://www.suz-lab.com

FFmpegで動画の切り取り

スズキです。

"-ss"で何秒目から開始するか、"-t"で何秒間切り取るか、が指定できます。

$ ffmpeg -h
...
-t duration record or transcode "duration" seconds of audio/video
-ss time_off set the start time offset
...

なので、"-ss 3 -t 5"なら、元動画の3秒目から5秒間の動画が作成されます。

せっかくなので、このオプションを利用した処理を、以前ちらっと紹介した、
FFmpeg::Commandで書いてみました。
http://blog.suz-lab.com/2010/01/theschwartz-ffmpegcommand.html

--------【Perl】--------
my $ffmpeg = FFmpeg::Command->new();

$ffmpeg->input_options({
  file => $input_file,
});

$ffmpeg->output_options({
  file => $output_file,
  format => 'flv'
});

$ffmpeg->options(
  "-ss" => 3,
  "-t" => 10
);

my $result = $ffmpeg->exec();
--------

元画像の3秒目からの10秒間を切り取り、FLVフォーマットで出力しています。

次は動画の結合だ...

--------
http://www.suz-lab.com

Migrating a CentOS S3 Based AMI to an EBS Based AMI

スズキです。

ずっとやりたかったことが、ようやくできました!

下記のような、S3ベースのAMIイメージから、
http://blog.suz-lab.com/2010/01/suz-lab-centos-ami-542_19.html
EBSベースのAMIイメージを作成しました。

AMIイメージのManifestは下記の通りです。
811118151095/suz-lab_ebs_centos-core-5.4.4
("suz-lab"で検索したら見つかると思います)

ということで作り方ですが、下記サイトの情報を参考にしました。
http://coderslike.us/2009/12/07/amazon-ec2-boot-from-ebs-and-ami-conversion/

まず適当なインスタンスを立ち上げ、ログインし、
"ec2-api-tools"が利用できるように、環境変数の設定です。

# export EC2_HOME=/opt/ec2-api-tools
# export JAVA_HOME=/usr/java/default

次に、S3上のイメージファイル群"us-east-1.ami.suz-lab.com/ami-centos-core-5.4.3.img..."
を"/mnt"以下にダウンロードします。

# ec2-download-bundle \
-a AAAAAAAA \
-s SSSSSSSS \
-k /mnt/pk.pem \
-b us-east-1.ami.suz-lab.com \
-p ami-centos-core-5.4.3.img \
-d /mnt

そして、一つのイメージにします。

# ec2-unbundle \
-k /mnt/pk.pem \
-m /mnt/ami-centos-core-5.4.3.img.manifest.xml \
-s /mnt \
-d /mnt

すると、"/mnt/ami-centos-core-5.4.3.img"といったイメージファイルが作成されます。

ここで、管理コンソールやElasticfoxで10GのEBS作って、
作業しているインスタンスに"/dev/sdb1"でアタッチしておきます。

すると、インスタンス上で"/dev/sdb1"が現れるので、以下のようにAMIイメージをコピーします。

# dd if=/mnt/ami-centos-core-5.4.3.img of=/dev/sdb1

コピーが終わったら、次のようにマウントして中身が見れる常態にします。

# mkdir /vol
# mount /dev/sdb1 /vol

その状態で、"/vol/etc/fstab"を以下のように/mntの行を削除した形に修正します。

--------【fstab】--------
/dev/sda1 / ext3 defaults 1 1
none /dev/pts devpts gid=5,mode=620 0 0
none /dev/shm tmpfs defaults 0 0
none /proc proc defaults 0 0
none /sys sysfs defaults 0 0
/dev/sda3 swap swap defaults 0 0
--------

修正が終わったら、次のようにのようにアンマウントして、

# umount /vol

管理コンソールやElasticfoxで"/dev/sdb1"をデタッチし、スナップショットを取り、
最終的には、EBSも削除します。

最後に、AMIイメージとして、スナップショットIDを登録します。

./ec2reg \
-K /mnt/pk.pem \
-C /mnt/cert.pem \
-s snap-e9a2e180 \
-a i386 \
-d suz-lab_ebs_centos-core-5.4.3 \
-n suz-lab_ebs_centos-core-5.4.3

以上で、S3ベースのAMIイメージからEBSベースのAMIイメージを作成することができました。

公開(public)したい場合は、管理コンソールにて、
右クリックの"Edit Permissions"で簡単にできます。

Enjoy!

--------
http://www.suz-lab.com

2010年1月21日木曜日

TheSchwartz & FFmpeg::Command

スズキです。

実は、TheSchwartzを利用したかったのは、近頃、FFmpegやImageMagickを使った、
静止画、動画処理の案件が多かったので、そして、その手の処理は、
処理コストが高いことが多く、本格的にキューイングシステム使いたいなーって理由です。

せっかくなので、下記のように、WorkerManagerを使った形で利用するところまで
もってきました。
http://blog.suz-lab.com/2010/01/macworkermanagerfor-theschwartz.html
(実は今日、なんとWorkerManager作った人とお会いできました)

ということで、実際にFFmpeg(FFmpeg::Command)の処理をしてみます。

まずは、(WorkerManagerの)Workerから。

--------【FFmpeg.pm】--------
package SuzLab::Worker::FFmpeg;
use strict;
use warnings;
use base qw( TheSchwartz::Worker );
use TheSchwartz::Job;
use FFmpeg::Command;

sub work {

  my $class = shift;
  my TheSchwartz::Job $job = shift;

  my $input_file = $job->arg->{input_file};
  my $output_file = $job->arg->{output_file};

  print $input_file . "\n";
  print $output_file . "\n";

  my $ffmpeg = FFmpeg::Command->new();

  $ffmpeg->input_options({
    file => $input_file,
  });

  $ffmpeg->output_options({
    file => $output_file,
    device => 'ipod',
  });

  my $result = $ffmpeg->exec();

  if($result){
    print "Processed\n";
    $job->completed();
  } else {
    print $ffmpeg->errstr . "\n";
    $job->failed($ffmpeg->errstr);
  }

}

sub max_retries { 0 }
sub retry_delay { 0 }

1;
--------

次に、(WorkerManagerの)Client。

--------【client.pl】--------
#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;
use lib File::Spec->catdir($FindBin::Bin, '..', 'lib', 'perl');
use lib File::Spec->catdir($FindBin::Bin, '..', 'sbin', 'workermanager', 'lib');

use WorkerManager::Client::TheSchwartz;

my $client = WorkerManager::Client::TheSchwartz->new({
  "dsn" => "dbi:mysql:theschwartz",
  "user" => "theschwartz",
  "pass" => "xxxxxxxx"
});

$client->insert('SuzLab::Worker::FFmpeg' => {
  "input_file" => "/Users/suzuki/Dropbox/suz-lab/common/tmp/input.mp4",
  "output_file" => "/Users/suzuki/Dropbox/suz-lab/common/tmp/output.mp4"
});
--------

あとは、FFmpegやImageMagickのお話です...

--------
http://www.suz-lab.com

2010年1月20日水曜日

EC2の"us-east-1"と"us-west-1"をab(apache)で比較

スズキです。

一回、計測しておきたかったので... やっちゃいました。

対象は、EC2のus-east-1、us-east-1、そして、日本のサーバです。
計測したコンテンツは、Apacheをインストールした直後のデフォルトページで、
以下のコマンド(apacheのab)を利用しています。
$ ab -n 100 -c 10 http://xxx.xxx.xxx/

比較ポイントは下記のような感じでしょうか?

us-east-1 / us-west-1 / japan
--------
Requests per second [#/sec]:   14.73 /    22.76  /   91.19
Time per request [ms]      : 678.723 /  439.380  / 109.662
Transfer rate [Kbytes/sec] :   75.39 /   122.41  /  466.81

んー... 割合みて、

- "us-west-1"は"us-east-1"より1.6倍速い
- "japan"は"us-east-1"より6.2倍速い
- "japan"は"us-west-1"より4.0倍速い

って言っちゃっていいんでしょうか...

まあ、解釈はおいておいて...
以下に実際の、それぞれの出力結果をのせておきます。

▼ us-east-1

$ ab -n 100 -c 10 http://ec2-67-202-23-37.compute-1.amazonaws.com/

-------- Server Software: Apache/2.2.3 Server Hostname: ec2-67-202-23-37.compute-1.amazonaws.com Server Port: 80

Document Path: / Document Length: 5043 bytes

Concurrency Level: 10 Time taken for tests: 6.787 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Non-2xx responses: 100 Total transferred: 524000 bytes HTML transferred: 504300 bytes Requests per second: 14.73 [#/sec] (mean) Time per request: 678.723 [ms] (mean) Time per request: 67.872 [ms] (mean, across all concurrent requests) Transfer rate: 75.39 [Kbytes/sec] received

Connection Times (ms) min mean[+/-sd] median max Connect: 206 212 3.2 212 223 Processing: 417 440 61.6 430 846 Waiting: 208 225 61.2 214 628 Total: 622 652 62.0 643 1059

Percentage of the requests served within a certain time (ms) 50% 643 66% 645 75% 647 80% 648 90% 656 95% 662 98% 1054 99% 1059 100% 1059 (longest request)

▼ us-west-1

$ ab -n 100 -c 10 http://ec2-204-236-167-225.us-west-1.compute.amazonaws.com/

-------- Server Software: Apache/2.2.3 Server Hostname: ec2-204-236-167-225.us-west-1.compute.amazonaws.com Server Port: 80

Document Path: / Document Length: 5043 bytes

Concurrency Level: 10 Time taken for tests: 4.394 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Non-2xx responses: 107 Total transferred: 550754 bytes HTML transferred: 529675 bytes Requests per second: 22.76 [#/sec] (mean) Time per request: 439.380 [ms] (mean) Time per request: 43.938 [ms] (mean, across all concurrent requests) Transfer rate: 122.41 [Kbytes/sec] received

Connection Times (ms) min mean[+/-sd] median max Connect: 129 134 3.8 134 157 Processing: 264 280 40.8 273 551 Waiting: 132 143 39.8 136 407 Total: 394 414 41.4 407 689

Percentage of the requests served within a certain time (ms) 50% 407 66% 410 75% 414 80% 415 90% 418 95% 430 98% 676 99% 689 100% 689 (longest request)

▼ japan

$ ab -n 100 -c 10 http://xxx.xxx.xxx/

Server Software: Apache/2.0.63 Server Hostname: xxx.xxx.xxx Server Port: 80

Document Path: / Document Length: 5044 bytes

Concurrency Level: 10 Time taken for tests: 1.097 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Non-2xx responses: 100 Total transferred: 524200 bytes HTML transferred: 504400 bytes Requests per second: 91.19 [#/sec] (mean) Time per request: 109.662 [ms] (mean) Time per request: 10.966 [ms] (mean, across all concurrent requests) Transfer rate: 466.81 [Kbytes/sec] received

Connection Times (ms) min mean[+/-sd] median max Connect: 16 33 18.1 23 72 Processing: 43 74 30.3 61 139 Waiting: 18 38 16.6 30 73 Total: 61 107 46.8 82 201

Percentage of the requests served within a certain time (ms) 50% 82 66% 118 75% 138 80% 180 90% 189 95% 193 98% 196 99% 201 100% 201 (longest request)

こういう内容、もっと増やしたいなー...

--------
http://www.suz-lab.com

MacでWorkerManager(for TheSchwartz)のサンプルを動かしてみた

スズキです。

MacでTheSchwartzネタ、再び、です。
http://blog.suz-lab.com/2010/01/mactheschwartz.html

今回は、こちらで紹介されているWorkerManagerを利用してみます。
http://d.hatena.ne.jp/stanaka/20091023/1256252946

とりあえず、下記から、一式ダウンロードします。
http://github.com/stanaka/WorkerManager
(右上の"Download Source"から取得できます)

展開すると、こんな感じになると思います。

README
bin/
config/
examples/
lib/
script/

ここで下記のように、必要なCPANモジュール一式をインストールします。

$ sudo cpan Proc::Daemon
$ sudo cpan File::Pid
$ sudo cpan YAML::Syck
$ sudo cpan Parallel::ForkManager
$ sudo cpan UNIVERSAL::require
$ sudo cpan Time::Piece
$ sudo cpan TheSchwartz::Simple

この状態で、"workermanager.pl"を実行すると、
エラーが無い状態でusageが表示されることが確認できます。

$ ./bin/workermanager.pl

usage: ./bin/workermanager.pl [-hdn] [-c concurrency] [-w
works_per_child] -f conf_file

-h : this (help) message
-d : debug
-n : prevent deamonize (non fork)
-c : the number of concurrency (default 4).
-w : the number of works per child process (default 100).
-f : YAML-formated file of configuration

準備が整ったら、次は、いよいよ、exampleを動かしてみます。

まず、"examples/bin/worker.conf.yml"に下記のように、DBの情報を記述します。

--------【worker.conf.yml】--------
workers:
 - Sandbox::Worker::A
 - Sandbox::Worker::B
worker_options:
 prioritize: 1
 databases:
  dsn: dbi:mysql:theschwartz
  user: theschwartz
  pass: xxxxxxxx
inc_path:
 - examples/lib
pidfile: examples/run/workermanager.pid
logfile: examples/log/workermanager.log
errorlogfile: examples/log/workermanager_error.log
--------

そして、"workermanager.pl"を以下のように実行します。

$ ./bin/workermanager.pl -n -d -f examples/bin/worker.conf.yml
2010-01-20T01:09:01 WorkerManager 0 started with PID 35247
2010-01-20T01:09:01 WorkerManager 1 started with PID 35248
2010-01-20T01:09:01 WorkerManager 2 started with PID 35249
2010-01-20T01:09:01 WorkerManager 3 started with PID 35250

これで、Workerが待ちうけ状態になったので、最後にClientです。

まあ、"examples/bin/client.pl"を実行するのですが、
以下のようにDBの接続情報も記述するようにします。

--------【client.pl】--------
#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;
use lib File::Spec->catdir($FindBin::Bin, '..', 'lib');
use lib File::Spec->catdir($FindBin::Bin, '..', '..', 'lib');

use WorkerManager::Client::TheSchwartz;
use Time::Piece;

my $client = WorkerManager::Client::TheSchwartz->new({
  "dsn" => "dbi:mysql:theschwartz",
  "user" => "theschwartz",
  "pass" => "xxxxxxxx"
});

$client->insert('Sandbox::Worker::A' => +{foo => localtime->epoch});
$client->insert('Sandbox::Worker::B' => +{foo => localtime->epoch},
{run_after => time, priority => 1});
$client->insert('Sandbox::Worker::B' => +{foo => localtime->epoch},
{run_after => time + 30});
$client->insert('Sandbox::Worker::B' => +{foo => localtime->epoch},
{run_after => time + 60});
--------

ちなみに、"lib/WorkerManager/Client/TheSchwartz.pm"の、
おそらく"dsn"のスペルが"dns"になっていたので、修正しておきました。

この状態で、下記のように実行すると、Workerが待ちうけ中のコンソールに、
いろいろと、処理内容が出力されるはずです。

$ ./examples/bin/client.pl

だんだん、TheSchwartzが、自分のものになってきたぞ!

--------
http://www.suz-lab.com

2010年1月19日火曜日

SUZ-LAB謹製 CentOS AMI アップデート (5.4.3)

スズキです。

今回は、"ec2-ami-tools"と"ec2-api-tools"を最新版にアップデートしています。

作業的には、こんな感じです。

# curl -OL http://s3.amazonaws.com/ec2-downloads/ec2-ami-tools.noarch.rpm
# rpm -Uvh ec2-ami-tools.noarch.rpm

# curl -OL http://s3.amazonaws.com/ec2-downloads/ec2-api-tools.zip
# unzip ec2-api-tools.zip

Manifestは下記となります。("suz"で検索したら、見つかると思います)

▼ us-east-1
us-east-1.ami.suz-lab.com/ami-centos-core-5.4.3.img.manifest.xml

▼ us-west-1
us-west-1.ami.suz-lab.com/ami-centos-core-5.4.3.img.manifest.xml

掲示板でもアナウンス中!
http://jbbs.livedoor.jp/bbs/read.cgi/computer/41921/1240456398/

Enjoy!

--------
http://www.suz-lab.com

SUZ-LAB謹製 CentOS AMI アップデート (5.4.2)

スズキです。

今回は、us-east-1、us-west-1、同時リリースです。

Manifestは下記となります。("suz"で検索したら、見つかると思います)

▼ us-east-1
us-east-1.ami.suz-lab.com/ami-centos-core-5.4.2.img.manifest.xml

▼ us-west-1
us-west-1.ami.suz-lab.com/ami-centos-core-5.4.2.img.manifest.xml

AMIを作成/登録するスクリプトを、us-east-1、us-west-1、
同時に行うように変更しています。

今後のバージョンアップでは、

(1) ec2-api-toolsのアップデート
(2) ec2-ami-toolsのアップデート
(3) eu-west-1でのAMI公開
(4) EBSの世代数を指定できるスナップショット作成スクリプト

に対応していこうと思ってます。

掲示板でもアナウンス中です。
http://jbbs.livedoor.jp/bbs/read.cgi/computer/41921/1240456398/

Enjoy!

--------
http://www.suz-lab.com

2010年1月18日月曜日

AMIを"us-east-1"から"us-west-1"そして公開(public)まで

スズキです。

下記を参考に自分でも試してみました。
http://mtl.recruit.co.jp/blog/2009/12/amazon_ec2amiuseastuswest.html

※ 以下は"us-east-1"上のインスタンスで実行しています。

まずは"us-east-1"のAMIファイル群を"us-west-1"に移します。

"us-east-1"の"bucket": ami.suz-lab.com (FROM)
"us-west-1"の"bucket": us-west-1.ami.suz-lab.com (TO)

※"us-east-1"と"us-west-1"で同じ"bucket"名は使えないようです。

ec2-migrate-bundle \
-k /mnt/pk.pem \
-c /mnt/cert.pem \
-a AAAAAAAA \
-s SSSSSSSS \
--bucket ami.suz-lab.com \
--manifest ami-centos-core-5.4.1.img.manifest.xml \
--location us-west-1 \
--region us-west-1 \
--destination-bucket us-west-1.ami.suz-lab.com

("ec2-migrate-bundle"は"ec2-ami-tools"のコマンドです)

次に、"us-west-1"にAMIを登録します。

ec2-register \
-K /mnt/pk.pem \
-C /mnt/cert.pem \
--region us-west-1 \
-n ami-centos-core-5.4.1 \
us-west-1.ami.suz-lab.com/ami-centos-core-5.4.1.img.manifest.xml

("ec2-register"は"ec2-api-tools"のコマンドです)

すると、AMIが作成され下記の出力が得られます。
IMAGE ami-d72c7d92

最後にいつものMacから、下記コマンドで公開(public)にします。

ec2-modify-image-attribute \
--launch-permission \
-a all \
--region us-west-1 \
ami-d72c7d92

※ やっぱり、ここでも"--region us-west-1"が必要です。

AMI作成&公開スクリプトに追加しておこう。

--------
http://www.suz-lab.com

rome.properties

スズキです。

以前、Romeのモジュールを作ったのですが、
http://blog.suz-lab.com/2010/01/romefeedburner.html
そのときに、rome.propertiesを作成したら、
もとのrome.propertiesが無視されるようになってしまい、
フィードを作成するときに、以下のようなエラーが出力されるようになってしまいました...

could not instantiate plugin com.sun.syndication.io.impl.Atom10Generator

ということで、もとのrome.propertiesもマージした形で作りなおしました。

--------【rome.properties】--------
# Feed Parser implementation classes
WireFeedParser.classes=\
com.sun.syndication.io.impl.RSS090Parser \
com.sun.syndication.io.impl.RSS091NetscapeParser \
com.sun.syndication.io.impl.RSS091UserlandParser \
com.sun.syndication.io.impl.RSS092Parser \
com.sun.syndication.io.impl.RSS093Parser \
com.sun.syndication.io.impl.RSS094Parser \
com.sun.syndication.io.impl.RSS10Parser \
com.sun.syndication.io.impl.RSS20wNSParser \
com.sun.syndication.io.impl.RSS20Parser \
com.sun.syndication.io.impl.Atom10Parser \
com.sun.syndication.io.impl.Atom03Parser

# Parsers for Atom 1.0 feed modules
atom_1.0.feed.ModuleParser.classes=\
com.sun.syndication.io.impl.SyModuleParser \
com.sun.syndication.io.impl.DCModuleParser \
suz.lab.gae.module.FeedBurnerParser

# Parsers for Atom 1.0 entry modules
atom_1.0.item.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser \
suz.lab.gae.module.FeedBurnerParser

# Parsers for Atom 0.3 feed modules
atom_0.3.feed.ModuleParser.classes=\
com.sun.syndication.io.impl.SyModuleParser \
com.sun.syndication.io.impl.DCModuleParser

# Parsers for Atom 0.3 entry modules
atom_0.3.item.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser

# Parsers for RSS 1.0 feed modules
rss_1.0.feed.ModuleParser.classes=\
com.sun.syndication.io.impl.SyModuleParser \
com.sun.syndication.io.impl.DCModuleParser

# Parsers for RSS 1.0 item modules
rss_1.0.item.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser

# Parsers for RSS 2.0 (w/NS) feed modules
rss_2.0wNS.feed.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser

# Parsers for RSS 2.0 (w/NS) item modules
rss_2.0wNS.item.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser

# Parsers for RSS 2.0 feed modules
rss_2.0.feed.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser

# Parsers for RSS 2.0 item modules
rss_2.0.item.ModuleParser.classes=\
com.sun.syndication.io.impl.DCModuleParser

# Feed Generator implementation classes
WireFeedGenerator.classes=\
com.sun.syndication.io.impl.RSS090Generator \
com.sun.syndication.io.impl.RSS091NetscapeGenerator \
com.sun.syndication.io.impl.RSS091UserlandGenerator \
com.sun.syndication.io.impl.RSS092Generator \
com.sun.syndication.io.impl.RSS093Generator \
com.sun.syndication.io.impl.RSS094Generator \
com.sun.syndication.io.impl.RSS10Generator \
com.sun.syndication.io.impl.RSS20Generator \
com.sun.syndication.io.impl.Atom10Generator \
com.sun.syndication.io.impl.Atom03Generator

# Generators for Atom 1.0 feed modules
atom_1.0.feed.ModuleGenerator.classes=\
com.sun.syndication.io.impl.SyModuleGenerator \
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for Atom 1.0 entry modules
atom_1.0.item.ModuleGenerator.classes=\
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for Atom 0.3 feed modules
atom_0.3.feed.ModuleGenerator.classes=\
com.sun.syndication.io.impl.SyModuleGenerator \
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for Atom 0.3 entry modules
atom_0.3.item.ModuleGenerator.classes=\
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for RSS 1.0 feed modules
rss_1.0.feed.ModuleGenerator.classes=\
com.sun.syndication.io.impl.SyModuleGenerator \
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for RSS_1.0 entry modules
rss_1.0.item.ModuleGenerator.classes=\
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for RSS 2.0 feed modules
rss_2.0.feed.ModuleGenerator.classes=\
com.sun.syndication.io.impl.DCModuleGenerator

# Generators for RSS_2.0 entry modules
rss_2.0.item.ModuleGenerator.classes=\
com.sun.syndication.io.impl.DCModuleGenerator

# Feed Conversor implementation classes
Converter.classes=\
com.sun.syndication.feed.synd.impl.ConverterForAtom10 \
com.sun.syndication.feed.synd.impl.ConverterForAtom03 \
com.sun.syndication.feed.synd.impl.ConverterForRSS090 \
com.sun.syndication.feed.synd.impl.ConverterForRSS091Netscape \
com.sun.syndication.feed.synd.impl.ConverterForRSS091Userland \
com.sun.syndication.feed.synd.impl.ConverterForRSS092 \
com.sun.syndication.feed.synd.impl.ConverterForRSS093 \
com.sun.syndication.feed.synd.impl.ConverterForRSS094 \
com.sun.syndication.feed.synd.impl.ConverterForRSS10 \
com.sun.syndication.feed.synd.impl.ConverterForRSS20
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/rome.properties?r=133

ちなみに、フィード作成するときに以下のように
setFeedTypesしなければいけないのですが、

SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("atom_1.0");

その引数は、rome.properties中の、"rss_2.0"や"atom_1.0"となります。

フィード関係は、得意になってきたので、そろそろPubSubHubbubもやってみようか...

--------
http://www.suz-lab.com

mixiアプリでJavaScriptを利用

スズキです。

続きです。
APIはJavaScriptで提供されているため、JavaScriptはどこに記述すればいいのか?
と調べてたら、こんな感じに書けばいいようです。

--------【mixi】--------
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hello World!">
    <Require feature="opensocial-0.8" />
  </ModulePrefs>
  <Content type="html"><![CDATA[
    <h1>Hello, world!</h1>
    <script type="text/javascript">
      alert("mixi");
    </script>
  ]]></Content>
</Module>
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=135

つまり、<Content type="html">内がHTMLと同じノリでってことですね。
Flashも使えるようだし。

よし、"suz-lab-feed"のmixiアプリ、作ろう。
http://feed.suz-lab.com/
(本体、まだできてないけど...)

--------
http://www.suz-lab.com

GAE/Jでmixiアプリ

スズキです。

といっても、GAE/Jはあまり関係ないです。
(XMLファイルの置き場所程度です)

まず、以下を参考にXMLファイルを準備します。
http://wiki.opensocial.org/index.php?title=OpenSocial_Tutorial

実際には、こんな感じで配置しています。

http://feed.suz-lab.com/other/mixi.xml
--------
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hello World!">
    <Require feature="opensocial-0.8" />
  </ModulePrefs>
  <Content type="html">
  <![CDATA[
    Hello, world!
  ]]>
  </Content>
</Module>
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/mixi.xml?r=134

ちなみに、mixiがサポートしているAPIのバージョンは、「0.8.1」です。
http://wiki.opensocial.org/index.php?title=Mixi#Container-specific_Tips

このXML(URL)を、アプリの登録時にガジェットURLとして指定します。

これで、"Hello, World!"mixiアプリの完成です。

あとは、OpenSocialの勉強か...

--------
http://www.suz-lab.com

2010年1月15日金曜日

SUZ-LAB謹製 CentOS AMI アップデート (5.4.1)

スズキです。

Manifestは下記となります。
ami.suz-lab.com/ami-centos-core-5.4.1.img.manifest.xml
("suz"で検索したら、見つかると思います)

とりあえず、下記でCentOSを5.4にアップデートしたものです。

# yum update
...
# less /etc/redhat-release
CentOS release 5.4 (Final)

掲示板でもアナウンス中です。
http://jbbs.livedoor.jp/bbs/read.cgi/computer/41921/1240456398/

Enjoy!

--------
http://www.suz-lab.com

Macで"ec2-api-tools"

スズキです。

今まで以下のようにWindowsから利用していたのですが、
http://blog.suz-lab.com/2009/04/centos53ami_20.html
近頃、Macの活用が復活してきたので、Macでもできるようにしました。

まあ、ようは、環境変数の話なので".profile"です。

--------【.profile"】--------
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
export EC2_HOME=/Users/suzuki/Dropbox/suz-lab/common/sbin/ec2/api
export EC2_PRIVATE_KEY=/.../pk.pem
export EC2_CERT=/.../cert.pem
export PATH=$EC2_HOME/bin:$PATH
--------

EC2_HOMEには、下記からダウンロード&展開したフォルダを指定しています。
http://developer.amazonwebservices.com/connect/entry.jspa?externalID=351&categoryID=88
(実は、DropBoxで共有してWindowsと同じものを利用しています...)

EC2_PRIVATE_KEY、EC2_CERT、は
Amazoの"Access Identifiers"ページから取得したものを指定します。

これで、Macからも気楽にEC2の操作ができるようになりました。

"EBS Boot AMI"もMacで作ろう。

--------
http://www.suz-lab.com

Elasticfoxで新規Regionの追加

スズキです。

Firefoxで"Amazon EC2"ができるようになるアドオン、
Elasticfoxに新規Regionを追加する方法です。
http://developer.amazonwebservices.com/connect/entry.jspa?externalID=609

といっても、左上の「Regions」ボタンを押して、
下記情報(us-west-1)を追加するだけでOKです。

Region Name: us-west-1
Endpoint URL: https://us-west-1.ec2.amazonaws.com

"EBS Boot AMI"の情報が少ないなー...

--------
http://www.suz-lab.com

「世界に向かって、やるか、やられるか!」

スズキです。

昨日、前社の社長と、かなり久しぶりにミーティングの際に会いまして、
その社長からのミーティングでの締めの言葉です。

SUZ-LAB自体は、まだ、世界に向かって何か、ってのは考えてませんが、
世界を視野に入れたときに、使ってみたい言葉です。

プロジェクトの成功を祈る...

--------
http://www.suz-lab.com

2010年1月14日木曜日

UltraVnc

スズキです。

iPhoneアプリを本格的にやる予感するので、
とりあえず、Macをリモートで利用できる環境を整えてみました。

最終的に、利用したVNCクライアントは"UltraVnc"です。
http://www.uvnc.com/

ビューアーのみでも提供されています。
http://www.uvnc.com/download/1082/1082viewer.html

まあ、ぎりぎり実用的って感じでしょうか...
(WindowsのRDTに比べて...)

レジストリ使ってるような気がしますが、
とりあえず、タグは"Portable"で...

--------
http://www.suz-lab.com

2010年1月13日水曜日

YouTubeからFLVファイルを取得

スズキです。

ちょっと調べてみました。(FireBugの"接続"を見ながら...)

まずは適当な動画表示画面にある「埋込みタグ」から、以下のようなURLを抽出します。
http://www.youtube.com/v/i6_Xi5H_3Pg&hl=ja_JP&fs=1

このURLを表示すると、プレーヤー込みの動画が表示されるのですが、
動画を再生すると、FireBugの"接続"に下記のようなURLが表示されます。
http://www.youtube.com/get_video_info?&video_id=i6_Xi5H_3Pg&el=embedded&ps=default&eurl=&hl=ja_JP

このURLの内容は以下の通りです。

--------
status=ok&vq=1&fmt_list=22%2F2000000%2F9%2F0%2F115%2C35%2F640000%2F9%2F0%2F115%2C34%2F0%2F9%2F0%2F115%2C5%2F0%2F7%2F0%2F0&author=Google&watermark=http%3A%2F%2Fs.ytimg.com%2Fyt%2Fswf%2Flogo-vfl106645.swf%2Chttp%3A%2F%2Fs.ytimg.com%2Fyt%2Fswf%2Fhdlogo-vfl100714.swf&muted=0&avg_rating=4.85185185185&video_id=i6_Xi5H_3Pg&length_seconds=16&allow_embed=1&fmt_url_map=22%7Chttp%3A%2F%2Fv24.lscache6.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Cratebypass%26itag%3D22%26ipbits%3D0%26sver%3D3%26ratebypass%3Dyes%26expire%3D1263387600%26key%3Dyt1%26signature%3DD3103501F7FA38392FCA190032F7EE1311969A03.2DF460578FD5A5E928FC10AEFA82A3E3A456923E%26id%3D8bafd78b91ffdcf8%2C35%7Chttp%3A%2F%2Fv3.lscache4.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D35%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3D59F59404937EB1F516C7A8770258C794B8933664.4448156958A3C1C7A9A9A004CEF96EB8A4D31180%26factor%3D1.25%26id%3D8bafd78b91ffdcf8%2C34%7Chttp%3A%2F%2Fv22.lscache1.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D34%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3D1332CF7CECCEB9E6C51862524500A8EC9F636AEC.B2A619D57C215E9699922EE60DAF6A0DA2B384E8%26factor%3D1.25%26id%3D8bafd78b91ffdcf8%2C5%7Chttp%3A%2F%2Fv12.lscache8.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D5%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3DB510FC1B80C090A8683E37D39E02AB3661D5090F.0E13F9F55B3DC4D8C412B37D61923680D38870AB%26factor%3D1.25%26id%3D8bafd78b91ffdcf8&fmt_stream_map=22%7Chttp%3A%2F%2Fv24.lscache6.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Cratebypass%26itag%3D22%26ipbits%3D0%26sver%3D3%26ratebypass%3Dyes%26expire%3D1263387600%26key%3Dyt1%26signature%3DD3103501F7FA38392FCA190032F7EE1311969A03.2DF460578FD5A5E928FC10AEFA82A3E3A456923E%26id%3D8bafd78b91ffdcf8%2C35%7Chttp%3A%2F%2Fv3.lscache4.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D35%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3D59F59404937EB1F516C7A8770258C794B8933664.4448156958A3C1C7A9A9A004CEF96EB8A4D31180%26factor%3D1.25%26id%3D8bafd78b91ffdcf8%2C34%7Chttp%3A%2F%2Fv22.lscache1.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D34%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3D1332CF7CECCEB9E6C51862524500A8EC9F636AEC.B2A619D57C215E9699922EE60DAF6A0DA2B384E8%26factor%3D1.25%26id%3D8bafd78b91ffdcf8%2C5%7Chttp%3A%2F%2Fv12.lscache8.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D5%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3DB510FC1B80C090A8683E37D39E02AB3661D5090F.0E13F9F55B3DC4D8C412B37D61923680D38870AB%26factor%3D1.25%26id%3D8bafd78b91ffdcf8&token=vjVQa1PpcFOx9GSRmMUYe4r_PmgPx_WlmYfx_t4Y88U%3D&thumbnail_url=http%3A%2F%2Fi2.ytimg.com%2Fvi%2Fi6_Xi5H_3Pg%2Fdefault.jpg&fmt_map=22%2F2000000%2F9%2F0%2F115%2C35%2F640000%2F9%2F0%2F115%2C34%2F0%2F9%2F0%2F115%2C5%2F0%2F7%2F0%2F0&allow_ratings=1&plid=AAR9Bb-6gyn9p9Co&keywords=Google%2Csearch%2Ctip&title=15+second+search+tip%3A+Weather&ftoken=&track_embed=0
--------

この中の、以下の部分がFLVのURLエンコードされたURLになってます。

※ 他にも似たようなURLがありますが、ダウンロードしてみると、
フォーマットがMP4だったりして、いまいちルールはわかっていません...

--------
http%3A%2F%2Fv12.lscache8.c.youtube.com%2Fvideoplayback%3Fip%3D0.0.0.0%26sparams%3Did%252Cexpire%252Cip%252Cipbits%252Citag%252Calgorithm%252Cburst%252Cfactor%26algorithm%3Dthrottle-factor%26itag%3D5%26ipbits%3D0%26burst%3D40%26sver%3D3%26expire%3D1263387600%26key%3Dyt1%26signature%3DB510FC1B80C090A8683E37D39E02AB3661D5090F.0E13F9F55B3DC4D8C412B37D61923680D38870AB%26factor%3D1.25%26id%3D8bafd78b91ffdcf8&fmt_stream_map=22
--------

これを次のサイトなどでURLデコードすると、
http://home.kendomo.net/board/decode/decode.php
次のようなFLVがダウンロードできるURLができあがります。
http://v12.lscache4.c.youtube.com/videoplayback?ip=0.0.0.0&sparams=id%2Cexpire%2Cip%2Cipbits%2Citag%2Calgorithm%2Cburst%2Cfactor&algorithm=throttle-factor&itag=34&ipbits=0&burst=40&sver=3&expire=1263387600&key=yt1&signature=CC76039244F5BAD5EAF65ECE327ED0088DD63BEC.96826CBCB1DC41A6DBA0EF67DD5AC06B32BA3B07&factor=1.25&id=2d6e973bb2b8d655&

この仕様が変わるのも、時間の問題か...

--------
http://www.suz-lab.com

3年後(H25)の年齢別人口分布と従業員の年齢分布

スズキです。

どっかで、見聞きした
「従業員の年齢分布をを年齢別人口分布に近づける」
ような話が、まだ頭の中に残っていたので、実データ使って整理してみました。

実データは下記の"年齢階級別人口及び年齢構成指数"を利用しています。
http://www.stat.go.jp/data/nenkan/02.htm

どうせなら、僕近辺の将来のことを考えたかったので、
20歳から、ナナロク世代までの3年後(H25)の年齢別人口分布の割合と、
従業員数が30人程度だった場合の、年齢別従業員数を出してみました。
(実際、こんな感じの小さめIT企業が多いのかな?、と...)

20~24歳 16% 5人
25~29歳 18% 5人
30~34歳 19% 6人
35~39歳 23% 7人
40~44歳 24% 7人

3年後、どうなってるんだろう...

--------
http://www.suz-lab.com

2010年1月12日火曜日

Memcacheの使い方(GAE/J)

スズキです。

三連休にGAE/JのDatastoreとMemcacheまわりのプログラミングをしていて、
なんとなく、自分流の使い方が見えてきたのでメモ。

(1) Low-level APIを使う。
細かいことできるし、本質が見えやすいと思ったから。

(2) Memcacheのデータはなかったら、Datastoreなどから作れるように。
まあ、当たり前ですね。

(3) Datastoreのデータは、できるだ冗長性をなくす。
画面表示情報などJOIN?が必要なものはMemcacheに持たせ、それを使う。
※検索結果などは当てはまりませんね...

ということで、特に(2)に関してですが、結局Memcache使うときは、
まず、データを取得してみて、Memcacheに値が有ればそのまま返し、
無ければDatastoreなどから値を作成し、それをMemcacheに登録したあと、返す、
ってパターンになります。

パターン化してるってことは、プログラムの枠組みが作れるってことなので、
下記のように作ってみました。

まずは、Datastoreなどから値を作成する処理を用意します。

--------【MypageUtil.java】--------
...
public static List<Map<String, Object>> getUserConditions() throws Exception {
  UserService user = UserServiceFactory.getUserService();
  DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
  Query conditionQuery = new Query("condition");
  conditionQuery.addFilter("user", FilterOperator.EQUAL, user.getCurrentUser());
  List<Map<String, Object>> userConditions = new ArrayList<Map<String,
Object>>();
  for(Entity conditionEntity :
datastore.prepare(conditionQuery).asList(Builder.withOffset(0))) {
    Entity feedEntity = datastore.get((Key)conditionEntity.getProperty("feed"));
    userConditions.add(StaticUtil.convertToMap(feedEntity));
  }
  return userConditions;
}
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/util/MypageUtil.java?r=123

そして、後述するMemcacheHelperクラスを用いて、上記処理(メソッド)を登録してから、
データを取得します。

--------【IndexHtml.java】--------
...
@Override
protected List<Map<String, Object>> createResults() throws Exception {
  return (new MemcacheHelper<List<Map<String, Object>>>(){
    @Override
    protected List<Map<String, Object>> getFromSource() throws Exception {
      return MypageUtil.getUserConditions();
    }
  }).get("condition" + this.user.getCurrentUser().getUserId());
}
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexHtml.java?r=123

実際のMemcacheHelperは下記のようになっており、
データを取得(get)するときにMemcacheに値が有ればそのまま返し、
上記の登録されたメソッドを実行し、おおもとからデータを取得し、
それをMemcacheに登録したあと値を返すって処理になってます。

--------【MemcacheHelper.java】--------
...
public abstract class MemcacheHelper<T> {

  public T get(String key) throws Exception {
    return get(key, false);
  }

  @SuppressWarnings("unchecked")
  public T get(String key, boolean forceFlag) throws Exception {
    MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();
    T value = (T)memcache.get(key);
    if(forceFlag || value == null) {
      value = this.getFromSource();
      memcache.put(key, value);
    }
    return value;
  }

  protected abstract T getFromSource() throws Exception;

}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/helper/MemcacheHelper.java?r=120

考え方やフレームワーク(っぽいもの)は、まだまだ変わっていくんだろうなー...

--------
http://www.suz-lab.com

JSONPが使える"Web API"

スズキです。

かなり適当に調べましたが、こんな感じです。

▼ Flickr
http://www.flickr.com/services/api/response.json.html

▼ リクルートWEBサービス(ホットペッパーなど)
http://webservice.recruit.co.jp/

▼ 楽天ウェブサービス
http://webservice.rakuten.co.jp/

▼ 路線/駅名/最寄駅データ
http://express.heartrails.com/

▼ 駅データ
http://www.ekidata.jp/tools/index.html

▼ delicious
http://delicious.com/help/json/

まだまだあると思うので、知ってる人、教えて下さい...


【追記】下記、早速コメントで教えてもらいました。ありがとうございます。

▼ Twitter
http://apiwiki.twitter.com/


--------
http://www.suz-lab.com

2010年1月11日月曜日

OValは配列でも検証(Validation)する

スズキです。

下記のように配列に対して検証(Validation)アノテーションを付けると、
配列中の値すべてを検証(Validation)するようになってます。

--------【IndexForm.java】--------
public static class IndexForm {

  @NotBlank
  @AssertURL
  @NotExistsOnDatastore(kind="feed", property="url", type=Link.class)
  public String url;

  @NotBlank
  public String[] tag;

}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexJson.java?r=115

そろそろ、
http://feed.suz-lab.com/
本気で作るか…

--------
http://www.suz-lab.com

2010年1月10日日曜日

MySQLでSQL文をログに出力

スズキです。

MySQLを利用したプログラムを開発する上で、
MySQL上で実行されている実際のSQLを確認しながら、
開発したい場合も多々あると思います。

そのような時は、my.cnfに下記を記述します。

--------【my.cnf】--------
...
[mysqld]
...
log=query.log
...
--------

すると、以下のように、すべてのSQLが出力されるようになります。

--------【query.log】--------
100110 14:35:21
5359 Connect theschwartz@localhost on theschwartz
5359 Query set autocommit=1
5359 Query
SELECT funcmap.funcid, funcmap.funcname
FROM funcmap
5359 Quit
5360 Connect theschwartz@localhost on theschwartz
5360 Query set autocommit=1
5360 Query
INSERT INTO job(
funcid, arg, uniqkey, insert_time, run_after, grabbed_until, priority, coalesce)
VALUES (
'2', '\0\0\0\n input.jpg\0\0\0file', NULL, '1263101721',
'1263101721', '0', NULL, NULL)
5360 Quit
--------

(上記のログはTheSchwartzにエンキューしたときのSQLです)
そろそろ、TheSchwartzのソース読むか…

--------
http://www.suz-lab.com

MacPortsで"Apache & PHP"

スズキです。

手軽にPHP関係の実験が出来る環境が欲しかったので、
MacPortsで"Apache & PHP"の環境を作ってみました。

とりあえず、下記でインストールです。(Apacheもインストールされます)

$ sudo port install php5

インストール時のメッセージに出てくるのですが、
下記のように、php.iniをコピーして作成します。
$ sudo cp /opt/local/etc/php5/php.ini-development /opt/local/etc/php5/php.ini

こちらも、インストール時のメッセージに出てくるのですが、下記のように、
PHPモジュールを使えるようにします。

$ cd /opt/local/apache2/modules
$ sudo /opt/local/apache2/bin/apxs -a -e -n "php5" libphp5.so

また、下記のようにhttpd.confを編集する必要もあります。

--------【httpd.conf】--------
...
Include conf/extra/mod_php.conf
--------

ここまで準備できたら、以下のコマンドで、Apacheのスタート&ストップができます。

$ sudo /opt/local/apache2/bin/apachectl start
$ sudo /opt/local/apache2/bin/apachectl stop
※ "sudo apachectl start"だと他のApacheが立ち上がります。

Apacheが起動できたら、以下のようなファイルを
"/opt/local/apache2/htdocs/"に置いて無事PHPの情報が表示されればOKです。

--------【phpinfo.php】--------
<?php phpinfo(); ?>
--------

PHPからTheSchwartzにエンキューしたい…

--------
http://www.suz-lab.com

RomeのFeedBurner用モジュールを作ってみた

スズキです。

トップページのニュース一覧に"はてブ数"を表示しようとしたら、
http://blog.suz-lab.com/2010/01/blog-post.html
意外と大掛かりで、RomeのFeedBurner用モジュールまで作ってしまいました。

トップページのニュース一覧はFeedBurnerのフィードを
Romeで読み込んで作っているのですが、FeedBurnerのフィードは
各記事のリンク先URLが、ブログ本体のものではなく、
http://feedproxy.google.com/~r/suz-lab-blog/~3/Ioq7XAQ4OMQ/blog-post.html
といったFeedBurnerのものになってしまっています。

当然、"はてブ数"を表示するには、ブログ本体のリンク先URLが必要になるのですが
それは、フィード中に以下のような形で記載されていました。
<feedburner:origLink>http://blog.suz-lab.com/2010/01/blog-post.html</feedburner:origLink>

当然、素のRomeでは上記の値を取得することはできないのですが、
FeedBurner用のモジュールを作成することで、取得できるようになります。

まず、モジュールクラスの作成です。ネームスペースのURIを定義して、
また、"origLink"をset/getできるようにしておきます。

--------【FeedBurnerModule.java】--------
...
@SuppressWarnings("serial")
public class FeedBurnerModule extends ModuleImpl {

  public static final String URI = "http://rssnamespace.org/feedburner/ext/1.0";
  private String origLink;

  public FeedBurnerModule() {
    super(FeedBurnerModule.class, URI);
  }

  @Override
  public void copyFrom(Object object) {
    FeedBurnerModule module = (FeedBurnerModule)object;
    this.origLink = module.getOrigLink();
  }

  @Override
  public Class<?> getInterface() {
    return FeedBurnerModule.class;
  }

  ...
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/rome/FeedBurnerModule.java?r=112

次に、パーサークラスの作成です。parseメソッドで、実際にフェード中の"origLink"の内容を、
上記のモジュールクラスにセットしています。

--------【FeedBurnerParser.java】--------
...
public class FeedBurnerParser implements ModuleParser {

  private static final Namespace NS
    = Namespace.getNamespace("feedburner", FeedBurnerModule.URI);

  @Override
  public String getNamespaceUri() {
    return FeedBurnerModule.URI;
  }

  @Override
  public Module parse(Element element) {
    FeedBurnerModule module = new FeedBurnerModule();
    Element origLink = element.getChild("origLink", NS);
    module.setOrigLink(origLink.getText());
    return module;
  }

}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/rome/FeedBurnerParser.java?r=112

最後に、プロパティファイルです。Atom(1.0)を読み込むときに、
FeedBurnerモジュールのパースもするように指定しておきます。

--------【rome.properties】--------
atom_1.0.feed.ModuleParser.classes=suz.lab.gae.rome.FeedBurnerParser
atom_1.0.item.ModuleParser.classes=suz.lab.gae.rome.FeedBurnerParser
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/rome.properties?r=112

すると、下記のような感じで、モジュールを取得することができ、
モジュール内の情報を利用することができます。

--------【index.html】--------
...
#foreach($entry in $results.entries)
<dt>$date.format("yyyy/MM/dd", $entry.publishedDate)</dt>
<dd>
  <a href="$entry.link">$entry.title</a>
  #set($module = $entry.getModule("http://rssnamespace.org/feedburner/ext/1.0"))
  <a href="http://b.hatena.ne.jp/entry/$module.origLink" target="_blank">
    <img src="http://b.hatena.ne.jp/entry/image/$module.origLink" border="0"/>
  </a>
</dd>
#end
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-apps/war/WEB-INF/vm/page/other/index.html?r=111

Romeモジュール、使えるね。

トップページのニュース一覧に"はてブ数"を表示するようにしてみた

スズキです。

SUZ-LABのトップページにはてブ数を表示するようにしてみました。
http://www.suz-lab.com/

以前Bloggerにはてブ数を表示する方法を応用すれば、瞬殺かと思いきや…
http://blog.suz-lab.com/2010/01/blogger.html

FeedBurnerのフィードをRomeで読み込んでいる関係上、
RomeのFeedBurnerモジュールまで作成してしまいました。

このあたりの話は、また後ほど…

--------
http://www.suz-lab.com

2010年1月9日土曜日

Mavenで"テストのスキップ"と"メモリ不足対策"

スズキです。

まず、テストのスキップは以下のオプションでMavenを実行すればOKです。

$ mvn -DskipTests=true install

次に、メモリ不足対策(OutOfMemoryError対策)は下記のように環境変数を設定して、
Mavenを実行です。

$ export MAVEN_OPTS=-Xmx512M

このあたり、久しぶりにMaven使うときに、いつも調べてる…

--------
http://www.suz-lab.com

2010年1月7日木曜日

ImageMagickでFisheyeエフェクト

スズキです。

下記URLに紹介されている内容を実験してみました。
http://scottpenberthy.com/2008/09/05/fisheye-effect-in-imagemagick/

以下のようなImageMagickのコマンドで実現できます。
convert -fx @fisheye.fx input.jpg output.jpg

"-fx"オプションを使うと、数式を用いてエフェクトをかけることができます。
http://www.imagemagick.org/script/fx.php
今回使っているFisheyeエフェクトは次の通りです。

--------【fisheye.fx】--------
kk=w*0.5;ll=h*0.5;dx=(i-kk);dy=(j-ll);aa=atan2(dy,dx);rr=hypot(dy,dx);rs=rr*rr/hypot(kk,ll);px=kk+rs*cos(aa);py=ll+rs*sin(aa);p{px,py}
--------
※横一行で記述しないとうまくいきませんでした…

PerlMagickだとFxがきかなかったけど、なんでだろう...

--------
http://www.suz-lab.com

MacにPerlMagick(ImageMagick)をインストール

スズキです。

"MacPorts & CPAN"で一発かと思いきや、かなりハマってしまいました。

最初に試みた方法は下記です。

$ sudo port install ImageMagick
$ sudo cpan Image::Magick

この方法だと、cpanのところでビルドエラーになってしまい、
インストールまで進みません…

で、成功したのがこちら。

$ sudo port install ImageMagick +perl

これで、ようやく、画像のリサイズなどをキューイングシステムを使って
画面アクションとは非同期で行うサンプルにとりかかれます。

--------
http://www.suz-lab.com

2010年1月6日水曜日

MacでTheSchwartz使ってみた

スズキです。

「4Gbpsを超えるWebサービス構築」の第二章(キューイング)で紹介されている、
http://www.amazon.co.jp/gp/product/4797354364?tag=iretsuzusblog-22
キューイングシステムTheSchwartzを試してみました。
(もう一つのGearmanはTheSchwartzがあればいいかな?と…)

ActiveMQなどは実業務で使ったこともありますが、LL言語などの案件で、
わざわざJava引っ張り出すのもどうかなー、と思い、試してみました。

まずはDB(MySQL)にテーブル群を作成します。
(MySQLは下記のようにMacPortsでインストールしています)
http://blog.suz-lab.com/2010/01/macportsmysql.html

ユーザーを作成して、
(myqsl)> GRANT ALL PRIVILEGES ON theschwartz.* TO
theschwartz@localhost IDENTIFIED BY 'xxxxxxxx'

データベース作って、
(bash)$ create database theschwartz character set utf8;

CREATE文探して、
(bash)$ perl -MCPAN -eshell;
cpan[1]> look TheSchwartz
(bash)$ cd doc

CREATE文実行って感じです。
(bash)$ mysql5 -u theschwartz -p theschwartz < schema.sql

次はPerlモジュールのDBD:mysqlをインストールです。

普通に
sudo cpan DBD:mysql
とすると、"mysql_config"が無い!ってエラーになってしまうので、

(bash)$ cd /opt/local/bin
(bash)$ sudo ln -s mysql_config5 mysql_config
(bash)$ sudo cpan -f DBD:mysql

とします。
※ どっちにしろテストでコケるので"forceオプション(-f)"つけときます。

そして、TheSchwartz(Perlモジュール)のインストールです。

(bash)$ sudo cpan -f TheSchwartz
※ こちらもテストでコケるので"forceオプション(-f)"です。

準備は整ったので、早速、プログラム(Perl)の作成です。

まずは、キューに溜まったジョブを処理するワーカーを作成します。

--------【worker.pl】--------
package SampleWorker;

use strict;
use warnings;
use base qw(TheSchwartz::Worker);

sub work {
  my ($class, $job) = @_;
  print("work\n");
  $job->completed();
}

package main;

use strict;
use warnings;
use TheSchwartz;

my $client = TheSchwartz->new(
  databases => [{
    dsn => "dbi:mysql:theschwartz",
    user => "theschwartz",
    pass => "xxxxxxxx"
  }],
);

print("start\n");
$client->can_do('SampleWorker');
$client->work();
--------

下記のように実行すると、処理待ち状態になります。
(bash)$ perl ./worker.pl

そして、下記のように、ジョブをキューに登録すると、
ジョブに応じたワーカーの処理が実行されます。
(bash)$ perl ./client.pl

--------【client.pl】--------
use strict;
use warnings;
use TheSchwartz;

my $client = TheSchwartz->new(
  databases => [{
    dsn => "dbi:mysql:theschwartz",
    user => "theschwartz",
    pass => "xxxxxxxx",
  }]
);

my $handle = $client->insert(
  "SampleWorker" => {}
);
--------

Webシステムの画像変換/合成処理は、これとPerlとImageMagickを...

--------
http://www.suz-lab.com

MacPortsでMySQLをインストール

スズキです。

だんだん、コンソールからしかMacを使わないようになってきました…
(せっかくのUIが…)

実は、初MacPortsです。

まずは、アップデートです。

$ sudo port selfupdate
$ sudo port upgrade outdated

そして、MySQLのインストール&設定です。

$ sudo port install mysql5-server
$ sudo -u mysql mysql_install_db5
$ sudo cp /opt/local/share/mysql5/mysql/my-small.cnf
/opt/local/etc/mysql5/my.cnf
$ sudo vi /opt/local/etc/mysql5/my.cnf
(適当に編集)

最後に起動と確認です。

$ sudo /opt/local/share/mysql5/mysql/mysql.server start
$ mysql5 -u root

TheSchwartzの準備ってことで…

--------
http://www.suz-lab.com

Mahoutを試してみた

スズキです。

Hadoopも利用出来るようになったので、
http://blog.suz-lab.com/2010/01/machadoopmap-reduce.html
次はMahoutです。
http://lucene.apache.org/mahout/

MahoutはHadoopを利用した機械学習ライブラリです。
機械学習とは… 下記を参考にして下さい…
http://ja.wikipedia.org/wiki/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92

まず、MahoutをビルドするためにMavenをインストールする必要があります。
(環境は引き続きMacです)

(1) Mavenの入手

下記より、apache-maven-x.x.x-bin.zipを入手して、
適当なフォルダ(MAVEN_HOME)に展開します。
http://maven.apache.org/download.html

(2) 環境変数の設定

MavenのパスとJAVA_HOMEを設定します。
(下記は.profileに書くとしたら、です)

--------【.profile】--------
MAVEN_HOME=/Users/suzuki/Dropbox/suz-lab/common/sbin/maven
HADOOP_HOME=/Users/suzuki/Dropbox/suz-lab/common/sbin/hadoop
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
export PATH=$MAVEN_HOME/bin:$PATH
export PATH=$HADOOP_HOME/bin:$PATH
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
--------
※ Hadoopの設定も入ってます。

Mavenの用意ができたら、いよいよMahoutです。

(3) Mahoutの入手

下記より、mahout-x.x-project.zipを入手して、適当なフォルダに展開します。
http://ftp.kddilabs.jp/infosystems/apache/lucene/mahout/

(4) Mahoutのビルド

展開したフォルダ直下で"mvn install"を実行してビルドします。
(少し時間がかかります)

(5) Mahout(サンプル)の実行

まず"$MAHOUT_HOME/examples/target"に"testdata"フォルダを作成し、
データを用意します。

データは下記よりダウンロードします。
http://kdd.ics.uci.edu/databases/synthetic_control/synthetic_control.data

次に"$MAHOUT_HOME/examples/target"直下で下記コマンドを実行します。

hadoop jar mahout-examples-x.x.job
org.apache.mahout.clustering.syntheticcontrol.kmeans.Job

するとコンソールに、それらしいログがいろいろと出力されます。

そもそも、機械学習を勉強しなくては…

--------
http://www.suz-lab.com

2010年1月5日火曜日

MacでHadoop(Map Reduce)

スズキです。

そろそろ、Hadoop(Map Reduce)やってみたいなー、と思い、
http://hadoop.apache.org/mapreduce/
でも、今さらって感じもあるので、Mahout中心で試してみようかなー、って思ってます。
http://lucene.apache.org/mahout/

ということで、まずは、Hadoopの実行環境ですが、
Windowsネイティブで動かすのは難しいようなので、
Macをで動かすことにしました。

(1) Hadoopの入手

下記より、hadoop-x.x.x.tar.gzを入手して、
適当なフォルダ(HADOOP_HOME)に展開します。
http://ftp.kddilabs.jp/infosystems/apache/hadoop/core/stable/

(2) 環境変数の設定

HadoopのパスとJAVA_HOMEを設定します。
(下記は.profileに書くとしたら、です)

--------【.profile】--------
HADOOP_HOME=/Users/suzuki/Dropbox/suz-lab/common/sbin/hadoop
export PATH=$HADOOP_HOME/bin:$PATH
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
--------

(3) Hadoop(サンプル)の実行

inputフォルダに適当なテキストファイルを置いて、wordcountを実行します。

cd $HADOOP_HOME
mkdir input
cp *.txt input/
hadoop jar hadoop-x.x.x-examples.jar wordcount input output

(4)結果の確認

outputフォルダに結果が出力されてます。

--------
"-" 2
"." 3
".." 1
"..", 1
...
--------

Macの用途を、ブラウザの表示確認以外にもみいださないと…

--------
http://www.suz-lab.com

"Blogger"に"はてなブックマーク"

スズキです。

今更ながらですが、本ブログ(Blogger)の記事に、
"はてなブックマーク"のブックマーク数とブックマークリンクを追加しました。

いろいろ調べながら、結局、テンプレートは下記のようにしました。

--------
<a expr:href='&quot;http://b.hatena.ne.jp/add?mode=confirm&amp;url=&quot;
+ data:post.url + &quot;&amp;title=&quot; + data:post.title'>
  <img expr:src='&quot;http://d.hatena.ne.jp/images/b_entry.gif&quot;'/>
</a>
<a expr:href='&quot;http://b.hatena.ne.jp/entry/&quot; +
data:post.url' target='_blank'>
  <img expr:src='&quot;http://b.hatena.ne.jp/entry/image/&quot; +
data:post.url'/>
</a>
--------

これを、
「管理ページ」 → 「レイアウト」 → 「HTMLの編集」
で、"ウィジェットのテンプレートを展開"にチェックをつけ、
適当な場所に差し込みます。

ってことで、気に入った記事があったら、気軽にブックマークして下さい。
(さっそく、セルクマしてしまった…)

--------
http://www.suz-lab.com

2010年1月4日月曜日

jQuery Validation Plugin & T2 & OVal

スズキです。

"jQuery Validation Plugin"には、remoteと呼ばれるメソッドがあり、
入力内容の有効性を逐次サーバに確認することができます。
http://docs.jquery.com/Plugins/Validation/Methods/remote#options

その、サーバ側を"T2 & OVal"でやってみよう、って試みです。(下記で実際に動いています)
http://feed.suz-lab.com/mypage/index.html
※ URL入力時に逐次サーバに有効性(Datastoreに存在しないか?)を確認しにいってます。

まず、HTMLとJavascriptはこんな感じです。

--------【index.html】--------
<form id="form" method="post" action="/mypage/index.json/addUrl">
  <input type="text" name="url" size="100"/><br />
  <input type="submit" value="登録"/><br />
  ...
</form>
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/WEB-INF/vm/page/other/mypage/index.html?r=104

--------【index.js】--------
$("#form").validate({
  rules: {
    url: {
      required: true,
      url : true,
      remote : {
        url : "/mypage/index.json/checkUrl",
        type: "post"
      }
    }
  },
  ...
});
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/war/other/js/app/mypage/index.js?r=105

remoteの項目で、アクセス先のURL(/mypage/index.json/checkUrl)と
メソッド(post)を指定しています。

アクセス先(/mypage/index.json/checkUrl)はT2で下記のように実装しており、
trueが帰ってくればOK、falseが帰ってくればNGと
"jQuery Validation Plugin"側で処理されます。

--------【IndexJson.java】--------
@Page("/other/mypage/index.json")
public class IndexJson {
...

  @Ajax
  @POST
  @ActionPath
  public Navigation checkUrl(@RequestParam("url") String url) {
    return StaticUtil.createValidationNavigation(IndexForm.class, "url", url);
  }

  public static class IndexForm {
    @NotBlank
    @AssertURL
    @NotExistsOnDatastore(kind="feed", property="url", type=Link.class)
    public String url;
  }

}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexJson.java?r=105

URLのチェックは、すでにURLの追加処理時にFormクラス(IndexForm)を用意して、
OValでバリデーションするようにしていたので、その処理を利用するようにしました。

利用するフォームクラス、対象フィールドの名前、チェックする値、を引数にして、
こんな感じに処理しています。

--------【StaticUtil.java】--------
public static Json createValidationNavigation(Class<?> form, String
field, Object value) {
  try {
    Validator validator = new Validator();
    List<ConstraintViolation> violations = validator.validateFieldValue(
      form.newInstance(),
      form.getField(field),
      value
    );
    if(violations.size() == 0) {
      return Json.convert(true);
    } else {
      return Json.convert(false);
    }
  } catch(NoSuchFieldException e) {
    return Json.convert(false);
  } catch (InstantiationException e) {
    return Json.convert(false);
  } catch (IllegalAccessException e) {
    return Json.convert(false);
  }
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/util/StaticUtil.java?r=105

冬休みを利用した、2010年に向けての予習は、これで終了です。
(本当はフィードがらみのWebサービスを作りたかったんだけど…)

--------
http://www.suz-lab.com

OValで独自アノテーションを作成

スズキです。

今回は、こちらで作成したメソッドでのバリデーション処理を、
http://blog.suz-lab.com/2010/01/oval.html
下記のような独自アノテーションで実現してみようと思います。

--------【IndexForm.java】--------
...
@NotExistsOnDatastore(kind="feed", property="url", type=Link.class)
public String url;
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexJson.java?r=102

ちなみに、上記のバリデーションはDatastore中に
すでに同じデータが無いかどうかチェックするもので、

kind: EntityのKind
property: Entityのprotertyのキー
type: Entityのprotertyの型

としています。そして、アノテーションのコードは下記となります。

--------【NotExistsOnDatastore.java】--------
...
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Constraint(checkWith=NotExistsOnDatastoreCheck.class)
public @interface NotExistsOnDatastore {
  String kind();
  String property();
  Class<?> type();
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/check/NotExistsOnDatastore.java?r=102

ポイントは、
@Constraint(checkWith=NotExistsOnDatastoreCheck.class)
でしょうか?
そして、ここで指定されているNotExistsOnDatastoreCheckが以下になります。

--------【NotExistsOnDatastoreCheck.java】--------
...
public class NotExistsOnDatastoreCheck
  extends AbstractAnnotationCheck<NotExistsOnDatastore> {

  private String kind;
  private String property;
  private Class<?> type;

  @Override
  public void configure(NotExistsOnDatastore constraintAnnotation) {
    this.kind = constraintAnnotation.kind();
    this.property = constraintAnnotation.property();
    this.type = constraintAnnotation.type();
  }

  @Override
  public boolean isSatisfied(
    Object form, Object value,
    OValContext context,
    Validator validator
  ) throws OValException {
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Query query = new Query(this.kind);
    if(type.equals(Link.class)) {
      query.setKeysOnly().addFilter(
        this.property,
        FilterOperator.EQUAL,
        new Link((String)value)
      );
    } else {
      return false;
    }
    if(datastore.prepare(query).countEntities() == 0) {
      return true;
    } else {
      return false;
    }
  }

}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/check/NotExistsOnDatastoreCheck.java?r=102

型引数として上記で作成したNotExistsOnDatastoreを指定した
AbstractAnnotationCheckクラスを継承し、
configureメソッドとisSatisfiedメソッドをオーバーライドしています。
(メソッドの役割は、まあ、名前の通りです)

と、偉そうに、いろいろ書いてきたのでうが…
実は、propertyの型がLinkのときしか対応していません…

2010年営業開始前の予習は、次の"jQuery Validation"のremote連携までかな?

--------
http://www.suz-lab.com

2010年1月3日日曜日

OValでバリデーション用メソッドを作成

スズキです。

OValでは、アノテーションで、バリデートするメソッドを指定することができます。
http://oval.sourceforge.net/userguide.html#d4e481
> You can write a method within the class that has a single parameter to receive the value
> to validate and that returns true if the constraint is satisfied and false if it is violated.

具体的には下記のように書けます。

(1) 引数が一つ(バリデーションする値が代入)で、
戻り値がboolean(OK: true, NG: false)のメソッドを作成します。

(2) 対象のフィールドに@ValidateWithMethodをつけ、
methodNameに(1)で作成したメソッドを指定し、
parameterTypeにその引数の型を指定します。

--------【IndexJson.java】--------
...
public static class IndexForm {

  @NotBlank
  @AssertURL
  @ValidateWithMethod(methodName="isUniqueUrl", parameterType=String.class)
  public String url;

  @SuppressWarnings("unused")
  private static boolean isUniqueUrl(String url) {
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Query query = new Query("feed");
    query.setKeysOnly().addFilter("url", FilterOperator.EQUAL, new Link(url));
    if(datastore.prepare(query).countEntities() == 0) {
      return true;
    } else {
      return false;
    }
  }

}
...
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexJson.java?r=98

上記では、入力されたURLに対して、
すでにDatastore(Kind=feed)に登録されてないかチェックしています。

Datastore内のユニークチェックは独自アノテーションにしよう。

--------
http://www.suz-lab.com

2010年1月2日土曜日

JSON化するときのpublicフィールド(のみ!?)対応(T2)

スズキです。

T2でOVal使うときに、pubicフィールド対応を場当たりでやったのですが、
http://blog.suz-lab.com/2009/12/ovalformresolverpublict2.html
その副作用として、JSON化時に返していたリクエストパラメータが
入らなくなってしまいました…
http://code.google.com/p/suz-lab-gae/issues/detail?id=2

ということで、今回も、かなり適当にな対応です…
っていうか、今度はpublicフィールドのみJSONになる感じです...
が、これで困ったら、また修正ってことで…

--------【ApiResponse.java】--------
public ApiResponse<P, R> createResponse(P params, ErrorInfo info) {
  this.errors = new ArrayList<String>();
  if(!info.hasError()) {
    this.params = new HashMap<String, Object>();
      for(Field field : params.getClass().getFields()) {
        try {
          this.params.put(field.getName(), field.get(params));
        } catch(IllegalAccessException e) {
          this.errors.add(e.getMessage());
        }
      }
    if(this.errors.size() == 0) {
      this.results = this.createResults();
    }
  } else {
    for(Throwable error : info.getErrors()) {
      this.errors.add(error.getMessage());
    }
  }
  return this;
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/t2/ApiResponse.java?r=95

で、こんな感じで使ってます。
(引数にfinalつけると、無名クラスのオーバーライドするメソッド中で使うことができます)

--------【ApiResponse.java】--------
@Ajax
@POST
@ActionPath
public Navigation insert(
  @Form(resolverClass=OvalFormResolver.class) final IndexJson.InsertForm form,
  ErrorInfo info
) {
  return (new ApiResponse<IndexJson.InsertForm, Map<String, Object>>() {
    @Override
    protected Map<String, Object> createResults() {
      Entity entity = new Entity("feed");
      entity.setProperty("url", new Link(form.url));
      DatastoreService datastore
        = DatastoreServiceFactory.getDatastoreService();
      datastore.put(entity);
      return GaeUtil.convertToMap(entity);
    }
  }).createResponse(form, info).toJsonNavigation();
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexJson.java?r=95

publicフィールド対応は、どっかで時間とって、しっかり実装したいなー。

--------
http://www.suz-lab.com

DatastoreのEntityをJSON化(GAE/J)

スズキです。

こんな感じで、ユーティリティメソッド作ってみました。
(マップにキーをputしなおしてるところがポイントでしょうか?)

--------【Java】--------
public static Map<String, Object> convertToMap(Entity entity) {
    Map<String, Object> map
        = new HashMap<String, Object>(entity.getProperties());
    map.put("key", entity.getKey());
    return map;
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-gae/src/suz/lab/gae/util/GaeUtil.java?r=92

こんな感じに使うと、

--------【Java】--------
@Override
protected Map<String, Object> createResults() {
    Entity entity = new Entity("feed");
    entity.setProperty("url", new Link(this.getParams().url));
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    datastore.put(entity);
    return GaeUtil.convertToMap(entity);
}
--------
http://code.google.com/p/suz-lab-gae/source/browse/trunk/suz-lab-feed/src/suz/lab/feed/page/other/mypage/IndexJson.java?r=93

こんなJSONになります。

--------【JSON】--------
{
  "errors": [],
  "params": {},
  "results": {
    "key": {
      "name": null,
      "parent": null,
      "id": 10,
      "complete": true,
      "kind": "feed"
    },
    "url": {
      "value": "http:\/\/www.suz-lab.com\/"
    }
  }
}
--------

あれ? "params"に値が入ってないぞ...
publicフィールドにしたからか... なおさなきゃ...

--------
http://www.suz-lab.com