<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
   <title>ゆめ技：ゆめみスタッフブログ</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/" />
   <link rel="self" type="application/atom+xml" href="http://yumewaza.yumemi.co.jp/atom.xml" />
   <id>tag:yumewaza.yumemi.co.jp,2012://5</id>
   <updated>2012-01-16T04:21:41Z</updated>
   <subtitle>携帯・モバイルの技術情報はもちろんその他色々な技を発信する株式会社ゆめみの社員によるブログです。</subtitle>
   <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.33-ja</generator>

<entry>
   <title>Objective-CのPropertyの解放忘れを見つけるスクリプト</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2012/01/objectivecproperty.html" />
   <id>tag:yumewaza.yumemi.co.jp,2012://5.506</id>
   
   <published>2012-01-16T04:11:03Z</published>
   <updated>2012-01-16T04:21:41Z</updated>
   
   <summary>最近は、iPhoneアプリの開発に携わっていまして、そこで「Objective-CのPropertyの解放忘れを見つけるスクリプト」を作ったのでご紹介します。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="iOS" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="109" label="iOS" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="111" label="Objective-C" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="113" label="Property" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      こんにちは、ちきん(@mokemokechicken)です。
最近は、iPhoneアプリの開発に携わっていまして、そこで「Objective-CのPropertyの解放忘れを見つけるスクリプト」を作ったのでご紹介します。

Objective-C で開発しているときに、一つ頭を悩ますのはメモリの解放周りです。
何に悩むかというと、以下の2パターンです。

(a) オブジェクトを解放し過ぎることによるアプリの異常終了
(b) オブジェクトを解放しないことによるメモリリーク

今回の話は(b)を発見する補助ツールのご紹介です。
何かのお役に立てば幸いです。

      <![CDATA[メモリリークは、アプリが落ちたりはしないので多少であればまだ許容できるのですが、
塵も積もれば・・・ということもあり、
ボディブローのようにいつかアプリのパフォーマンスに影響してくることも多いです。

私達は、オブジェクトを保持(retain)するときは、大抵property機能を使うことにしています。
しかし、そのpropertyでretainしたものは、 dealloc のときには必ず解放(release)しないといけないのですが、
結構大量にコードを書いているうちに、何かあとで付け足したりしているうちに忘れることが多いのです。
プロジェクトのソースファイルの数が1000個くらいになってくると、人力で全部調べるのは時間がかかり過ぎるので、
ちゃちゃっと調べるのが今回のスクリプトです。
ちなみに、もちろん完璧ではないので疑わしい部分を見つけるだけですが、
私のプロジェクトでは、約1000個ソースファイルがあって、50箇所くらい実際のrelease忘れが見つかりました。

<h2>使い方</h2>
<pre>
    python check_release.py &lt;root_path&gt; [&lt;exclude_class_list_filename&gt;]
    
        root_path: ソースコードのルートディレクトリ。そこから再帰的に .m .mm .h ファイルを探します。
        exclude_class_list_filename: もしチェックしたくないClassがあれば、このファイルに「1行に1クラス名」を書いて指定します。
</pre>

<h2>動作の仕様</h2>

拙い英語ですが、
<a href="https://github.com/mokemokechicken/CheckRelease/blob/master/README">https://github.com/mokemokechicken/CheckRelease/blob/master/README</a>
に書いてあるのでここでは省略します。

<h2> 実行例</h2>
試しに、私たちのプロジェクトでも使用していて、公開されている iphone-rsdb に対して実行してみます。

<pre>
# checkout tool
git clone git://github.com/mokemokechicken/CheckRelease.git
# checkout iphone-rsdb
hg clone https://code.google.com/p/iphone-rsdb/
# run tool
python CheckRelease/check_release.py iphone-rsdb 
------------------------------------- 実行結果
ETYPE1: BooksTableViewController: [items] is not released?
ETYPE1: RSDBDatabase: [databaseURL] is not released?
ETYPE1: RSDBQuery: [table] is not released?
ETYPE1: RSDBRecord: [database] is not released?
ETYPE1: RSDBRecord: [table] is not released?
ETYPE1: RSDBTable: [database] is not released?
ETYPE1: RSDBTable: [tableName] is not released?
ETYPE1: RSDBTable: [primaryKeyColumnName] is not released?
-------------------------------------
</pre>

ETYPE1というのが、解放忘れ？っぽい部分です。
後は本当に解放忘れなのか、誤検知なのか見ていけばOKというわけです。

<h2>Download</h2>

<a href="https://github.com/mokemokechicken/CheckRelease">https://github.com/mokemokechicken/CheckRelease</a>

からDownloadできます。
]]>
   </content>
</entry>
<entry>
   <title>Git-Redmine: GitのコミットとRedmineを連携する。チケット駆動開発にも。</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/08/git-redmine-integration-using-rest-api-python.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.497</id>
   
   <published>2011-08-04T00:59:38Z</published>
   <updated>2011-08-04T01:13:19Z</updated>
   
   <summary>今回は、日常のGitの利用シーンでRedmineと連携できるGit-Redmineという小さなツールを作ってみたのでご紹介します。

このツールは、Redmineが用意しているREST APIを利用しています。また、ソースコードをカスタマイズしやすいようにPythonで書いています。

コミット時に作業の流れでチケットを確認できるので、チケット駆動開発にもぴったりです。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Git" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="103" label="API" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="57" label="Git" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="70" label="Python" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="105" label="Redmine" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="107" label="REST" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[おひさしぶりです。
kaoruです。

今回は、日常のGitの利用シーンでRedmineと連携できるGit-Redmineという小さなツールを作ってみたのでご紹介します。
（ツールは後ろのリンクからダウンロードできるので、ぜひお試しください。）

作業管理にRedmine、ソースコードの管理にGitを使っているプロジェクトは最近増えてきていると思います。

Redmineのチケットを見て作業内容を確認、該当するソースコードを修正、最後にGitでコミットしますよね。

Gitでコミットする時には、コミットメッセージというのを書きます。ここには、作業内容や変更点を簡単に記録します。

でも、Redmineを使って作業管理をしているプロジェクトでは、作業内容や変更点はRedmineのチケットの内容と基本的に同じですよね。

だからコミットメッセージを入力するとき、いままで、ウインドウを２つ並べて、Redmineからコミットメッセージの入力欄へのコピペを繰り返し・・・、チケットの番号を書き加えて・・・、とういうことをしていませんでしたか？

これだと、毎回、単純な作業にうんざりだし、コピペミスも怖い。

しかも、コミットメッセージが書くのが面倒で、ついつい２，３文字で済ませちゃった経験ありませんか？

<img alt="git-redmine-nailbook3.jpg" src="http://yumewaza.yumemi.co.jp/img/postfile/git-redmine-nailbook3.jpg" width="500" height="624" />


面倒くさいことはツールで解決！

Git-Redmineを使うと、こんな問題を解決できます。

コミット時に作業の流れでチケットを確認できるので、巷で流行りのチケット駆動開発にもぴったりです。


<h4>できること</h4>

コマンドラインから、Redmineに登録されているチケットを確認できます。
Gitコミット時に、Redmineのチケットのタイトルをコミットメッセージに利用できます。

このツールは、Redmineが用意しているREST APIを利用しています。また、ソースコードをカスタマイズしやすいようにPythonで書いています。


<h4>インストール方法</h4>

1. 本ツール(Git-Redmine)のソースファイルを取得します。

(例)
<pre>$ git clone git://github.com/coiled-coil/git-redmine.git</pre>


2. git-redmineというファイルを実行パスの通った場所にコピーします。

(例)
<pre>$ cd git-redmine
$ mkdir -p $HOME/bin
$ cp src/git-redmine $HOME/bin/</pre>

git-redmineファイルの１行目にpythonのパスが書かれています。
/usr/bin/python以外を利用する場合はパスを書き換えてください。
Python 2.6 以降が必要です。CentOS5では、/usr/bin/python26 などに書き換えてください。


3. RedmineのAPIキーを設定します

APIキーは、Redmineログイン後に、個人設定の中に表示されます。（下の赤線部分）

<img alt="git-redmine-apikey.png" src="http://yumewaza.yumemi.co.jp/img/postfile/git-redmine-apikey.png" width="500" height="502" />

もし、表示されない場合はREST API機能がオフになっている可能性がありますので、
管理者にお問い合わせください。また、古いRedmineでは利用できない可能性があります。


(例)
<pre>$ git config --global redmine.apiKey xxxxxxxxxxxxxxxxxxxxxxxxxxx</pre>


4. プロジェクトのGitレポジトリに、プロジェクトのRedmineのURLを設定します。

(例)
<pre>$ cd /repos/YOUR-PROJECT
$ git config redmine.projectUrl http://SAMPLE.COM/repos/YOUR-PROJECT</pre>


<h4>使い方</h4>

1. チケットの一覧を表示する。
<pre>$ git redmine</pre>

2. あるチケットの詳細を表示する。
<pre>$ git redmine 1234</pre>

3. あるチケットの情報を利用して、コミットを行う。
<pre>$ git redmine commit 1234</pre>

4. あるチケットの情報を利用して、コミットを行う。同時にチケットの達成率を90%に更新する。
<pre>$ git redmine commit 1234 --done-ratio=90</pre>

5. あるチケットの情報を利用して、コミットを行う。同時にチケットの達成率を100%、ステータスを解決済みに更新する。
<pre>$ git redmine commit 1234 --done-ratio=100 --status=3</pre>


<h4>ソースコード</h4>

こちらにソースコードを置いています。
<a href="https://github.com/coiled-coil/git-redmine" target="_blank">https://github.com/coiled-coil/git-redmine</a>]]>
      
   </content>
</entry>
<entry>
   <title>完全公開！ソーシャルゲーム設計事例：後編</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop03.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.496</id>
   
   <published>2011-07-27T02:00:00Z</published>
   <updated>2011-07-28T04:43:09Z</updated>
   
   <summary>ゆめみにてGREEプラットフォームにおけるソーシャルアプリ携帯ゲーム「偉人伝心」のリリースをした際のシステム構築事例を今だから公開!データ保存ポリシーから冗長化構成の実例まで。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="MySQL" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="携帯/モバイル" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="101" label="ソーシャルゲーム" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<h1>はじめに</h1>
<p>
こんにちは。樽八です。<br />
この記事は、前記事「完全公開！ソーシャルゲーム設計事例：前編」の続きになります。
</p>
<p>
まだの方は是非合わせてお読みください。<br/>
○<a href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop01.html">完全公開！ソーシャルゲーム設計事例：プロローグ編</a><br/>
○<a href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop02.html">完全公開！ソーシャルゲーム設計事例：前編</a><br/>
</p>
前編では、弊社構築ソーシャルゲームにおける。コンテンツ生成Webサーバの構成とキャッシュまわりについて紹介いたしました。<br/>
いよいよ後編では、データベース構成とシステム全体の冗長化に関して記述させていただきます。<br/>
本丸であり、少々長くなりますがお付き合い下さい。<br/>
<br/>]]>
      <![CDATA[<h1>どうやって要求を達成したか：後編</h1>
<h2>データ保存レイヤの最適化について</h2>
<ul>
<li>サービスで扱う各種のデータをどういうデータとして分類したか。</li>
<li>上記のそれぞれに分類されたデータをどう扱ったのか。&nbsp;</li>
</ul>
<h3>はじめに：目標スループットからの逆算</h3>
以前の記事で、2000万PV/日を目標にという要求があったと記述しましたが、これを、単純計算で一日の秒数で割ると、231.5PV/秒という秒間アクセス数が算出されます。<br />
ピーク時のアクセスを出す必要があることから、この数値を3倍くらいしたものが適当だろう(ざっくり計算)と仮定したときに、およそ700PV/秒くらいは処理をこなせる能力が必要となります。<br />
ここで、本コンテンツはゲームコンテンツだということを考えると、ほぼ全てのページで何らかのデータ更新処理は発生しうると仮定されるため、DBへの秒間の更新トランザクション量は700commit/secが必要となります。<br />
ここまでが大前提です。<br />
<br />
ここで、CPU2コア(HT換算で4コア)で、HDDが10000rpmの能力をもったMySQLサーバがあったと仮定します。この時、InnoDBの秒間更新トランザクション量の最大は理論上およそ166commit/秒です。<br />
今回弊社で用意したDBはCPU8コア(HT換算16コア)でHDDの速度は同じでした。この時の理論上の秒間更新トランザクション量は、、、実は2コアのCPUを使ったときと同じで、166commit/秒を超える事ができないんです。（注1）<br />
シャーディング(ユーザー区分毎に別のDBを利用する方式)を利用したとしても、5セットのマスタサーバ/スレーブサーバのペアとして、大量のサーバを準備する必要があります。<br />
この時点で、単純にMySQLに全部突っ込めばいいじゃん！方式は取れない（注2）ことが決定しました。<br />
弊社では初めての採用となるのですが、いわゆるNoSQLにも手を出して、データ属性ごとに格納するサーバを分けるという大方針が出来上がりました。<br />
<blockquote style="border: #0000ff 1px solid">
注1:1分間に1万回転するHDDにデータの記載をできる回数の上限は、1分間に1万回(=秒間166回)までという制約に基づいています。<br />
データ保存ストレージとして、SSDを採用した場合や、ストレージへの遅延書き込みを有効にした場合はこの制約から逃れて、より高速の書き込みを行う事ができるようになります。<br />
注2:現在であれば、DeNA様の提供されている、HandlerSocket plugin for MySQLなどを利用するという選択も可能かと思われます。
</blockquote>
<p>
いろいろ検討した結果、最終的に、以下の構成としました。<br />
<table width="100%" style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">MySQL5.1(7/28訂正)</td><td>
<ul>
<li>シングルマスタマルチスレイブ構成</li>
<li>HDDでは心もとないので、マスタサーバのみSSDを採用(注2)</li>
<li>MySQL5.1.x(7/28訂正)環境では、マルチコア時の同時コミット性能の担保の為、InnoDB Pluginは必須</li>
<Ii>利用エンジンを選ぶことにより、トランザクション処理をさせることが可能</li>
</ul>
一つのキーから複数のレコードを検索する必要のある場合や、データの横断検索をする必要がある場合に利用しました。<br/>
また、データの信頼性が高いことから、信頼性が必要とされるデータの格納に利用しました。<br/>
KVSと比較するとどうしても書き込み性能が劣ることから、更新の多いデータに関してはできるだけ利用しないようにして全体のスループットを稼ぎました。<br/>
</td></tr><tr style="background-color: #ccffff"><td style="width: 30%">KumoFS(注1)</td><td>
<ul>
<li>数あるKVS(注3)の一種。</li>
<li>主キーを特定可能なデータに関しては参照、更新共にMySQLに対してオーダーが1桁から2桁上の回数のアクセスが可能。</li>
<li>データのインクリメント処理を行うことができないので、同時に複数スレッドから同一のデータにアクセスされたときのデータ担保はできない。</li>
<li>トランザクション処理はできない。</li>
<li>サーバを複数台用意することで、データを分散してもつ機能を備える。</li>
<li>サーバを複数台用意することで、データを多重化してもつ機能も備える。</li>
<li>運用中のサーバの追加をサポート</li>
</ul>
高速な読み書きができることから、横断検索が必要ないデータの格納に多用しました。
</td></tr><tr style="background-color: #ccffff"><td style="width: 30%">TokyoTyrant(注1)</td><td>
<ul>
<li>数あるKVSの一種。</li>
<li>トランザクション処理はできない。</li>
<li>データのインクリメント処理が可能。</li>
<li>サーバを複数台用意することで、データを多重化してもつ機能を備える。</li>
<li>サーバを複数台用意することで、参照負荷を下げる事ができるが、データは全てのサーバで同一データのコピーとなるため、<span style="color:#f00">スケールアウト要件を満たすことができない。</span></li>
<li>運用中のサーバの追加をサポート</li>
</ul>
スケールアウト要件を満たせないという、今回の要求仕様に対する致命的な欠陥があったため、後に述べるカウンタ専用のシステムとして利用しました。<br/>
</td></tr></tbody>
</table>
<br />
</p>
<blockquote style="border: #0000ff 1px solid">
注1：2010年6月調査時における比較です。KVSは進化が速く1年たった今は別物である可能性があります。<br/>
注2：シングルマスタマルチスレイブ構成の時に、マスタサーバのみHW性能を強化するというのは禁じ手ではあると思っています（更新負荷が高い時にレプリケーション遅延で身動き取れなくなるだけ）。しかし、今回に関しては過去にSSDの採用実績が無かったため、データバックアップ目的を兼ねるスレイブサーバまでSSD化してしまうことの抵抗がありました。<br />
また、本システムにおいてはスレイブサーバ側では遅延書き込みオプションを使い、上記の166commit/秒という制約から逃れています。<br />
注3：KVSとは、Key Value Storeの略で、他のプログラム言語で言う、mapや、hashのようなものです。keyを指定することで該当データに一瞬でアクセス出来るのですが、keyがわからないときにはデータへのアクセスルートが絶たれてしまいます。<br/>
<br />
</blockquote>
<p>
&nbsp;
</p>
<h3>扱うデータの特性を分類する</h3>
扱う必要のあるデータを以下の10種類のデータのどれにあたるのかを分類します。<br />
<ul>
<li>1 キャッシュ系データ</li>
<li>2 管理者作成マスタ系データ</li>
<li>3 ユーザー作成マスタデータ</li>
<li>4 ユーザーの状態データ</li>
<li>5 ユーザー所有物データ</li>
<li>6 ユーザー所有物状態データ</li>
<li>7 ログ系データ1(上限付きログモデル)</li>
<li>8 ログ系データ2(上限無し+参照せず)</li>
<li>9 ランキング処理用データ</li>
<li>10 グループバトル処理用データ</li>
</ul>
一般的なサイトであった場合には、このデータ種別には分類しきれないものもありますが、（会員の購入履歴を無制限に参照したい場合など。）今回は、ゲームであるという制約のもと、ここに分類しきれないデータはそもそも持たないという強い決意で臨みます。<br />
<blockquote style="border: #0000ff 1px solid">
注：とはいえ、アーキテクトをシンプルに保つためのこういったポリシーは運用を続けるうちにだんだんと崩れていくものです。<br />
今回は幸いにも社内案件であることもあり、エンジニアの設定したポリシーに沿った運用がやりやすい背景がありました。（代替案が受け入れやすい土壌でした。）<br />
</blockquote>
<br />
<a href="http://yumewaza.yumemi.co.jp/img/postfile/data_kind.html" onclick="window.open('http://yumewaza.yumemi.co.jp/img/postfile/data_kind.html','popup','width=1315,height=614,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="http://yumewaza.yumemi.co.jp/img/postfile/data_kind-thumb.gif" width="500" height="233" alt="" /></a><br/>
<a href="http://yumewaza.yumemi.co.jp/img/postfile/deta_group.html" onclick="window.open('http://yumewaza.yumemi.co.jp/img/postfile/deta_group.html','popup','width=1025,height=533,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="http://yumewaza.yumemi.co.jp/img/postfile/deta_group-thumb.jpg" width="500" height="260" alt="" /></a>

<h4>1 キャッシュ系データ</h4>
SNSプラットフォーム側に所属するデータのキャッシュや一時的なセッションデータをここに分類しました。<br/>
参照頻度や、挿入頻度は比較的多いデータではあるのですが、重要な特性として、「時間経過とともに自動的に失われても構わない。」(むしろ、短期間しか意味のあるデータではない。)という特性を持つため、memcache上に保存するデータとしました。<br/>
<br/>
<h4>2 管理者作成マスタ系データ</h4>
管理者が作成する、ゲームの設定データや、アイテムデータ、カードマスタデータ、ショップ商品データなどが該当します。<br/>
これらのデータはユーザーから横断検索をされるデータとなる(ショップ商品一覧の検索)上、管理者の管理が行い易い必要もあります。<br/>
さらにデータ更新時にはトランザクション処理を行い、安全に更新をさせたいデータです。<br/>
これらの要件を満たすため、このデータはMySQL上で扱うこととしました。<br/>
<br/>
<h4>3 ユーザー作成マスタデータ</h4>
ユーザーが作成するデータの中でも特に、○○マスタと言われる系統のデータです。<br/>
具体的には、会員マスタとなりますが、これはマスタという名前をもつトランザクションデータであり、どちらかと言えばイベントデータに分類されても良いデータになります。<br/>
会員管理用に横断検索をする必要もあり、MySQL上で扱うこととしました。<br/>
このデータには会員の基本属性のうち、「容易に変動しない属性」のみを扱います。この制約をつけることにより、データの更新頻度を低く抑えることができます。<br/>
<br/>
<h4>4 ユーザーの状態データ</h4>
例として、会員のログイン状態、保有ポイントや、残り行動力、HP、攻撃力、防御力、各種の一時的なボーナスパラメータなどが含まれます。<br/>
DB設計を進める際に、ユーザーと1:1で対応する状態系のデータは全て会員マスタテーブルに格納したくなってきます。しかし、ここではぐっと我慢して別のテーブルに切りだしてしまいます。<br/>
これらのデータは入会時に一度作成された後は新規に挿入されることは少ないのですが、ユーザーのアクション毎に更新されますし、参照頻度も非常に多いデータになります。そのため参照・更新性能の高いKVS(KumoFS)への保存を行いました。<br/>
KVSへの保存Keyは、データ種別と会員IDを組み合わせた形で生成することができます。<br/>
<br/>
<h4>5 ユーザー所有物データ</h4>
ここでは会員の保有するカードやアイテム、称号など、会員マスタと管理者作成マスタデータの間の対照表となる系統のデータ全般を指します。<br/>
上記の例を具体例にしますと、<br/>
<ul>
<li>会員マスタとカードマスタをつなぐ「会員保有カード」</li>
<li>会員マスタとアイテムマスタをつなぐ「会員保有アイテム」</li>
<li>会員マスタと称号マスタをつなぐ「会員保有称号」</li>
</ul>
などになります。<br/>
これらのデータ会員IDをキーにごっそり関連データを引っ張ってくるという使い方がメインなのですが、会員一人当たりのデータ数の上限を規定できないデータとなります。<br/>
このことにより、保存場所としては出来ればMySQLに置きたいデータとなります。(KVSだと、主キーではない会員IDをキーにして複数のデータを同時に取得することができない。)<br/>
ここでデータの種類によっては更新頻度が多いデータもあるのですが、ここではMySQLに保存させるため、データ中の更新が多い部分を次に述べる「ユーザー所有物状態データ」として分離することとしました。<br/>
<br/>
<h4>6 ユーザー所有物状態データ</h4>
先の例における、会員保有アイテムのうち、更新頻度が大きいデータを切り出したデータとなります。<br/>
具体的には、残りアイテム保有数などになります。<br/>
このデータの主キーは上位のテーブルの発行した主キーとし、KVS上に保存することとしました。<br/>
<br/>
<h4>7 ログ系データ1(上限付きログモデル)</h4>
<h5>○他の会員から挨拶されたログを期間無制限で参照したい。</h5>
という要件があったとします。<br/>
この要件に答える解の一つは、「MySQLに突っ込む」かと思われます。<br/>
しかしながらこの解はデータの一方的な肥大化および、挿入頻度が高いという2つの問題を含んでいます。<br/>
そこで、エンジニアとしてはなんらかの制約をつけさせてもらうという提案をしたいところです。<br/>
<h5>○他の会員から挨拶されたログを過去2週間分参照したい。</h5>
この提案を了承された場合は、「MySQLに突っ込んでおいて、バッチで定期削除する」という解も考えられますが、バッチ削除時の負荷など面倒がまだまだ残ります。<br/>
この場合は、「MySQLのパーティショニングテーブルに突っ込む」が最適解と思われます。<br/>
パーティショニングテーブルに突っ込むことにより、不必要な期間のバッチでのデータ移動、削除コストが上がります。また、検索性能も該当パーティションのみの検索で済むというメリットがあります。<br/>
しかし、削除バッチが必要であることは同じですし、パーティション作成バッチまで必要になってきます。つまりまだまだ面倒ではあります。<br/>
また、MySQLに対するデータ挿入量は変わらない。という問題が残っていますし、過去2週間分のデータ量が人にってまちまちになるという問題もあります。<br/>
<br/>
やはり、ゲーム的に一番スマートな制約をつけるとすると、<br/>
<h5>○他の会員から挨拶されたログを直近100件分参照したい。</h5>
という要件になるかと思います。<br/>
全てのユーザーに対して同じ量のログを見せることができるし、機能要件としてはいい落とし所ではないでしょうか。<br/>
結果として、参照される可能性のあるログは全てこちらの形式での処理としました。<br/>
<br/>
この要件に対して、MySQLで解決しようとすると、以下の2つの方法が考えられます。<br/>
1. 一旦MySQL上に残しておいたログから、ユーザー毎に100件を超えた古いログをバッチで定期削除する。<br/>
2. ユーザーのアクション発生時に新たなログを追加する、その上で自分のログの件数をチェックして、100件を超えていた場合には古い方のログから削除する。<br/>
1. に関してはバッチの実行コストを考えたときに恐ろしいことになりそうです。(会員数100万人オーダとしたとき。)<br/>
MySQLで解決するという前提では2.が正解ではあるのですが、1度のアクションで2回の更新が走ってしまうし、検索コストも掛かってきますので、正直悩ましい処理です。<br/>
<br/>
そこで、弊社ではこの要件をTokyoTyrantとKumoFSの組み合わせで対応することにしました。<br/>
具体的な手法を例示します。<br/>
<pre>
TokyoTyrantとKumoFSの組み合わせで<span style="color:#00f">○他の会員から挨拶されたログを直近100件分参照したい。</span> という要件に答えた方法

■準備
　対象会員へのN回目の挨拶を格納したデータというものが存在するとして、そのKeyを対象会員IDとNを組み合わせて作成する。(例：会員ID=9999 1000回目の挨拶だと、aisatsu_9999_1000など)

■ある会員が挨拶されたときの処理
　1. 対象会員が挨拶された回数をTokyoTyrantでカウントアップし、その回数を取得する。(注1)
　2. 1で取得した回数でKeyを作り、そのキーでKumoFSに挨拶ログを残す。
　3. 1で取得した回数から100回前のデータは削除対象のデータなので、100少ないキーを作成し、そのキーでKumoFS上の挨拶ログを消す。

■ある会員の直近10件の挨拶を取得する処理
　1. 対象会員が挨拶された回数をTokyoTyrantから取得する。
　2. 1で取得した回数をNとすると、(N-9, N-8, N-7, N-6,,, N-1, N)に対応する10個のキーを生成する。
　3. 2で生成したキーに対してKumoFS上のデータをマルチゲット(注2)で参照する。
</pre>
この手法をモデル化したことにより、「直近○○件のログを参照前提で残す」という要件に対しては、KVS上で非常に高速に処理することができました。<br/>
参照、更新共に高速に処理でき、バッチ処理も必要としません。<br/>
<br/>
<blockquote style="border: #0000ff 1px solid">
注1:複数のユーザーが同時にカウントする可能性があるので、TokyoTyrantでのインクリメント処理を用いて、カウント漏れが発生しないようにしています。<br/>
KumoFSをカウンタに利用してしまうと、インクリメント処理ができず、データ取得→データ変更→データ更新の流れをとるため、同時アクセス時にカウント漏れが発生する可能性があります。<br/>
注2:複数のキーを同時に問い合わせし、結果を同時に取得することができます。この場合、1回の問い合わせの実行コストはほとんど増大せずに同時に複数の結果を取得することが可能です。
</blockquote>
<br/>
<h4>8 ログ系データ2(上限無し+参照せず)</h4>
ここでは課金系のログやポイントの増減ログなどの重要データを扱います。<br/>
重要データであるため、記入頻度は少ないのですが、できるだけ信頼出来る形で、そして複数箇所に取得しておきたいデータです。<br/>
また、先程の要件に対して今度はデータ保有個数の上限がありません。<br/>
上限がないということは逆に、いつどのタイミングで消すかが規定されていないだけで、わからないということでもあります。<br/>
ただし、今度のデータは「ユーザーから参照されることはない」という強力な制約を付けています。<br/>
そのため、これらのデータをMySQLとApacheから吐き出すアプリケーションログの両方に吐き出させることにしました。<br/>
MySQL上のデータであってもユーザーからの参照を考える必要がありませんので肥大化を強く気にする必要がありませんし、インデックスを付ける必要もありません。<br/>
<br/>
<h4>9 ランキング処理用データ</h4>
全ユーザーを対象としたランキングの処理は、非常に面倒なデータ処理の一つです。<br/>
リアルタイムで計算させるためには、全員のスコアを横断検索、ソートさせる必要がありますし、バッチで処理させた場合にはリアルタイムな結果を見せることができなくなりますし、そもそもそのバッチ処理させる為の仕組みを準備する必要があります。<br/>
この面倒な処理に対して、下記の2つの制約を付けることによりリアルタイムでの処理が可能なようにしました。<br/>
<ul>
<li>ランキングを表示する人数を限定する。<br/>(上位100件など)</li>
<li>ランキング対象となるスコアは増加するが減少しない<br/>(ユーザーのアクションにより、ランクインすることはあるが、ランクアウトはしない)</li>
</ul>
<br/>
<pre>
<span style="color:#00f">○上位100人のランキングをリアルタイムで参照したい</span> という要件に答えた方法

■準備
　スコアの高い人のみを格納するランキング者テーブルを一つ準備する。
　ランキング者テーブルにはスコアは記載されているが順位は記載されていない。

■ある人がスコアを獲得した場合
　対象者がそもそも上記のランキング者テーブルに含まれていた場合
　　→対象者のスコアを書き換える
　対象者がランキング者テーブルに含まれていなかった場合
　　→ランキング者テーブルのレコードが100件以下だった場合は無条件で追加する。
　　→ランキング者テーブル中の最低スコアを超えた場合は入れ替えを行う

■ランキングを表示したい場合
　上記、ランキング者テーブルをスコアでソートして表示する。

</pre>
<br/>
この方法を用いると、参照頻度こそ高いものの更新頻度に関しては上位の100人付近のアクションによってしか更新されないという制約がつくため、かなり低くなりMySQLで簡単に扱うことのできるデータとなります。<br/>
また、データ総量も非常に少なく抑えられますので、参照にソート作業を伴うとはいえ、システム的な負荷が問題になることはありません。<br/>
<br/>
<h4>10 グループバトル処理用データ</h4>
これは、S-in後しばらくして導入された5vs5のグループバトル専用のデータ種別となりました。<br/>
今回導入したグループバトルは1回10分で敵味方入り乱れて戦うゲームです。<br/>
グループバトルでは、データの信頼性は低くても構わないものの、ユーザーを短期間のバトルに合わせて誘導させたことと、グループの戦況全てを同時に扱う必要があったため、更新頻度、参照頻度共に高く、かつ複雑な形式のデータを扱わなければなりませんでした。<br/>
そのため、それまでの9つの分類とは別に、最悪、飛んでしまってもごめんなさい。というスタンスでMySQLのmemoryストレージエンジンを利用したテーブルを利用し、バトルイベント毎に破棄するという手法を取りました。<br/>
<br/>
全てオンメモリで動作するために、ストレージの性能に左右されず、また複数の検索キーを貼った上での横断検索も可能という特性を持ちます。<br/>
<blockquote style="border: #0000ff 1px solid">
memoryストレージエンジンを利用する場合、max_heap_table_size システム変数を規模に応じて大きくさせなければならないのですが、マスタサーバ側で行った変更はスレイブサーバにはレプリケーションされないことに注意する必要があります。
</blockquote><br/>
<br/>
<h2>各サーバの冗長化<span style="color:#00F">(+負荷分散)</span>について</h2>
<p>
最後になりますが、今回のサービスにおいて利用したサーバそれぞれにおける冗長化についての簡単な説明をして終わりたいと思います。<br/>
各サーバに関して、全ての場所を冗長化しており、偶発的に発生したサーバの単体トラブルがサービス停止や以降のサービス提供不能な事態を引き起こさないようにされていました。<br/>
</p>
<a href="http://yumewaza.yumemi.co.jp/img/postfile/servers.html" onclick="window.open('http://yumewaza.yumemi.co.jp/img/postfile/servers.html','popup','width=813,height=813,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="http://yumewaza.yumemi.co.jp/img/postfile/servers-thumb.jpg" width="500" height="500" alt="" /></a>
<ul>
<li>ロードバランサ<br/>商用ロードバランサをHAホットスタンバイ構成(注1)で2台並べました。</li>
<li>リバースプロクシ<br/>squidサーバを並列(注2)に2台並べました。<br/>ロードバランサから死活監視されており、死亡時は切り離されます。</li>
<li>XHTML生成Webサーバ。<br/>S-in時、Webサーバを並列に6台並べました。<br/>ロードバランサから死活監視されており、死亡時は切り離されます。<br/>また、サービス運用中のバージョンアップにおいてはロードバランサから順次切り離す事により、安全なプログラムの更新を行う事ができました。</li>
<li>画像合成Webサーバ<br/>Webサーバを並列に2台並べました。<br/>ロードバランサから死活監視されており、死亡時は切り離されます。</li>
<li>Flash合成Webサーバ<br/>物理的に2台の画像合成Webサーバに相乗りしています。<br/><span style="color:#00F">並列構成ですので、サーバ追加により負荷分散が可能です。</span>
</li>
<li>MySQLマスタサーバ<br/>2台HAホットスタンバイ構成<br/><span style="color:#00F">シングルマスタ構成であるため、将来のボトルネックになる可能性はありますが、負荷試験においては想定しうる最大限の負荷に対して何の問題もなくさばき、ボトルネックとはなりませんでした。さらに高負荷となった場合にどこまで行けるかは負荷をかけきれずに調査できなかったのですが、そこのラインを超えた際にはより高機能のサーバを準備する、スケールアウトをするか、会員毎に格納DBを分割するシャーディングのどちらかを選択する必要はありました。<br/>※ただ、サービスがそこまで大ブレイクしたときには別途予算がつく筈ですので、今回は想定外としました。</span></li>
<li>MySQLスレイブサーバ<br/>並列に4台並べました。<br/>うち、1台は管理者による集計作業用に利用していました。<br/><span style="color:#00F">想定外の高負荷において、ここに負荷が集中した場合であってもサーバを追加することで負荷を分散させることが可能です。</span></li>
<li>memcacheサーバ<br/>物理的に2台の画像合成Webサーバに相乗りしています。<br/>データはハッシュ値を元に分散して取得していますが、該当のサーバが落ちていた場合にはハッシュ値を再計算して、有効なサーバにあたるまでリトライする機構を導入しています。<br/>memcacheの性質上、データの保護機構は存在しません。<br/><span style="color:#00F">性能には十分なマージがあったのですが、万が一ここに負荷が集中した場合であってもmemcacheサーバを追加することで負荷を分散させることが可能です。</span></li>
<li>KumoFS<br/>3台並べました。KumoFSはデータの冗長度を設定でき、どれかのサーバが一つ落ちてもデータの欠損が出ないようになっています。<br/><span style="color:#00F">性能には十分なマージがあったのですが、万が一ここに負荷が集中した場合であってもKumoFSサーバを追加することで負荷を分散させることが可能です。</span></li>
<li>TokyoTyrant<br/>物理的に3台のKumoFSサーバに相乗りしました。TokyoTyrantは全てのサーバに同一のデータコピーを持つため、3台全てが同時に破壊されない限りデータの欠損が出ないようになっています。<br/><span style="color:#00F">サーバを追加しても書き込み性能は改善されないのですが、TokyoTyrantに関してはカウンタに限定して利用するというポリシーを持っていたことと、もともと十分に数値に余裕があることからこの部分が問題になることは無いという判断を行いました。</span></li>
</ul>
<blockquote style="border: #0000ff 1px solid">
注1：ここではHAホットスタンバイ構成とは、High Availability(高可用)のホットスタンバイ構成を指す。<br/>バックアップ機に常に通電されているが、サービスを提供しているサーバはフロントのサーバ1台。フロントのサーバにトラブルがあった場合はバックアップサーバが速やかにサービスの提供を継続する仕組み。<br/>
注2：ここでは負荷分散構成を指す。通電中の全てのサーバが並列でサービスを提供しており、どれかのサーバがダウンした時も他のサーバがその業務をそのまま受け継ぐ。<br/><span style="color:#00F">負荷分散構成においては、高負荷時には単純にサーバを追加することにより、負荷を分散させ、スループットを上昇させることが可能です。</span></blockquote>
<br/>
<h2>最後に</h2>
完全公開!といいつつ、構成の都合上省略してしまったネタもあるのですが、いかがだったでしょうか？今回の記事に関して、自分でもここまで書いちゃっても良かったかな？と思った原稿に対して、掲載が許可されたので、もし好評なようであれば負荷試験の話なども公開してもいいかなと考えていますので、コメントいただければ幸いです。<br/>
<span style="color:#00F">※7/27 青字部分を頂いたコメントを受けて追記しました。冗長構成の説明ばかりで、負荷分散構成としての役割の説明が抜けておりましたので補足ささせて致しました。</span>
]]>
   </content>
</entry>
<entry>
   <title>完全公開！ソーシャルゲーム設計事例：前編</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop02.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.495</id>
   
   <published>2011-07-26T02:00:00Z</published>
   <updated>2011-07-27T02:10:23Z</updated>
   
   <summary>ゆめみにてGREEプラットフォームにおけるソーシャルアプリ携帯ゲーム「偉人伝心」のリリースをした際のシステム構築事例を今だから公開!コンテンツサーバ構築ミドルウエアからキャッシュポリシーまで。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="MySQL" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="携帯/モバイル" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="98" label="ソーシャルゲーム" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<h1>はじめに</h1>
<p>
こんにちは。樽八です。<br />
この記事は、前記事「<a href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop01.html">完全公開！ソーシャルゲーム設計事例：プロローグ編</a>」の続きになります。
</p>
<h3>おさらいとして、今回要求されたシステム要件(機能要件および非機能要件)</h3>
<ul>
<li>2000万PV/日のアクセスに耐えるシステムを作ること。</li>
<li>会員数の増減に合わせて速やかにシステム全体の構成を変更出来ること。</li>
<li>予期される障害発生に関しては、たとえスループットが落ちた状態状態であったとしても通常サービスの継続が可能であること。</li>
<li>特定の機器が障害によって復旧不可能な状態に陥っても、サービス継続に必要なデータが復旧可能であること。</li>
<li>負荷のピーク時においても、各ページの応答時間が5秒を超えないこと。</li>
<li>サービスを動かしながらの頻繁なアップデートおよびデータ更新が可能であること。</li>
</ul>
]]>
      <![CDATA[<h1>どうやって要求を達成したか：前編（前記事の続き）</h1>
<h2>各種コンテンツ生成サーバの最適化について</h2>
<ul>
<li>XHTMLコンテンツ生成Webサーバ</li>
<li>画像合成Webサーバ</li>
<li>Flash合成Webサーバ</li>
</ul>
それぞれの役割と設計の説明
<p>
開発効率の問題もあり、今回はそれぞれのWebサーバの基本設計を同じとしました。<br />
PythonもDjangoも初めてというエンジニアも多かったのですが、比較的スムーズに導入できました。<br />
<h3>各Webサーバでの共通設計</h3>
<table style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">Apache</td><td>mod_wsgiでPythonを動かしています。</td></tr><tr style="background-color: #ccffff"><td>PythonDjangoフレームワーク</td><td>ソーシャルアプリプラットフォームに対応するために、出力するXHTML内のURLの書き換えを自動的に行うなどのカスタマイズをいれています。<br />
次回の記事で触れる予定のデータベースへのアクセスも、それぞれ抽象化しています。</td></tr></tbody>
</table>
</p>
<h3>XHTMLのコンテンツを吐き出すWebサーバ</h3>
<p>
携帯に返すコンテンツ本文をXHTML形式にて生成するサーバであり、今回の開発の中心です。
<table style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">mod_ktai_info</td><td>i接続してきた携帯端末のキャリア、端末情報などを取得する弊社作成のApache moduleです。</td></tr></tbody>
</table>
</p>
<h3>画像動的合成Webサーバ</h3>
<p>
カード収集ゲームだったのですが、カード画像に攻撃力や防御力をオーバーレイ表示するために画像のリアルタイム合成を利用しました。<br />
また、S-in後の追加リリースにおいて導入された、5vs5のグループバトルでの戦況表示用の画像表示にても利用しました。<br />
<table style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">PIL(PythonImageLibrary)</td><td>Python上で画像の動的合成を行う事ができるライブラリです。<br />
正直、インタプリンタ言語ということもあり、実行速度に若干の不安はあったのですが、他のプロセスを起動しながら合成するよりはるかにマシだろうということでの選択。</td></tr></tbody>
</table>
</p>
<h3>Flashの動的合成Webサーバ</h3>
<p>
カードバトルの経過を表示する部分（ユーザーの入力のフィードバック無し）、ボス戦（ユーザーの入力のフィードバックあり）などで、あらかじめ用意されたFlashの一部の画像や、パラメータ、敵の名前などを置き換えるために利用しました。<br />
※フィーチャーフォンで扱えるFlashLiteでは、外部から動的にコンテンツにパラメータを渡したり置き換えたりする機能が貧弱で、ゲームで利用するにはこういった動的なFlashの書き換え技術が必要となります。
<table width="100%" style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">SWF Tools</td><td>Cで書かれたSWF変換用のライブラリ群。<br />
一部カスタマイズを行った上でPythonにバインディングして利用しました。</td></tr></tbody>
</table>
今回利用したシステムにては画像を直接置き換えることができなかったため、png画像を予めswfのmovie clipに変換しておき(これもSWF Toolsを利用)、そのmovie clipをテンプレート中で指定したmovie clipと置き換えるという手法を用いました。
</p>
<h2>キャッシュの利用ポリシーについて</h2>
高速レスポンスのためにはキャッシュの利用は必須です。当然、本システムにおいても積極的に利用しました。<br />
<ul>
<li>生成されたコンテンツのキャッシュ</li>
<li>リロード対策用ページキャッシュ</li>
<li>関数キャッシュ</li>
<li>DBへのアクセスのキャッシュ</li>
<li>SNSプラットフォームへのAPI発行</li>
</ul>
<p>
等のキャッシュポリシーについて簡単な説明をします 。
</p>
<blockquote style="border: #0000ff 1px solid">
キャッシュの基本的な考え方について、今回のプロジェクトでは以下のように利用ポリシーを定めました。<br />
<ul>
<li>利用することで高速化が期待できる部分では積極的に使う。</li>
<li>データが消えても、通常の処理で再生成可能なデータをキャッシュ対象とする。</li>
<li>冗長化されたキャッシュサーバそのものが何らかの理由で使えなくなったときは潔く諦める。</li>
</ul>
3番目の項目ですが、本来はキャッシュサーバが使えなくなった時であっても2番目の項目による処理が走るので問題ないはずなのですが、実際にはキャッシュが使えない状態では実用にならないスループットしかでないという問題もあり、いっそのこと諦めることとしました。
</blockquote>
<h3>生成されたコンテンツのキャッシュ</h3>
<p>
<img alt="リバースプロクシ利用例" src="http://yumewaza.yumemi.co.jp/img/postfile/reverse_proxy.gif" width="500" height="496" />
</p>
<p>
基本的にゲームコンテンツであるため、静的なニュースページなどと異なり、XHMLのコンテンツに関しては全てリクエスト毎に異なるコンテンツを生成する必要があります。<br />
そのため、キャッシュ可能なコンテンツは動的に生成された画像と、Flashに限られます。<br />
上記の画像やFlashに関して、キャッシュを効かせるために以下の処理を行いました。(注1)<br />
この処理を行うことにより、偶然同じ画像を表示することになったユーザーに関してはリバースプロクシ(注2)上のキャッシュが返答されることとなり、コンテンツ生成サーバへの負荷が軽減されます。<br />
</p>
<ul>
<li>コンテンツを要求する側のリクエストURLとして、同じURLでアクセスされたときに同じコンテンツを生成できるようにURLに画像やFlashの生成パラメータのハッシュ値を組み込む</li>
<li>ハッシュ値からコンテンツを逆生成出来るように、ハッシュ値をkeyとするmemcacheに必要なパラメータを追記しておく(注3)</li>
<li>画像/Flash合成サーバではmemcacheから合成用パラメータを取得し、実際のコンテンツを合成して返信する(注4)</li>
<li>画像およびFlashの場合にのみリバースプロクシを経由してアクセスするようにネットワークの設定をする</li>
</ul>
<blockquote style="border: #0000ff 1px solid">
注1：画像に関してはSNSプラットフォーム側が強力なキャッシュを持つようで、実際にはこの処理が必要なのはFlash生成だけでした。<br />
注2：今回はsquidを利用しました。nginxの利用も検討したのですが、squidは弊社での実績が多かっただけでなく、実際に負荷試験を行った結果、キャッシュのミスヒット時のオーバーヘッドがほとんど無かったことが採用の決め手でした。<br />
注3：この方法ではmemcache上にゴミが残りますが、そもそもmemcacheはゴミが残ることを許容するデータ処理方式と認識しています。<br />
注4：URLから一意にコンテンツを生成する別の方法として、コンテンツ生成に必要なパラメータを可逆圧縮して、URLに直接埋め込む方法も考えられます。しかしこの方法では埋め込むことの出来る情報量に限界があることから、将来の拡張等の妨げになるとの判断により不採用としました。
</blockquote>
<h3>リロード対策用ページキャッシュ</h3>
<p>
これはサービスインの時点で実装されていたキャッシュではないのですが、サービスイン後しばらくしてから、以下のようなクレームが多く寄せられるようになりました。
<pre>
今日のお祓いをまだ行っていないのに、行ったことになっている。
今日の分の○○を最初にやったはずなのに「既に終わっています。」の表示がされる。
etc...
</pre>
調査をしてみると、高負荷な時間帯において上流の回線の遅さにより端末のタイムアウトが発生している場合の症状だということがわかりました。<br/>
基本的に何らかのパラメータの増減を伴う更新系のページに関しては、全てのページで以下の流れの処理を行っています。<br/>
<pre>
そのアクションを行う事ができる残り回数があるかの判定
　→残り回数0の場合、「本日はもう出来ません。」等の表示。
残り回数がある場合、1回減らして、目的のアクションを行う。
　→「○○を行いました。残り回数は○回です。」等の結果を表示する。
</pre>
一方、回線の問題等でユーザーのリクエストがタイムアウトして、結果を取得できなかったとき、ユーザーは通常リロード作業を行います。<br/>
この時、サーバでは既に1回目の処理を追え、レスポンスを返して居るわけですので、プログラム的には2回目のリクエストが処理され、「本日はもう出来ません。」の方だけ表示される。という事になります。<br/>
プログラム的には不具合、バグでは無いのですが、ユーザー様から見て、バグのように見える面倒くさいケースでした。<br/>
本来は、これらの部分を全て手作業で洗いだして個別の対策コードを仕込む必要があったのですが、ページ別にあるべき挙動を再定義する必要があり、困難が想定されました。<br/>
そこで取った対策が、対象ページに関しては出力を全てキャッシュするという「リロード対策用ページキャッシュ」となります。
</p>
<p>
この機構を用いると、Pythonプログラム中で指定されたページに関して、出力を全てmemcacheにバッファしてからユーザーに返信します。<br/>
予め指定した制限時間以内に、同一リクエストパラメータによるリクエストが発生した場合（※注）に、ユーザーがリロード作業を行ったものと判定し、前回生成したキャッシュ上のコンテンツをそのまま返信します。<br/>
このことにより、通信断によるリロード等において、ユーザーの複数回のページリクエストがあった場合にもユーザーの求める結果を表示することが出来るようになりました。<br/>
また、この対策はほぼ全ての更新系ページにおいて一律に導入可能な対策であり、ページ別の挙動の定義が必要なくなりました。
<blockquote style="border: #0000ff 1px solid">
注：ページ中に記載されたURL中には全てシステムで自動発番したランダムな文字列を埋め込んでいたため、新たに生成されたページ中のリンクを踏んだ上で同一ページにアクセスした場合にはこの機構は発動しません。
</blockquote>
</p>
<h3>関数キャッシュ</h3>
<p>
Python上でmemcacheを用いた関数キャッシュを実装しました。<br />
関数の実行結果をmemcache上に保存し、他のリクエストからの関数実行の際に、関数の引数が同じ場合は関数を実行する前にmemcache上のデータを返答します。<br />
Pythonではdecoratorという機能を利用可能であるため、非常にスマートに関数キャッシュ機能を利用出来るようになりました。
</p>
<blockquote style="border: #0000ff 1px solid">
<h4>decoratorを利用した関数キャッシュ利用例</h4>
<pre>
from prjlib.django.cache.function_cache import fcache

class クラス名():

    -中略-

    @classmethod
    @fcache(10, 30)
        def 関数キャッシュを効かせたいクラスメソッド名(cls, opt1, opt2, opt3):
            何か実行コストの高い処理
         return 結果
</pre>
とすると、上記クラスメソッドを呼び出す際に自動的に関数キャッシュが利用できます。<br/>
通常のクラスメソッドとの違いは@fcache(opt1, opt2)の記載だけです。
</blockquote>
<p>
<br />
この関数キャッシュにて指定可能なオプションは、以下の2つです。
</p>
<table style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">関数の再実行を試みる期限</td><td>前回の関数実行時間から比較して、この時間を超えていた場合、memcache上に保存された関数の実行中フラグを確認します。<br />
もしまだ誰もこの関数を実行中でない場合、関数実行中フラグを上げたうえで、関数の再実行を試みます。<br />
関数の実行が完了後、関数の実行結果をキャッシュしてから関数実行中フラグを落とします。その後、関数の実行結果を返答します。<br />
もし既に関数実行中フラグが上がっていた場合、関数の再実行は行わずにmemcache上に残された以前の実行結果あればそれを返答、無かった場合はmemcache上に実行結果が生成されるまで待ち、それを返答します。</td></tr><tr style="background-color: #ccffff"><td style="width: 30%">関数実行結果を破棄する期限</td>
<td>この時間を超えると、以前の関数実行結果を取得することは許されませんので、キャッシュが無かった場合と同じ場合の動作を行います。</td></tr></tbody>
</table>
<br/>
<a href="http://yumewaza.yumemi.co.jp/img/postfile/function_cache.html" onclick="window.open('http://yumewaza.yumemi.co.jp/img/postfile/function_cache.html','popup','width=859,height=638,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false"><img src="http://yumewaza.yumemi.co.jp/img/postfile/function_cache-thumb.jpg" width="500" height="371" alt="" /></a>
<br />
<blockquote style="border: #0000ff 1px solid">
注：負荷試験を実施するまで、この期間オプションは「関数実行結果を破棄する期間」の1つしか無かったのですが、この場合、関数実行結果を破棄するタイミングが来た瞬間に複数のプロセスから同時に関数の再実行が呼び出されてしまいます。<br />
これが通常の実行コストの低い処理であれば問題とならないのですが、そもそも実行コストの高い処理に対して関数キャッシュを効かせるため、ある程度の実行時間がかかってしまいます。<br />
すると、それらの処理が完了するまでは関数実行結果は更新されないため、高負荷時においては関数実行用のプロセスがどんどん溜まっていくという負のスパイラルが発生してしまいました。<br />
例：ピーク時に秒間100回コールされるページであると仮定した場合において、一つの関数の実行に10秒かかったとします。(巨大なテーブルのソート処理など。)すると、関数実行結果の破棄タイミング以降の10秒間で1000回のクエリを発行しようとすることになります。<br />
負荷試験後のこの改修により、上記パターンにおいてもDBへのクエリ実行は1回で済み、他のプロセスは前回に取得したデータを一瞬で参照可能となりました。<br />
この関数キャッシュの仕組みは非常に有効で、後述のDBへのアクセスキャッシュおよびSNSプラットフォームAPIのキャッシュに大活躍しました。
</blockquote>

<h3>DBへのアクセスのキャッシュ</h3>
そもそもDBアクセスは少ないにこしたことはありませんので、DBアクセスのキャッシュは積極的に行いました。<br />
この結果のキャッシュは前述の関数キャッシュを用いました。<br />
キャッシュ対象のデータは、主に以下の二つです。<br />
<table style="background-color: #0066ff; border: #0000ff 1px solid" cellspacing="1" cellpadding="1">
<tbody><tr style="background-color: #ccffff"><td style="width: 30%">マスタ系データ</td><td>管理者によってのみ更新されるマスタ系データ。<br />
そもそも変更される頻度があまりない。</td></tr><tr style="background-color: #ccffff"><td style="width: 30%">ユーザーに紐づいたテーブルからのリスト抽出</td><td>ユーザー数に比例したテーブルの検索となるため、非常に重いクエリとなるのですが、ユーザーの条件によっては同じ抽出結果を用いることが出来るため、キャッシュさせました。<br />
具体的には、1ページ中に10件の結果が必要なページであっても、1000件分の抽出を行いその結果を関数キャッシュで共有、その結果から再度ランダムに10件取得という処理を行いました。<br />
この処理により利用ユーザーにはサーバ負荷が高い処理と思われていた対戦相手一覧の抽出は実は内部的な負荷がほとんどかからないページとなっていました。</td></tr></tbody>
</table>
<h3>SNSプラットフォームへのAPI発行</h3>
上流のSNSプラットフォーム上のデータを参照、更新するAPIの利用はネットワーク的に外部であることもあり、全体として非常に実行時間のかかる処理となります。<br />
APIの種別によっては実行結果をキャッシュすることを許可されていないのですが、その他のAPIは積極的に結果のキャッシュさせる必要があります。<br />
この結果のキャッシュも前述の関数キャッシュを用いました。<br />
例：ユーザーニックネーム、画像URL取得APIなど。<br />
<blockquote style="border: #0000ff 1px solid">
余談ですが、各APIの実行を待つ時間の調整はS-in後も最後まで難航しました。長いと全体のタイムアウトにつながる。短いと個別のAPIの実行結果が担保できない。。。。
</blockquote>
<br />
長くなることが予想されるので、記事を分けます。<br />
<h1>どうやって要求を達成したか：後編(以下、次号)</h1>
<h2>データ保存レイヤの最適化について</h2>
<ul>
<li>サービスで扱う各種のデータをどういうデータとして分類したか。</li>
<li>上記のそれぞれに分類されたデータをどう扱ったのか。</li>
</ul>
<h2>各サーバの冗長化について</h2>
<p>
今回のサービスにおいて利用したサーバそれぞれにおける冗長化についての簡単な説明
</p>
<a href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop03.html">後編へ続く</a>]]>
   </content>
</entry>
<entry>
   <title>完全公開！ソーシャルゲーム設計事例：プロローグ</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/07/social_game_develop01.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.494</id>
   
   <published>2011-07-25T01:44:07Z</published>
   <updated>2011-07-25T02:22:08Z</updated>
   
   <summary>ゆめみにてGREEプラットフォームにおけるソーシャルアプリ携帯ゲーム「偉人伝心」のリリースをした際のシステム構築事例をまるっと公開します。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="MySQL" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="携帯/モバイル" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="87" label="ソーシャルアプリ" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<h1>はじめに</h1>
<p>
こんにちは。樽八です。
</p>
<p>
ちょうど1年ほど前になりますが、弊社にてGREEプラットフォームにおけるソーシャルアプリ携帯ゲーム「偉人伝心」のリリースを致しました。<br />
その時のシステムに対する機能要求および非機能要求に対してできたこと、できなかったことをエンジニア視点で列挙したいと思います。
</p>
<h1>システムに対する要求と、達成、未達成状況</h1>
<h2>ほぼ達成された要求項目(４項目)</h2>
<h3>2000万PV/日のアクセスに耐えるシステムを作ること。</h3>
<p>
&nbsp;&nbsp;&rarr;負荷試験時において、画像合成、Flash合成ページを含めて、webサーバ１０台時の構成において、1400リクエスト/秒(単純計算だと1億2000万PV/日)以上のスループットを達成。<br />
　（※残念なことに、これだけの負荷が実際に掛かることはありませんでした。）
</p>
<h3>会員数の増減に合わせて速やかにシステム全体の構成を変更出来ること。</h3>
<p>
&nbsp;&nbsp;&rarr;サーバの追加に応じてシステム全体のスループットを上げることのできる、スケールアウト可能な構成としました。<br />
&nbsp;&nbsp;&rarr;上記負荷試験時の数字もWebサーバの追加によりさらに引き上げることが出来る事を確認しました。<br />
</p>
<h3>予期される障害発生に関しては、たとえスループットが落ちた状態状態であったとしても通常サービスの継続が可能であること。</h3>
<p>
&nbsp;&nbsp;&rarr;全てのHW機器を冗長化して構築しました。
</p>
<h3>特定の機器が障害によって復旧不可能な状態に陥っても、サービス継続に必要なデータが復旧可能であること。</h3>
<p>
&nbsp;&nbsp;&rarr;上記、HW機器の冗長化に加えて、データの保有に関しても冗長化を行いました。
</p>]]>
      <![CDATA[<h2>一部達成できなかった要求項目(2項目)</h3>
<h3>負荷のピーク時においても、各ページの応答時間が5秒を超えないこと。</h3>
<p>
　負荷試験時には問題が無かったのですが、実際の運用環境においては夜間の高負荷時において、上流のSNSプラットフォームへのAPI発行に時間がかかるようになりました。キャッシュ可能なAPIはキャッシュを参照していたのですが、規約上キャッシュ不可のAPIもあり、5秒の制約を守るためにはAPIの実行結果を待たずに混雑中のページを表示させなければならないことが発生しました。</p>
<h3>サービスを動かしながらの頻繁なアップデートおよびデータ更新が可能であること。</h3>
<p>
&nbsp;基本的には無停止でのアップデートを行っていたのですが、MySQLのAlterTable文を発行しなければならない場合においては一時的なメンテナンスによるサービス停止(数分程度)を必要としました。<br />
&nbsp;&nbsp;これを回避する手段はあるのですが、非常に複雑なシステムとなり、また、HW投資もかさむため、今回のシステムにては上記のメンテナンスの為のサービス停止を許容しました。
</p>
<h1>どうやって要求を達成したか（以下、本編を前編：後編の2回に分けて記載します。）</h1>
<p>
上記要求項目の達成に関して行ったことを以下の4つの観点から記事を公開ていきたいと思います。
</p>
<h2>各種コンテンツ生成Webサーバの最適化について</h2>
<ul>
<li>XHTMLのコンテンツを吐き出すWebサーバ</li>
<li>画像動的合成Webサーバ</li>
<li>Flashの動的合成Webサーバ</li>
</ul>
<p>
それぞれの役割と構成についての簡単な説明。
</p>
<h2>キャッシュの利用ポリシーについて</h2>
<ul>
<li>生成されたコンテンツのキャッシュ</li>
<li>リロード対策用ページキャッシュ</li>
<li>関数キャッシュ</li>
<li>DBへのアクセスのキャッシュ</li>
<li>SNSプラットフォームへのAPI発行</li>
</ul>
<p>
等のキャッシュポリシーについて説明 。
</p>
<h2>データ保存レイヤの最適化について</h2>
<ul>
<li>サービスで扱う各種のデータをどういうデータとして分類したか。</li>
<li>上記のそれぞれに分類されたデータをどう扱ったのか。&nbsp;</li>
</ul>
<h2>各サーバの冗長化について</h2>
<p>
今回のサービスにおいて利用したサーバそれぞれにおける冗長化についての簡単な説明
</p>
]]>
   </content>
</entry>
<entry>
   <title>Android NFCとNexusSで MifareClassic を読み書きする（後編）</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/03/android_nfc_nexuss_mifare_classic_3.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.468</id>
   
   <published>2011-03-07T05:51:10Z</published>
   <updated>2011-03-07T05:52:48Z</updated>
   
   <summary>MifareClassicシリーズ最終回ということでMifareClassicに関するまとめをしてみたいと思います</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="22" label="android" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
こんにちは、ちきんです。今回も引き続きMifareClassicの話です。<br/>
今回は、入手した真っさらな MifareClassic(Mifare Standard)のカードに NdefFormatable#format() をしたところ、
NDEF_DISCOVERED で intentが発生したり、Ndef の instanceが使えるようになりましたのでその辺りのお話と、<br/>
MifareClassicシリーズ最終回ということでMifareClassicに関するまとめをしてみたいと思います。
</p>
]]>
      <![CDATA[
<h2>（１） MifareClassicのカードをNDEF Formatする</h2>
<p>
まず、早速MifareClassicの初期状態を見てみると、以下のようになっていました。<br/>
<pre>
○初期状態
========== Sector 1 ===============
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx  // ID Blockなので伏せ
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 0000FF07 8069FFFF FFFFFFFF
========== Sector 2-15 ============
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 0000FF07 8069FFFF FFFFFFFF
========== Sector 16 ==============
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 0000FF07 80<b>BC</b>FFFF FFFFFFFF  // 何故かちょっと違う
===================================
</pre>
前回のカードとほぼ同じアクセス権情報ですが、最終セクタの1byteだけ違いました。<br/>
この部分は前回の資料には特に用途が書いてなかったので、どういう意味か不明ですが、気にしないことにします。<br/>
<br/>
それでは、前回と同じように、format()を実行してみると、以下のようになりました。<br/>

<pre>
○ ndefFormatable.format(ndefMessage) の実行後
========== Sector 1 ===============
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx  // ID Blockなので伏せ
140103E1 03E103E1 03E103E1 03E103E1  // 更新
03E103E1 03E103E1 03E103E1 03E103E1  // 更新
00000000 00007877 88C10000 00000000  // 更新
========== Sector 2 ===============
00000319 <span style="color:red">D20A0C74 6578742F 706C6169</span>  // 更新
<span style="color:red">6E48656C 6C6F2C20 4E444546 21</span>FE0000  // 更新
00000000 00000000 00000000 00000000 
00000000 00007F07 88400000 00000000  // 更新
========== Sector 3-16 ============
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 
00000000 00007F07 88400000 00000000  // 更新
===================================
Sector 1 の鍵は、
  KeyA: <b>KEY_MIFARE_APPLICATION_DIRECTORY</b>
  KeyB: KEY_DEFAULT

Sector 2-16 の鍵は、
  KeyA: <b>KEY_NFC_FORUM</b>
  KeyB: KEY_DEFAULT

<span style="color:red">赤字</span>は ndefMessage.toByteArray() と一致する部分。
</pre>
主な変更点は、
<ul>
<li>全セクタのSector Trailer(最終ブロック)が更新された</li>
<li>Sector Trailerの変化は前回と同様で、セクタ１のKeyAは <b>KEY_MIFARE_APPLICATION_DIRECTORY</b>に、他のセクタのKeyAは <b>KEY_NFC_FORUM</b> になった。 KeyBは読み取り不可に。 </li>
<li>Sector1のDataBlockは、 Indexっぽい情報になっている(?)</li>
<li>実際のNdefMessage Dataは、 Sector2以降に書き込まれる</li>
<li>より大きいDataを書きこむと、Sector3以降にも書込みが行われる</li>
</ul>
というところでした。<br/>
</p>

<h2>（２） NDEF formatしたカードの振る舞い</h2>
<p>
以下のように振る舞いが変わりました。

<ul>
<li>NDEF formatしたカードを 再度認識させると TECH_DISCOVERED ではなく <b>NDEF_DISCOVERED</b> で通知が来るようになりました。</li>
<li>プリインストールされている Tagアプリでも読み取れるようになりました（まあ、当然ですか、、）。</li>
<li>getTechList()の結果が変わり、 tech.NdefFormatable が無くなり <b>tech.Ndef</b> になりました。</li>
<li>しかし、</li>
</ul>
<pre>
intent.getParcelableExtra(NfcAdapter.EXTRA_ID)
intent.getParcelableExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
</pre>
は null のままでした

<ul>
<li>NDEF_DISCOVEREDのときの Intent Filterを</li>
</ul>
<pre><xmp>
<intent-filter>
	<action android:name="android.nfc.action.NDEF_DISCOVERED"></action>
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="text/plainX" />   <---- plainX にした
</intent-filter>
</xmp></pre>			
とすると、 NDEF_DISCOVERED ではなく TECH_DISCOVERED になりました。 mimeTypeは一致しないといけないようです。

<ul>
<li>TECH_DISCOVEREDでも、 getTechList() に Ndef は残っていたので、 Ndef APIは使えるようです</li>
</ul>
</p>

<h2>（３） NDEF formatReadOnlyしたらどうなるのか？</h2>
<p>
今度は、 format() ではなく、 <b>formatReadOnly()</b> をしてみました。しかし、正常終了しませんでした。<br/>
しかし、NDEF formatにはなっていましたので、 format() -> makeReadOnly() という順番で実行され、最後で失敗したような感じでした。<br/>
<br/>
試しに、 tech.Ndef の以下の methodを呼び出すと
<pre>
canMakeReadOnly(): <b>false</b>
getMaxSize(): 716  // (16byte * 3block * 15sector = 720) - 4
isWritable(): true
</pre>
という応答でした。 MifareClassicを ReadOnly にすることは可能なはずですが、 canMakeReadOnly() の応答は false ということでした。<br/>
ちょっと意外だったので、どういう条件で判定しているかを調べてみると、
<pre>■ <a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob_plain;f=core/java/android/nfc/tech/Ndef.java;hb=HEAD">android/nfc/tech/Ndef.java</a>
<xmp>
public boolean canMakeReadOnly() {
	if (mNdefType == TYPE_1 || mNdefType == TYPE_2) {
		return true;
	} else {
		return false;
	}
}</xmp>
</pre>
ということなので、 Ndef.NFC_FORUM_TYPE_1 Ndef.NFC_FORUM_TYPE_2 というTypeのカードしか、trueにならないようです(少なくとも現時点では)。
このTypeというのは、 <a href="http://www.radio-electronics.com/info/wireless/nfc/near-field-communications-tags-types.php">NFC tag type definitions</a>におけるTypeと思われます。<br/>
MifareClassic は、Type1かType2に当てはまりそうですが、Androidでは別な区分にされています(理由はわかりませんが)。<br/>
<a href="http://developer.android.com/reference/android/nfc/tech/Ndef.html">API Document</a>では、
<pre>
* NFC Forum Type 1 Tag (NFC_FORUM_TYPE_1), such as the Innovision Topaz
* NFC Forum Type 2 Tag (NFC_FORUM_TYPE_2), such as the NXP MIFARE Ultralight
* NFC Forum Type 3 Tag (NFC_FORUM_TYPE_3), such as Sony Felica
* NFC Forum Type 4 Tag (NFC_FORUM_TYPE_4), such as NXP MIFARE Desfire 
</pre>
となっているので、 MifareUltralight は Type2 だけど、 MifareClassicはどうもこの規格には入らないみたいですね。<br/>
NDEFを扱いたいなら、 MifareClassicではなくてMifareUltralightを使うほうが良いのかもしれませんね（今更…--;）。
</p>

<h2>（４） MifareClassicのまとめ</h2>
<p>最後に  Android NFC API と MifareClassic のポイントをまとめます。</p>
<h3>NDEF Format済みか否かを調べるには</h3>
<p>
Tag#getTechList() に <b>"Ndef"</b> が入っていればFormat済み、 <b>"NdefFormatable"</b> が入っていればFormatされていない、ということができそうです。
判定には、例えば以下のような判定ロジックを作って、
<pre><xmp>
public class NFCUtil {
    static public boolean hasTech(Tag tag, String klassName) {
        for (String tech : tag.getTechList()) {
            if (tech.equals(klassName)) {
                return true;
            }
        }
        return false;
    }

    static public boolean hasTech(Tag tag, Class< ? extends TagTechnology> tech) {
        return hasTech(tag, tech.getCanonicalName());
    }
}
</xmp></pre>
で、
<pre>
Tag tag = (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (NFCUtil.hasTech(tag, Ndef.class)) {
  // NDEF Format済み
}
// とか
if (NFCUtil.hasTech(tag, NdefFormatable.class)) {
  // NDEF Format ではない
}
</pre>
というように判定できます。
</p>

<h3>Format済みの場合</h3>
<ul>
<li>NDEF_DISCOVERED の Intentが発生する（ことがある。 発生するかしないかはNdefMessageの最初のNdefRecordに依存すると仕様ではなっている）。<br/>
    例えば、MimeType,text/plain のNDEFなら、以下のようなIntentFilterを AndroidManifest.xml の activityタグの中に書いておくと良いです。
</li>
</ul>
<pre><xmp>
<intent-filter>
	<action android:name="android.nfc.action.NDEF_DISCOVERED"></action>
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="text/plain" />
</intent-filter>
</xmp></pre>
<ul>
<li>上記、android:mimeType の部分を変更したときのIntentの変化をいくつか調べてみると以下のようになります。
<table border="1">
<tr><td style="text-align:center;background-color: yellow;">タグの内容</td><td style="text-align:center;background-color: yellow;">Intent種別</td></tr>
<tr><td>タグを省略する</td><td>TECH_DISCOVERED</td></tr>
<tr><td>mimeType="text/plainX"</td><td>TECH_DISCOVERED</td></tr>
<tr><td>mimeType="*"</td><td>アプリがInstallできない</td></tr>
<tr><td>mimeType="*/*"</td><td>NDEF_DISCOVERED</td></tr>
<tr><td>mimeType="text/*"</td><td>NDEF_DISCOVERED</td></tr>
<tr><td>mimeType="te*/*"</td><td>TECH_DISCOVERED</td></tr>
<tr><td>"text/*" と "image/*" を２つ記述</td><td>NDEF_DISCOVERED</td></tr>
</table>
*でマッチングさせたり、複数を列挙してマッチングさせたりできるようです。
</li>

<li>tech.NdefのAPIが利用可能です</li>
</ul>
<pre>Ndef ndef = Ndef.get(tag); // で instance化</pre>
<ul>
<li>鍵を考える必要はなく、NdefMessageの Read/Write を行うことができます。但し、データサイズだけは気にする必要があります。</li>
</ul>
<pre>
if (!ndef.isConnected()) { 
	ndef.connect(); // connect() 忘れると例外がでます。
}
ndef.writeNdefMessage(ndefMessage);
</pre>
<ul>
<li>makeReadOnly() は使えないようです</li>
</ul>
<p>書き込めるSizeや makeReadOnly などは、カードに依存しますが、後はだいぶ抽象化されている感じですね。</p>

<h3>Formatしていない場合</h3>
<ul>
<li>NDEF_DISCOVERED は発生せず、 TECH_DISCOVERED の Intent が発生します。<br/>
    捕捉するには、AndroidManifest.xml に下記を追加して、
</li>
</ul>
<pre><xmp>
<intent-filter>
  <action android:name="android.nfc.action.TECH_DISCOVERED"></action>
  <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" 
             android:resource="@xml/nfc_filter"/>
</xmp></pre>
<b>res/xml/nfc_filter.xml</b>を以下のようにします。
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;<xmp><resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- MifareClassic の 未Formatの時のみ捕捉する場合 -->
     <tech-list>
		<tech>android.nfc.tech.NdefFormatable</tech>
		<tech>android.nfc.tech.MifareClassic</tech>
	 </tech-list>
</resources>
</xmp></pre>

<ul>
<li>(おそらく)全セクタのKeyAが KEY_DEFAULT で、KeyAで任意の書込みができるならば、 NdefFormatable#format() が可能です。</li>
</ul>
<pre>
NdefFormatable ndefFormatable = NdefFormatable.get(tag); // tag は android.nfc.Tag
if (!ndefFormatable.isConnected()) {
	ndefFormatable.connect();
}
ndefFormatable.format(ndefMessage);
</pre>

<ul>
<li>（もちろん）Ndef Formatしなくても、Read/Writeすることはできます(当然 KeyA, KeyB や アクセス権次第ですが)</li>
</ul>
<pre>
MifareClassic mc = MifareClassic.get(tag); // tag は android.nfc.Tag
if (!mc.isConnected()) { mc.connect(); }
if (mc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_DEFAULT)) { // KEY_DEFAULT を使う場合
  // for read
  mc.readBlock(targetBlock);
  // for write
  mc.writeBlock(targetBlock, writeData);
}
</pre>

<h2>（５） さいごに</h2>
<p>
MifareClassic は <a href="http://goo.gl/Lt0fY">ステルス・ネットワークス</a>さんのサイトで10枚で3650円(送料、消費税込)でした。<br/>
前日の夕方(一応営業時間内)に注文したら、翌日の午前中に届けてくれました。<br/>
皆さんもちょっと実験に如何でしょうか(^^;)。<br/>
それにしても、手元の10枚のカードを何に使おうか、、何か軍人将棋みたいなカードゲームでも作れないかな、、と思ってしまうのでした。
</p>
]]>
   </content>
</entry>
<entry>
   <title>Android NFCとNexusSで MifareClassic を読み書きする（中編）</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/03/android_nfc_nexuss_mifare_classic_2.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.467</id>
   
   <published>2011-03-04T06:51:41Z</published>
   <updated>2011-03-04T06:53:46Z</updated>
   
   <summary>Android NFCAPIで NdefFormatable を使って、 MifareClassic にデータを書き込めるか調査しました</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="22" label="android" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
こんにちは、ちきんです。<br/>
<a href="http://yumewaza.yumemi.co.jp/2011/03/android_nfc_nexuss_mifare_classic_1.html">前回</a>に続き、 MifareClassicの話です。<br/>
今回は、 NdefFormatable を使って、 MifareClassic にデータを書き込めるか調査しました。<br/>
結果は、 (おそらく)検証に用いたカードの問題で「中途半端に書込みが成功」というものでした。きっとまっさらなカードなら成功したのではないかと思います。<br/>
色々興味深いこともいくつかわかったので、以下、その状況について説明していきます。<br/>
</p>
]]>
      <![CDATA[<h2>（１） NDEFデータの作成</h2>
<p>
まず、 NdefFormatable#format(NdefMessage) を実行するために、 NdefMessage を作成する必要があります。<br/>
NdefMessage は NdefRecord を複数含むものなので、 NdefRecord を作れば良いわけです。<br/>
NdefRecord は immutable な data class で、コンストラクタは、
<pre><xmp>
NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload)
</xmp></pre>
という形をしています。<br/>
これだけでは、何をどうしていいかわかりずらいですが、
<a href="http://www.nfc-forum.org/specs/spec_list/">NFC Forumの仕様書リスト</a>の「NFC Data Exchange Format (NDEF) Technical Specification」などを
見るとだいたい理解できると思います（見るためには要メンバー登録です）。<br/>
それぞれの意味は以下のようになるようです。
<ul>
<li>tnf: TNF(Type Name Format)のこと。<a href="http://developer.android.com/reference/android/nfc/NdefRecord.html">NdefRecord の 定数</a>として定義されている値をセットする。<br/>
</li>

<li>type: データのタイプを表す。TNFによってどういう値が適切かが変わる。
</li>

<li>id: ID. 省略も可能。何に使われるんでしょうか・・・
</li>

<li>payload: 内包する任意のデータ。
</li>
</ul>
TNFやTypeをどうすると、何がどう反応するのか、というのが全然わかりませんが、形式的に正しいデータを作るだけなら簡単なようです。<br/>
ということで、今回は以下のような NdefMessage を作ることにします。
</p>

<pre><xmp>
NdefRecord ndefRecord = new NdefRecord(
                             NdefRecord.TNF_MIME_MEDIA,  // TNF
                             "text/plain".getBytes(),  // TYPE
                             new byte[]{},   // ID: 今回は無し
                             "Hello, NDEF!".getBytes() // payload
                            );
NdefRecord[] ndefRecordList = new NdefRecord[1];
ndefRecordList[0] = ndefRecord;
NdefMessage ndefMessage = new NdefMessage(ndefRecordList);
</xmp></pre>

<h2>（２） NdefFormatable#format() の実行結果</h2>
<p>
書きこむ部分のコードは以下のような感じです。簡単ですね。<br/>
</p>
<pre><xmp>
NdefFormatable ndefFormatable = NdefFormatable.get(tag);
if (!ndefFormatable.isConnected()) {
    ndefFormatable.connect();
}
ndefFormatable.format(ndefMessage);
</xmp></pre>
<p>
それでは前回使った保育園のカードに書込みをしてみます。<br/>
再起不能になると色々困るわけですが、、<br/>
・<br/>
・<br/>
ぽちっとな<br/>
・<br/>
・<br/>
おお、、 IOException の例外が。。 getMessage() は null でした。うーむ、嫌な予感です。。<br/>
<br/>
どういうデータになったか確認してみると、一応各Sectorを見ることは問題なくできました。<br/>
<ul>
<li>最初のセクタであるSector1 から Sector3までが何か書き換わっていて、</li>
<li>Sector4は鍵がかかっている(既知ではない)ので、どうなっているか不明。</li>
<li>Sector5は以前のままでした。</li>
</ul>
どうも、 Sector4まで書込みに行って失敗して終了したようです。 ちなみに、Sector4の鍵は保育園のシステムが使用しているんだと思います。<br/>
今回書きこもうとした NdefMessage はそれほど大きくないはずですが、
format() という名前からして、割と広範囲に初期化書込みをするのかもしれません。
</p>
<p>
Sector1 から Sector3 で、 DataBlockが更新されているのが、 Sector1 の ２つ目、３つ目のみ。<br/>
Trailerは３つとも更新されていました。<br/>
具体的には下記のようになっていました。<br/>
<pre>
-- Sector1 --  
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx     // ID Blockにつき省略
0F0003E1 03E103E1 03E103E1 03E103E1     // 更新
03E103E1 03E103E1 03E103E1 03E103E1     // 更新
00000000 00007877 88C10000 00000000     // 更新
-- Sector2,3 --  
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
00000000 00007F07 88400000 00000000     // 更新
----------------
</pre>
とりあえず、気になるのはアクセス権を表す Trailer 部分がどうなっているかです。<br/>
色々調べたところ以下のようになっていました。<b>太字</b>が変更点です。
</p>
<pre>
■ Sector1: AccessBytes: 0x787788c1 なので、
    KeyA: <b>不明</b>(KEY_DEFAULT や KEY_NFC_FORUM ではない)
    KeyB: KEY_DEFAULT
    
  Trailer Access: (C1,C2,C3)=<b>(0,1,1)</b>
    KeyA R: Never         // KeyAは 読み込む不可
    KeyA W: <b>KeyB</b>          // KeyAは KeyBで書込み可能
    Abit R: KeyA or KeyB  // アクセス権は、KeyAorBで読込み可能
    Abit W: <b>KeyB</b>          // アクセス権は、KeyBで書込み可能
    KeyB R: <b>Never</b>         // KeyBは、読込み不可
    KeyB W: <b>KeyB</b>          // KeyBは、KeyBで書込み可能

  Data Access: (C1,C2,C3)=<b>(1,0,0)</b>
    Read: KeyA or KeyB    // DataBlockは、 KeyAorKeyBで読込み可能
    Write: <b>KeyB</b>           // DataBlockは、 KeyBで書込み可能
    Type: Read/Write Block// ブロックタイプは、 Read/Writeタイプ(Valueタイプではない)

■ Sector2,3: AccessBytes: 0x7f078840 なので、
  KeyA: <b>KEY_NFC_FORUM</b>
  KeyB: KEY_DEFAULT
  Trailer Access: (C1,C2,C3)=<b>(0,1,1)</b> // Sector1と同じ
  Data Access: (C1,C2,C3)=(0,0,0)
    Read: KeyA or KeyB
    Write: KeyA or KeyB
    Type: transport configuration??
</pre>
<p>
変更点をざっくりというと、
<ul>
<li>KeyAが KEY_DEFAULTから変更された。Sector1は不明。Sector2,3はKEY_NFC_FORUM になっている</li>
<li>KeyAによる権限が色々剥奪されているが、KeyBでは全情報を更新可能</li>
<li>KeyBが読込み不能になっている</li>
</ul>
というところです。 Sector1 の KeyA が何になっていたのか気になりますが、とりあえず、KeyBによって全て書き戻すことが可能なことがわかりました。<br/>
KeyBの読み取りがOFFになっているのも面白いです。<br/>
ただ、この実行結果は format() の途中結果に過ぎないので、正常終了した場合はもっと違う形式になっている可能性もありますので、その点はご注意下さい。<br/>
<br/>
IOException の詳細な理由はわかりませんが、多分、 Sector4が読み書きできないことが原因のような気がします。<br/>
これ以上、このカードで検証することはできなさそうなので、残念ながら今回はここで終わりにします。<br/>
最後に、KeyBを使って、元データを(trailerも含めて)書き戻してみましたが、ちゃんと戻りました（良かった・・）。<br/>
</p>
<h2>（３） さいごに</h2>
<p>
やはりちゃんとした MifareClassic カードが無いとダメっぽいので、入手したらこの続きを行いたいと思います。<br/>
思いの外長引きますが、もう少し続きます・・・
</p>
]]>
   </content>
</entry>
<entry>
   <title>Android NFCとNexusSで MifareClassic を読み書きする（前編）</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/03/android_nfc_nexuss_mifare_classic_1.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.466</id>
   
   <published>2011-03-01T13:17:55Z</published>
   <updated>2011-03-01T13:19:49Z</updated>
   
   <summary>MifareClassicの仕様を調べてみたところ、
MifareClassicは後戻りできない書込みもできてしまうことなども判明しましたので、その辺りを共有したいと思います</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="22" label="android" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
こんにちは、ちきんです。<br/>
<a href="http://yumewaza.yumemi.co.jp/2011/03/nexuss_nfc_gettechlist.html">前回のエントリ</a>で「MifareClassicにNdefFormatableで書きこむとどうなるのか？」という疑問がありましたので、調査を続けていきます。<br/>
今回は準備として MifareClassic カードに読み書きをしたり、データのバックアップやリストアを実装してみました。<br/>
しかし、その過程でいつものようにトラブルが発生し、 解決のためにMifareClassicの仕様を調べてみたところ、
MifareClassicは後戻りできない書込みもできてしまうことなども判明しましたので、その辺りを共有したいと思います。<br/>
<br/>
何せこのカードは私物の保育園の入館証なので、再起不能にしてしまったかと色々冷や汗物語はありましたが、
無駄に長くなるので、なるべく要点を記述していきたいと思います。<br/>
<br/>
</p>
]]>
      <![CDATA[<h2>（１） MifareClassicの仕様</h2>
<p>
今回参考にしたのは、
<a href="http://www.scdeveloper.com/datasheet/m043531.pdf">Mifare Standard 4 kByte Card IC</a>
という資料です。 Mifare Classic と呼ぶべきか Mifare Standard と呼ぶべきかわかりませんが、とりあえず MifareClassicと呼ぶことにします。<br/>
<br/>
私のカードは、 MifareClassic インスタンスの応答によると、<br/>
<ul>
<li>getSize(): 1024  // SIZE_1K</li>
<li>getBlockCount(): 64 // 全体で64ブロック</li>
<li>getSectorCount(): 16 // 全体で16セクタ、つまり、1セクタ4ブロック。</li>
</ul>
ということなので、 SIZE 1K です。
この資料はタイトルからして4KBのものですが、仕様はほぼ同じなのではないかと思います。<br/>
<br/>
MifareClassic の仕様を箇条書きに書いてみます。<br/>
<ul>
<li>１つカードには複数の「セクタ」があり、１つの「セクタ」には複数の「ブロック」がある。１ブロックは16byteである。</li>
<li>セクタ単位で、アクセス権を定めることができる。</li>
<li>最初のセクタの最初のブロックは、IC manufacturer data があり<b>書込みはできない</b>(最初のセクタでも2番目、3番目はDataBlock)。最初の４バイトがSerialNumber(=ID)、次の1byteがcheckbyte、残りが manufacturer data である。
</li>
</ul>
<pre>
    |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
                |  |  
    SerialNumber|  |         manufacturer data
              checkbyte
</pre>
<ul>
<li>各セクタの最後のブロック(今回のカードなら、3,7,11,,, 番目のブロック)は、「Sector Trailer」と呼ばれる部分で、２つの鍵(KeyA,KeyB)とアクセス権情報が格納されている。
</li>
</ul>
<pre>
    |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
                      |           |
          Key A       | AccessBits|      Key B
</pre>
<ul>
<li>その他のDataBlockは、「read/write ブロック」か「value ブロック」である( アクセス権情報で規定される )。read/writeブロックは、単純なRead/Writeが可能で、ValueブロックはIncrementやDecrement等ができるようだ。<br/>
    ※今回は、 read/write ブロックのみ関心をもって調べました。
</li>

<li>アクセス権情報には、「Sector Trailer へのアクセス権」、「DataBlock へのアクセス権(とDataType)」 が含まれる。<br/>
    ※アクセス権の読み方は、参考資料の P12, P13, P14 を読んでください。細かすぎてここには書けません。。
</li>

<li>KeyAで行える操作、KeyBで行える操作(read/write権)が異なる(P13,P14)。 
</li>

<li> (仕様を見る限り、今回試した限り)<b>KeyAは端末側は読み出すことができない</b>(端末からは<b>WriteOnly！</b>)。読み込むと6byteの「0x00」 が戻ってくる（コレが罠）。
</li>

<li>ブロックを読み書きするには、まず、該当セクタに対して鍵Aか鍵Bで認証して、認証OKならば、 AccessBits で許可されている操作ができる。
</li>
</ul>
ざっとこんなところでしょうか。<br/>

また Android側のAPIで、 MifareClassic には、 鍵がいくつかstatic finalで定義されていて、
<ul>
<li>KEY_DEFAULT: 	The default factory key.  (値は、 6バイト全て 0xFF )</li>
<li>KEY_MIFARE_APPLICATION_DIRECTORY: 	The well-known key for tags formatted according to the MIFARE Application Directory (MAD) specification.<br/>
    (値は, 0xA0A1A2A3A4A5)
</li>
<li>KEY_NFC_FORUM: 	The well-known key for tags formatted according to the NDEF on Mifare Classic specification.<br/>
    (値は、0xD3F7D3F7D3F7)
</li>
</ul>
とあります。<br/>
<br/>
</p>

<h2>（２） MifareClassicで読み書きするには</h2>
<p>
「読み書きできるカード」があることが前提になりますが、例えば私のカードは、
<ul>
<li>Trailerへの読み書き権限は(C1,C2,C3)=(0,0,1)で、 「KeyAを使えば、KeyA以外全て読み取り可能 ＆ 全て上書き可能」という状態(transport configuration という言葉をよく見かけます)。
</li>

<li>DataBlockへの読み書き権限は(C1,C2,C3)=(0,0,0)で、 「KeyAでもKeyBでも、全て読み書き可能」という状態。
</li>

<li>１つのセクタを除いて、DataBlockは全て「0xFF」だった。そのセクタは、鍵が異なるようで読み書き不能だった。
</li>
</ul>
でした。<br/>
<br/>
<br/>
ポイントとなる手順は、例えば以下のようになります。<br/>
<pre><xmp>
MifareClassic mc = MifareClassic.get(tag); // tag は android.nfc.Tag
if (!mc.isConnected()) { mc.connect(); }
if (mc.authenticateSectorWithKeyA(sectorIndex, MifareClassic.KEY_DEFAULT)) { // KEY_DEFAULT を使う場合
  // for read
  mc.readBlock(targetBlock);
  // for write
  mc.writeBlock(targetBlock, writeData);
}
// 以下も便利
// mc.getBlockCountInSector(sectorIndex); // セクタ内に含まれるブロック数を返す
// mc.sectorToBlock(sectorIndex); // そのセクタの最初のBlockIndexを返す
</xmp></pre>
</p>

<h3>注意点！</h3>
<p>
「 Sector Trailer 」ブロックには注意が必要で、ここをうっかり上書きすると、後戻りできない事態になる可能性があります。 <br/>
私の場合は、とりあえず KEY_DEFAULT でアクセスできたので、<br/>
何も考えずにカードに対して「全部読みだしてファイルに書き出す」「ファイルから全部書き戻す」というプログラムを最初に作ってしまい、<br/>
KeyAの読み取りが全部「0x00」で戻ってきているのに、そのデータをKeyAに書きこんでしまいました。<br/>
</p>

<p>
つまり、KeyAを「ALL 0xFF」->「ALL 0x00」と変更してしまったわけです。<br/>
すると途端に、読み書き不能になり、とても焦りました。。<br/>
改めてKeyAを「ALL 0xFF」に書き戻すと元に戻りました。うっかり他のByte列を書き換えてなくて良かったです。。<br/>
Dumpを書き戻すのはDataBlockだけにすることにしました。<br/>
</p>

<h2>（３） おわりに</h2>
<p>
というわけで、保育園のカードは、ID情報か鍵でプロテクトしてあるブロックのみを見ているようなので、おそらく何を書いても大丈夫なきがしてきました。(さすが！いや、当然??)<br/>
<br/>
次回は、 NdefFormatable を使って書きこんでみようと思います。<br/>
予想としては、KeyAやKeyBが KEY_NFC_FORUM になったり、 NdefFormatable#formatReadOnly() を使うと、アクセス権をReadOnlyにしたりするのではないかと思いますが、<br/>
やってみてからのお楽しみというところですね。<br/>
</p>

]]>
   </content>
</entry>
<entry>
   <title>NexusSによるNFC調査： getTechList 編</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/03/nexuss_nfc_gettechlist.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.465</id>
   
   <published>2011-03-01T13:14:42Z</published>
   <updated>2011-03-01T13:16:33Z</updated>
   
   <summary> getTechList() という「AndroidがNFC系カードをどのような種別として認識するか」という結果について簡単にメモしておきます。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="22" label="android" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
こんにちは、ちきん(mokemokechicken)です。<br/>
さて、私のNexusSでもやっとAPI10が利用可能になったので、現在色々調査中なのですが、
今回は、 getTechList() という「AndroidがNFC系カードをどのような種別として認識するか」という結果について簡単にメモしておきます。
</p>
<h2>getTechList()の結果</h2>
<p>
<a href="http://developer.android.com/reference/android/nfc/Tag.html#getTechList%28%29">android.nfc.Tag#getTechList()</a>は、
Android端末がNFC系カードを発見したIntentに含まれるTagインスタンスのメソッドで、
<pre>
Tag tag = (Tag)intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String[] techList = tag.getTechList();
</pre>
などで取得できる値です。<br/>
この techList に入る値は、 android.nfc.tech の TagTechnology を ImplementしたClassの(fully-qualifiedな)名前になります。<br/>
</p>
]]>
      <![CDATA[<p>
色々なカードを試していると、カードによってだいぶ値が違っていて興味深いです。
早速結果ですが、以下のようになりました。
<ul>
<li>Softbank 830SHs: <b>NfcF</b></li>
<li>AU K002: <b>NfcF</b></li>
<li>会社の社員証： <b>NfcF</b></li>
<li>FeliCaLite: <i>未所持なので未検証</i></li>
<li>保育園の入館証: <b>MifareClassic</b>, <b>NfcA</b>, <b>NdefFormatable</b></li>
<li>KさんのTASPO: <b>MifareClassic</b>, <b>NfcA</b>, <b>NdefFormatable</b></li>
<li>練馬区の住基カード(写真入り、2009年作成): 反応せず</li>
<li>運転免許証: <b>IsoDep</b>, <b>NfcB</b></li>
</ul>

NfcA(ISO 14443-3A) の MifareClassic は、２つとも NdefFormatable となっているのは興味深いです。<br/>
NfcB(ISO 14443-3B) や NfcF(JIS 6319-4, FeliCa) は、 NdefFormatable にはなりませんでした。<br/>
ただし、おそらく上記のNfcFカードは FeliCaStandard なので FeliCaLite では違う結果になるかもしれません。<br/>
噂によると、住基カードは NfcB という話なのですが、どうも反応しません。必ずしもそうではないということなのかもしれません。<br/>
運転免許証は、 NfcB と IsoDep(ISO 14443-4) という結果になりました。<br/>
<br/>
ここで気になるのは、 MifareClassicのカード(保育園の入館証) を NdefFormatable と認識をしているという点です。<br/>
これらのカードには、 NDEFデータとして書込みが可能かもしれず、 今までは発見Intentが TECH_DISCOVERED なのですが、
NdefFormatable で Formatすると NDEF_DISCOVERED でIntentされるかもしれない、という辺りがもう少し調べておきたいところです。<br/>
この調査結果はまた後日・・・<br/>
<br/>
</p>

<h2>補足： Intent Filter周りの設定</h2>
<p>
最後に、今回の調査で使ったときの設定を書いておきます。<br/>
Android2.3.3から、 NFC発見は４つの方法で通知されます。 <br/>
これらの意味については既に他のサイトでも色々説明されていますが、<a href="http://twitpic.com/42uyte">簡単にまとめた</a>中にも書いてあるので、こちらもご参考下さい。<br/>
</p>

<hr/>
<h3>AndroidManifest.xml の activity ブロックの中</h3>
<b>※NDEF_DISCOVEREDについては調査不十分ですが、text/plainに反応するならこんな記述になるかと思います。</b><br/>
<pre>
<xmp>
<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED"></action>
  <category android:name="android.intent.category.DEFAULT" />
  <data android:mimeType="text/plain" />
</intent-filter>

<intent-filter>
  <action android:name="android.nfc.action.TECH_DISCOVERED"></action>
  <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" 
             android:resource="@xml/nfc_filter"/>

<intent-filter>
  <action android:name="android.nfc.action.TAG_DISCOVERED"></action>
  <category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
</xmp>
</pre>

<hr/>
<h3>res/xml/nfc_filter.xml</h3>
1つのtech-listにたくさん指定すると、同時にサポートしてないと反応しない。tech-listに１つずつ全部書くととにかく反応する。
<pre>
&lt;?xml version="1.0" encoding="utf-8"?&gt;<xmp><resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- どれか一つでもサポートしていたら反応する。
	      検証用設定。本当は使うもののみにすべき。 -->
     <tech-list><tech>android.nfc.tech.IsoDep</tech></tech-list>

     <tech-list><tech>android.nfc.tech.NfcA</tech></tech-list>

     <tech-list><tech>android.nfc.tech.NfcB</tech></tech-list>

     <tech-list><tech>android.nfc.tech.NfcF</tech></tech-list>

     <tech-list><tech>android.nfc.tech.NfcV</tech></tech-list>

     <tech-list><tech>android.nfc.tech.Ndef</tech></tech-list>

     <tech-list><tech>android.nfc.tech.NdefFormatable</tech></tech-list>

     <tech-list><tech>android.nfc.tech.MifareClassic</tech></tech-list>

     <tech-list><tech>android.nfc.tech.MifareUltralight</tech></tech-list>
</resources>
</xmp></pre>

]]>
   </content>
</entry>
<entry>
   <title>Android2.3.2(rooted)→2.3.3 への手動Update ＆ root化失敗による起動不能からの復旧メモ・・・</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/02/android_nexuss_update_rooted_recovery.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.464</id>
   
   <published>2011-02-28T02:51:58Z</published>
   <updated>2011-02-28T05:24:31Z</updated>
   
   <summary>NexusS の2.3.3へのUpdateとRoot化を行った際のトラブルと復旧記録</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="73" label="Android" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<h1>はじめに</h1>
<p>
こんにちは、ちきん(mokemokechicken)です。<br/>
NexusS の2.3.3のOTAが始まりましたが、root化しているので、ちゃんと適用されるかわからないので、手動でやってみました。<br/>
2.3.3へのUpdateは無事上手くいったのですが、再度のRoot化で見事にハマりました。<br/>
私のようなAndroid初心者の方も多いと思われ、こういう失敗の共有もそれなりに有意義だと思ったので、その顛末を公開しておきたいと思います。<br/>
メモベースなので、乱文乱筆すみません。
</p>

<h1>UpdateとRoot化</h1>
<h2>NexusS 2.3.2(rooted) -> 2.3.3 への手動Update</h2>
<h3>Update前の環境</h3>
NexsusS＆Android2.3.2で

<ul>
<li>oem unlock</li>
<li>root化(boot.superboot.img という 2.3.2専用のimgを入れた。そしてこれが後々仇になる・・・)</li>
<li>ClockworkMod(=CWM) 3.0.0.5 導入済み</li>
</ul>
という状態でした。]]>
      <![CDATA[<h3>手順１</h3>

<a target="_blank" href="http://forum.xda-developers.com/showthread.php?t=967953">http://forum.xda-developers.com/showthread.php?t=967953</a>
にあるように、Link for GRH78C 用のZIPファイルをDLしてくる。
それを <b>update.zip</b> という名前で Android の /sdcard/ に置く。

<ul>
<li>電源を消す(Power長押し)</li>
<li>Volumeの＋を押しながら、電源を長押ししてから、recovery mode に入る(Volumeの-を２回押して、電源キー)</li>
<li>apply update from sdcard, を選び、 update.zip を適用する</li>
</ul>
<br/>
上手くいけばこれでOKです。<br/>
<br/>

<h3>手順２: 手順１が上手くいかない(Status 7 エラー)場合</h3>
手順１を実行しても、
<pre>
assert failed: apply_patch_check("MTD:boot:2949120:   ...etc

E:Error in /sdcard/update.zip
(Status 7)
</pre>
<p>
というメッセージが出て止まる場合があります。<br/>
これは、現在のInstallされているbootイメージがTargetと異なるということと思われます。<br/>
私も boot.superboot.img が bootのimgなので、正規版とは違うのでこうなるようです。<br/>
これは今回は<br/>
<a href="http://forum.xda-developers.com/showpost.php?p=11620726&postcount=478">http://forum.xda-developers.com/showpost.php?p=11620726&postcount=478</a>
にある手順で解決できました(boot.img を 正規版に書き戻してからupdate.zipを適用する)。<br/>
</p>

<h2>Root化再び</h2>
<p>
Root化が解かれているので、再度行います。<br/>
おそらく、この時点のイメージを保存しておかないと、またStatus7を次のUpdateでくらいそうなので、
CWMでバックアップを取っておきます。<br/>
と思ったら、Root化されていないので、CWM動かない。。<br/>
<br/>
うーん、じゃあ、まあいいや。また、 boot.superboot.img でも入れたらいいのかなぁ、と思って、<br/>
<pre>
  install-superboot-windows.bat
</pre>
という以前Root化に使ったBATを実行してみる。<br/>
Installは成功(内部で、fastboot flash boot boot.superboot.img; fastboot reboot しているだけ)<br/>
<br/>
Rebootが始まる<br/>
・<br/>
・<br/>
・<br/>
起動しない。。。。<br/>
<br/>
あー、そっか。全然Version違うからダメなのか、、と実は途中から思っていたが、<br/>
やってしまった。でも後悔はしていない。
</p>

<h2>文鎮になりつつあるAndroidの復旧開始</h2>
<p>
ここから、冷や汗をかきながら復旧を試みる。<br/>
なんか、昔適当にLinuxを弄っていたころを思い出すなぁ・・・まあ、今も適当にいじるだけだけどさ!<br/>
一応、adb shell とか fastboot とかはつながるので、一安心。<br/>
<br/>
世間で公開されているimgはCWM対応のものばかりで、(google先生が教えてくれるのは)、<br/>
CWMが無いとなかなか操作が辛い。UpdateによってかCWMもいずこかに行っているので苦戦する・・・<br/>
<br/>
boot.superboot.img も中身を観たかったが、 unyaffs が broken image とか言って仕事してくれない。どうも微妙に形式が異なるのかなぁ？<br/>
（後で弊社CTOから聞いた話だと、boot, recovery はちょっと形式が異なるらしいです。system 等はいけるらしいです。そこまで気がつかなかったなぁ・・）<br/>
<br/>
2-3時間経過する。。。<br/>
<br/>
おかげでいくつかわかったことがある。<br/>
<br/>
<ul>
<li>イメージ(img)をflashする(installする)のは、意外と気軽にできる。bootloaderが起動しなくなるということはなさそうだ。bootloaderが起動するなら、USB経由でfastbootコマンドも使えるので、最終的にはなんとかなるだろうと思う。</li>

<li>適当に落ちている .img を flash しようとしても、FAILED (remote: Write Fail) とか言われて失敗する。大抵CWMのバックアップから抜き出しているので独自の形式のものなのかもしれない。うまくいくimgはうまくいく。謎。。</li>

<li>実際にAndroidにimgをInstallするには、
<pre>
fastboot flash (partition) (.img)
例: 
    fastboot flash boot boot.img
    fastboot flash recovery recovery.img
</pre>
を使い、手元(PC)にあるimgでOSを起動するには、
<pre>
fastboot boot (.img)
</pre>
を使う。この場合、実際にはimgは書き込まれないから適当にやっても安心である。</li>

<li>また、Bootは２つのモードがあり、 boot, recovery である。それぞれは、それぞれのpartitionにInstallされているimgから起動される。<br/>
既出だが、NexusSでは Volume＋と電源で起動すると、bootloaderモードになって、どちらのimgで起動するか選べる、ということだったのだ。今理解した。</li>
</ul>
で、「NEXUSS GRI40 stock」とかでググルと、既にCWMでバックアップしてrootedなimgを公開している人がいるのを発見する。DLする。<br/>
<br/>
現時点の状態は「GRI40(=2.3.3) だけど bootだけはGRH78C(=2.3.2)」という状態なので、boot.img を書き戻せばいいはずである（願望）。<br/>
<br/>
GRI40を展開して、boot.img を取り出し、<br/>
<pre>
fastboot flash boot boot.img
</pre>
とかする。ダメである。(FAILED (remote: Write Fail)) 。<br/>
じゃあ、<br/>
<pre>
fastboot boot boot.img
</pre>
は、というとOKである。<br/>
<br/>
数時間ぶりに、NexusSが起動してくれた。ちょっと安心。やっと前に進み始めたかな。。<br/>
<br/>
ROMManagerから改めてCWMをInstallする。Root化されているのですんなりOK。<br/>
<br/>
で、せっかくDLしたので GRI40-2.3.3-rooted をCWMからInstallする。<br/>
<br/>
なんとか通常起動でも 2.3.3 になったが、アプリとか全部消えてしまった。<br/>
そりゃそうか。でも、だいたいわかってきたのでもう一度今日をやり直してみよう。
</p>
<h1>今日の復習</h1>
<h2>まず、Updateを再実行</h2>

まず、GRI40-rootedでRomManagerとCWMをInstallする。<br/>
と思ったが、アカウントの設定とかAPNとか全部消えてて超めんどい。<br/>
<br/>
とにかくCWMが起動すればいいんだが、と思っていたらSDCARDにそれっぽいの発見。<br/>
<pre>
adb pull /sdcard/clockworkmod/download/mirror1.kanged.net/recoveries/recovery-clockwork-3.0.0.5-crespo.img .
</pre>
んじゃあ、recovery領域にInstallしてみよう。<br/>
<pre>
adb reboot bootloader
fastboot flash recovery recovery-clockwork-3.0.0.5-crespo.img
</pre>
エラー出なかった。よしよし。リブート。
<pre>
fastboot reboot
adb reboot recovery
</pre>
あれ、、リカバリーモードがまたおかしいや(アンドロイド君が困ってる。デフォルトのが起動する。)、、失敗。。<br/>
じゃあ、インスタントな起動でいけるかな？ 
<pre>
fastboot boot recovery-clockwork-3.0.0.5-crespo.img
</pre>
でCWM起動。上手くいった。<br/>
<br/>
ようやくCWMを使ってBackupしていた今日のUpdate前(2.3.2)に戻す。restore 無事成功！<br/>

今日の、手順１、手順２を再度実行する。とりあえず、状況確認のために通常起動。<br/>
AngryBirdsのデータもOK！ （これで娘に怒られなくて済む、とホッとしたのは秘密）<br/>
マーケットにもアクセスOK!<br/>
<br/>

<h2>再度！懲りずに！ rootに挑戦</h2>
<p>
今度は大人しめにKazzzさんの方法でいくことにする。<br/>
参考： <a href="http://d.hatena.ne.jp/Kazzz/20110110/p1">http://d.hatena.ne.jp/Kazzz/20110110/p1</a>
</p>
<br/>
再び recovery mode に。。と思ったらまたこける。この辺の機微がよくわからない。（編集後記： あーrecoveryが書き変わっているなら当たり前かな？）<br/>
また、fastboot から CWMのimgを直接起動してもいいけど、<br/>
もう一度 ROMManagerで CWMをInstallしてみる。<br/>
<br/>
と思ったら、今度はRoot化されていないから駄目だったよ。。。<br/>
やはり、<br/>
<pre>
adb reboot bootloader
fastboot boot recovery-clockwork-3.0.0.5-crespo.img
</pre>
でいくことにする。これは成功。もうこれがあればいいや、的な気分にもなってくる。<br/>
<br/>
あとは、Kazzzさんの手順にならう。<br/>
su-2.3.6.1-ef-signed に含まれているファイルを使って、以下のようにする。<br/>
<br/>
* CWM: mount /system<br/>
<pre>
adb push su /system/bin/
adb push SuperUser.apk /system/app/
adb shell chmod 6775 /system/bin/su
</pre>
* CWM: umount /system<br/>
* CWM: reboot <br/>
<br/>
普通っぽくちゃんと起動！<br/>
<br/>
ROMManager で もう一度 CWM をInstallしてみる。<br/>
成功!<br/>
<br/>
もう一度 Recover Modeを試す。今度はちゃんと起動する。<br/>
よし終わり！<br/>
<br/>

<h1>さいごに</h1>
<p>
Updateは計画的に行いましょう(^^;<br/>
まあ、でも時間があればなんとかはなるので色々やってみるのも楽しいですね。<br/>
もし、同様な感じで困っていて、もう少し本記事の内容や手順について聞きたい方がいれば、気軽にmokemokechicken@twitter までご連絡下さい(＾＾。
</p>]]>
   </content>
</entry>
<entry>
   <title>VirtualBoxでVolume group &quot;VolGroup00&quot; not found というエラーが発生した場合の対処方法</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/02/virtualbox_grub_fstab_volgroup00_not_found.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.462</id>
   
   <published>2011-02-16T07:26:54Z</published>
   <updated>2011-02-16T07:29:23Z</updated>
   
   <summary>VirtualBoxでCentOSのイメージファイルをコピーした際などに発生することがある、Volume group &quot;VolGroup00&quot; not foundというエラーの対処方法についてご紹介します。VirtualBoxのイメージファイルをクローニングする方法も解説しています。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Kernel" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="76" label="fstab" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="77" label="grub" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="78" label="initrd" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="80" label="linux rescue" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="81" label="mkinitrd" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="82" label="vbox" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="84" label="VirtualBox" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
  こんにちは<br />
  kaoruです
</p>

<p>
  今回は、ちょっとしたTipsです。VirtualBoxでCentOSのイメージファイルをコピーした際などに発生することがある、Volume group "VolGroup00" not foundというエラーの対処方法についてご紹介します。
</p>

<p>
  あるひとつのエラーに対する対処方法ですが、VirtualBoxやvmwareなどの仮想環境を開発環境に利用することは一般的ですので、意外と同じ問題で困っている方がいらっしゃるのでは・・・と思います。

</p>
<p>
  私も英語サイトも含めて対処方法を検索してみましたが、同じエラーメッセージが出て質問されている方は何人かいらっしゃったものの、ずばり解決という回答を掲載しているWebサイトは発見できませんでしたので、この記事がご参考になればと思います。
</p>

<h4>エラーが発生するシチュエーション</h4>
<p>
  CentOSがインストールされたVirtualBoxのイメージファイルをクローニングした場合、ホスト間で移動した場合、vmwareのイメージファイルをVirtualBoxにコンバートした場合などに発生するようです。今回発生したCentOSのバージョンは5.4です。発生する原因については、下記の参考になるサイトにあるVirtualBoxのしくみの説明が参考になります。
</p>

<h4>解決方法</h4>

<h5>rescueモードで起動する</h5>
<p>
  CentOSのイメージファイル(ISOイメージファイル)をダウンロードします。
  VirtualBoxの仮想メディアマネージャーに登録します。
  仮想環境設定のストレージに割り当てをします。
  そのまま仮想環境を起動します。
  linux rescueと入力してrescueモードで起動すればOKです。
</p>

<h5>initrdを再作成</h5>
<p>
  以下のコマンドを実行してinitrdを再作成します。なお、<a href="http://www.atmarkit.co.jp/flinux/rensai/linuxtips/572scsion.html" target="_blank">@ITの記事</a>を参考にしています。***の部分は環境によって異なります。uname -rで得られる結果を利用してください。
</p>
<pre>
  chroot /mnt/sysimage
  depmod -a
  uname -r
  mv /boot/initrd-******.img /boot/initrd-******.img.org
  mkinitrd /boot/initrd-*****.img *******
</pre>

<h5>/boot/grub/grub.cfgを確認</h5>
<p>
  上記で作成した*.imgファイルがinitrdに指定されていることを確認します。
</p>

<h5>/etc/fstabを確認</h5>
<p>
  ディスクのマウント情報が正しいかどうか確認します。
</p>

<h5>仮想環境を再起動</h5>
<p>
  仮想環境設定のストレージに割り当てを解除してから、仮想環境を再起動すればOKです。
</p>

<h4>補足：VirtualBoxの仮想イメージファイルを変換・クローニングする方法</h4>
<p>
  VirtualBoxの仮想イメージファイルは単にコピーしてリネームしてもうまく動作しません。
  正しくは、VBoxManege clonehdコマンドを利用します。
</p>
<pre>
  VBoxManege clonehd old.vdi new.vdi
</pre>
<p>
  また、入力・出力はvmwareの仮想イメージファイル(*.vmdk)など、他のファイルの種類を指定することも出来ます。
</p>
<pre>
  VBoxManege clonehd old.vmdk new.vdi
</pre>

<h4>参考になるサイト</h4>
<ul>
    <li><a href="http://www.virtualbox.org/manual/ch08.html#vboxmanage-clonevdi" target="_blank">http://www.virtualbox.org/manual/ch08.html#vboxmanage-clonevdi</a></li>
    <p>VBoxManageコマンドのリファレンス</p>

    <li><a href="http://www.virtualbox.org/manual/ch05.html#cloningvdis" target="_blank">http://www.virtualbox.org/manual/ch05.html#cloningvdis</a></li>
    <p>VirtualBoxでディスクイメージをクローンする際の注意点</p>

    <li><a href="http://www.gnu.org/software/grub/" target="_blank">http://www.gnu.org/software/grub/</a></li>
    <p>GNU GRUBのリファレンス</p>

    <li><a href="http://www.atmarkit.co.jp/flinux/rensai/linuxtips/572scsion.html" target="_blank">http://www.atmarkit.co.jp/flinux/rensai/linuxtips/572scsion.html</a></li>
    <p>initrdの作成方法</p>

    <li><a href="http://www.atmarkit.co.jp/flinux/rensai/linuxtips/572scsion.html" target="_blank">http://www.atmarkit.co.jp/flinux/rensai/linuxtips/572scsion.html</a></li>
    <p>initrdの作成方法</p>
</ul>
]]>
      
   </content>
</entry>
<entry>
   <title>AndroidのNFC機能でFeliCaの読み書きをする</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/02/androidnfcfelica.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.459</id>
   
   <published>2011-02-10T02:02:22Z</published>
   <updated>2011-02-10T03:22:25Z</updated>
   
   <summary>NexusSを使って、NFCを利用して、FeliCaデータの読み書きをするAndroidアプリを作ったのでそのご紹介をします。 </summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="73" label="Android" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="75" label="NFC" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
初めまして、ちきん(mokemokechicken)です。 今回は、NexusSを使って、FeliCaデータの読み書きをするAndroidアプリを作ったのでそのご紹介をします。
</p>
<p>
AndroidOSの2.3から NFC(Near Field Communication)用のAPIがサポートされるようになりました。 NFCの特徴については<a target="_blank" href="http://goo.gl/tiCIH">この@ITの記事</a>が参考になります。<br />
<br />
NFCの通信規格は、日本で普及しているFeliCaの通信規格を含んでいるので、大抵のNFCリーダー・ライターで物理的にはFeliCaにアクセスすることがでそうですが、 現在の標準NFC APIでは、NDEFデータ以外にアクセスはできません(IDmは取得できますが)。<br />
<br />
しかし、先人たちの努力によって遂に隠しClassを通して、FeliCaのコマンドを発行できるようになり、<a target="_blank" href="http://goo.gl/G9vAI">そのソースコードが公開</a>されました。<br />
<br />
ちょうどその時、kouさんがNexusSを調達されており、Android,NFC,FeliCaについて遊ぶ・・ぢゃなくて調査することになりました。
</p>
<p>
その結果、NexusSと隠しClassによって、FeliCaに対して以下の操作ができることがわかりました。
</p>
<ul>
<li>システムコード一覧の取得： システムコードは、アプリケーション毎に定義されたシステム領域です。</li>
<li>サービスコード一覧の取得： サービスコードは、システム領域内の「ファイル識別子」のようなもの？です。</li>
<li>サービスコード内のデータの読みこみ： 但し、非暗号化領域に限ります。</li>
<li>サービスコード内のデータの書きこみ： 但し、非暗号化＆書込み可能領域に限ります。</li>
</ul>
ただし、隠しClass(@hide)を用いての実装なので、NexusS以外のAndroid2.3端末や、今後のAndroidOSのVersionUP次第ではできなくなる可能性もありますので、一般サービスとしては使えないです。<br />
ですが、どのFeliCaカードがどういう領域を持っているか調べたり、書込み可能領域を編集できるのは、今後色々役に立ちそうなので、今回アプリを作ってみました。
<p>
&nbsp;
</p>
]]>
      <![CDATA[<p>
作成したアプリは、<a target="_blank" href="http://goo.gl/ERaZX">github: Android_NFC_FelicaEdit</a>にソースコードを公開しています。<br />
Androidアプリを作ったのは初めてなのもあり、UIとかあまりイケテナイですが、技術者向けのアプリということでご勘弁下さい。<br />
</p>
<h2>アプリの紹介</h2>
<p>
現時点で実際にこのアプリを動かすには、NexusSを調達する必要があり、なかなか難しいのでスクリーンショットでご紹介します。
</p>
<table >
<tbody><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/intent.jpg" />
</td><td>まず、本アプリをインストールしたNexusSをFeliCaに近づけると、Intentが発生し、どのアプリで処理するか聞かれます。<br />
<br />
ここで、本アプリFelicaEditを選びます。</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/system_au.jpg" />
</td><td>すると、システムコードの一覧が表示されます。<br />
例えば、AU携帯を見た場合こんな感じになります。<br />
3個のシステム領域があることがわかります。ちなみに、「FE00」は共通領域と呼ばれる領域です。「0003」はSuicaなどの領域のようです。「FE0F」はAU独自の領域なのでしょうか？？<br />
</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/system_sb.jpg" />
</td><td>また、例えば、Softbankの携帯を見た場合、こんな感じになります。<br />
この携帯にはFE00しかないことがわかります。次に、このままFE00をTAPしてみます。</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/service_list1.jpg" />
</td><td>すると、このように表示されます。<br />
「7048」とかあるのが「サービスコード」です。このサービスコードを見るとアクセス権がわかるようになっています(JIX_X_6319-4 を参照)。<br />
Lockedとなっている部分は「暗号化領域」なので、(鍵を知らないと)見ることも書くこともできません。また「RO」はReadOnlyの部分で読むことはできますが、書くことはできません。<br />
で、「サービスコード:70C9」は、「RW(24)」となっていて、ReadWriteできる非暗号化領域が24Blockあることを表しています。ので、本アプリで書込みができます。ちなみに1Blockは16Byteです。この「70C9」をTapしてみます。<br />
</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/service_list2.jpg" />
</td><td>すると、 と、Blockのデータが表示されます。Byte列の16進表示で、4バイト毎に区切ってあります。続いて「Block00」をTapしてみます。</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/block1.jpg" />
</td><td>すると、データが1バイト毎に縦に並ぶので、一番上をTapしてみると、</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/block2.jpg" />
</td><td>となり、ここでByte値の編集ができます。</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/block3.jpg" />
</td><td>4bit毎に16進で指定します。この辺のUIがなんか間延びしているのが、あれですが、、</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/block4.jpg" />
</td><td>「5F」とかにしてみました。<br />
その後、「OK」を押したあと、上の「Save」ボタンを押しますと、</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/block6.jpg" />
</td><td>となり、</td></tr><tr><td>
<img height="400" align="top" width="240" src="http://yumewaza.yumemi.co.jp/img/postfile/service_list3.jpg" />
</td><td>のように更新されていることがわかります。</td></tr></tbody>
</table>
<p>
本アプリの用途は、
</p>
<ul>
<li>FeliCaの中身がどうなっているか見てみたいとき</li>
<li>書込み可能領域を何か編集したいとき</li>
<li>自分でFeliCa向けツールを作りたいとき、多少の参考に</li>
</ul>
という感じですが、ご興味と機会があれば是非使ってみてください。<br />
<br />
<br />
※ちなみに、悪いこと(例えばEdyの金額を書き換えるとか)はできませんよ?(^^;
<p>
&nbsp;
</p>
<h2>参考にしたURL</h2>
<ul>
<li><a target="_blank" href="http://goo.gl/G9vAI">Kazzzの日記: nfc-FeliCa rev5</a></li>
<li><a target="_blank" href="http://goo.gl/msjSP">JIS_X_6319-4</a></li>
<li><a target="_blank" href="http://goo.gl/j1N9K">PaSoRi/RC-S320</a>: FeliCaコマンド情報</li>
</ul>
<p>
■追記： 2011/2/10 <br/>
<a href="http://goo.gl/ogNbl">Android2.3.3のAPIが公開</a>され、どうやら隠しClassを使わなくてもよくなっているようです。
引き続き調査していこうと思います。
</p>]]>
   </content>
</entry>
<entry>
   <title>Tweepon開発でGoogle App Engineに初挑戦</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2011/02/tweepon-google-app-engine.html" />
   <id>tag:yumewaza.yumemi.co.jp,2011://5.458</id>
   
   <published>2011-02-08T02:33:06Z</published>
   <updated>2011-02-08T03:00:06Z</updated>
   
   <summary>Tweeponの開発でPython版のGoogle App Engineに初挑戦しました。Google App Engine初心者の筆者が、開発環境を構築して開発を始めるまでに通った手順やノウハウをご紹介します。PyDevのGoogle App Engineへの設定方法などもわかりやすくご紹介します。</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Google App Engine" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="66" label="Google App Engine" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="68" label="PyDev" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="70" label="Python" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="72" label="Tweepon" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<style>
.yumewaza_20110207 li {
  margin-top: 0.5em;
  margin-bottom: 0.5em;
}
</style>
<div class="yumewaza_20110207">
<p>
  少し、間が空いてしまいました。<br />
  kaoruです。
</p>

<img alt="iPhone-tweepon-dev-300px.jpg" src="/img/postfile/iPhone-tweepon-dev-300px.jpg" width="300" height="402" style="float:right;margin-left:1em;margin-bottom:1em;" />

<p>
  このところ、Twitter連携サービス「Tweepon」の開発に携わっています。<br />
</p>

<p>
  Tweeponはまだ生まれたばかりのサービスですが、無料でお店情報やお得なクーポンをGet！！できるサービスですので、是非ご利用ください。<br />
</p>

<p>
  Tweepon iPhoneアプリ<br />
  <a href="http://tweepon.jp/app_store" target="_blank"><img alt="app_dl.png" border="0" src="/img/postfile/app_dl.png" width="200" height="62" /></a><br />
  <a href="http://tweepon.jp/yumemi" target="_blank">http://yumemi.co.jp/tweepon/</a><br />
</p>

<p>
  Twitterもはじめました！<br />
  <a href="http://tweepon.jp/tweepon_appli" target="_blank">@tweepon_appli</a>
</p>

<p>
  この開発では、仕事では初めてGoogle App Engineを利用してみました。<br />
</p>

<p>
  今回は、Google App Engineでの開発をはじめるにあたって色々と調べたノウハウをご紹介します。<br />
  なお、開発言語はJavaとPythonが選べるのですが、今回はPythonを利用しました。<br />
</p>

<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  1. Google App EngineのサイトからSDKをダウンロードしてインストール
</h4>

<p>
  Google App Engineのサイトはぐぐるとすぐに出てきます。<br />
  後ほど紹介するのですが、慣れてくると英語版のサイトの方がお勧めです。<br />
  まずはSDKをダウンロードしてインストールしましょう<br />
</p>

<p>
  <a href="http://code.google.com/intl/ja/appengine/downloads.html" target="_blank">http://code.google.com/intl/ja/appengine/downloads.html</a>
</p>

<p>
  その後のGoogle App Engine設定はこちらに解説があります。
</p>

<p>
  <a href="http://code.google.com/intl/ja/appengine/docs/python/gettingstarted/devenvironment.html" target="_blank">http://code.google.com/intl/ja/appengine/docs/python/gettingstarted/devenvironment.html</a>
</p>


<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  2. Pythonをダウンロードしてインストール
</h4>

<p>
  Google App Engine SDKにはPythonはついてきませんので、自分でPythonをインストールする必要があります。<br />
  ここで注意しないといけないのは、今後変わるかもしれませんが、このブログの執筆段階ではPythonのバージョンが2.5でないといけない点です。
</p>

<p>
  Pythonのダウンロードページに行くと、一見2.7しかおいてありません。
</p>

<p>
  私はここで一旦あきらめて、Active Python (<a href="http://www.activestate.com/activepython/downloads" target="_blank">http://www.activestate.com/activepython/downloads</a>) を入れてみたのですが、なぜかエラーが出てGoogle App Engineの開発環境が起動できず。（これは私のやりかたが悪かったのかもしれません）
</p>

<p>
  結局こちらからwww.python.org版の2.5系をダウンロードしてインストールするとうまくいきました。
</p>

<p>
  <a href="http://www.python.org/download/releases/2.5/" target="_blank">http://www.python.org/download/releases/2.5/</a>
</p>

<p>
  気がついたら、2.5のありかを探すのに少し時間がかかってしまいました。
</p>

<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  3. Google App Engine Launcher に Python のありかを登録
</h4>

<p>
  Google App Engine Launcherを起動して、メニューの中の Edit &gt; Preferences... の中で、Pythonのパスの設定をします。<br />
  <code>例： C:\Python25\pythonw.exe</code>
</p>


<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  4. Eclipse + PyDev をインストール
</h4>

<p>
  1-3までの設定で、あとはテキストエディターがあれば一通りの開発が出来ます。
  しかし、本格的に開発をしたいならばデバッガーで変数の中を見たり、ステップ実行をしたりしたいところです。
</p>

<p>
  Eclipse用のPython開発環境であるPyDevは、Google App Engineに対応しているためデバッグ作業に非常に便利です。
  また、文法チェックやコード補完機能もあります。
</p>

<p>
  インストール方法はこちらに詳しく書いてあります。
</p>

<p>
  <a href="http://code.google.com/appengine/articles/eclipse.html" target="_blank">http://code.google.com/appengine/articles/eclipse.html</a>
</p>

<p>
  英語なので簡単に説明すると、
  <ol>
    <li>まずはEclipseをインストール</li>
    <li>ヘルプの中のInstall New Software...を選択</li>
    <li>Available Software Siteを選択</li>
    <li><code>http://pydev.sourceforge.net/updates/</code> を追加</li>
    <li>PyDevにチェックを入れてプラグインをインストール</li>
    <li>Window &gt; Preferences の中の PyDev &gt; Interpreter Python の中の Python Interpreters に Pythonのパスを追加</li>
  </ol>
</p>

<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  5. EclipseからGoogle App Engineのプロジェクトを作成
</h4>

<p>
  新規プロジェクトの作成ウィザードで PyDev &gt; PyDev Google App Engine Project を選択します。
  Nextを選んでいくとGoogle App Engineの場所を入力するように言われますので、インストールした場所を選択します。
</p>

<p>
  <code>例：C:\Program Files (x86)\Google\google_appengine</code>
</p>

<p>
  はじめは、Hello world プロジェクトなどを作ってみるとよいでしょう。
</p>

<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  6. デバッグ実行の方法
</h4>

<p>
  せっかくEclipse上で開発をするのですから、ステップ実行したりブレークポイントを使ったりしたいですよね！
</p>

<p>
  そこで、デバッグ実行の方法を説明します。
</p>

<ol>
    <li>メニューから Run &gt; Debug Configurations を選択</li>
    <li>PyDev Google App Runを選びます。</li>
    <li>ProjectにはGoogle App Engine用のプロジェクトを選択</li>
    <li>Main Module には、Google App Engine のdev_appserver.py を選択します。</li>
    例：${GOOGLE_APP_ENGINE}/dev_appserver.py
    <img alt="pyDev-debug-config.png" src="/img/postfile/pyDev-debug-config.png" width="344" height="289"   style="clear:both;margin-left:1em;margin-bottom:1em; margin-top:1em;" align="center" />

    <li>Argumentsにはdev_appserver.pyの起動オプションを書きます。</li>
    <p>
      こちらを参考にしてください。<br />
    </p>
    <p>
      <a href="http://code.google.com/intl/ja/appengine/docs/python/tools/devserver.html" target="_blank">http://code.google.com/intl/ja/appengine/docs/python/tools/devserver.html</a>
    </p>
    ここでは、
    <p>
      <code>${project_loc}/src</code>
    </p>
    と設定しました。
    <li>Applyを押して保存します。</li>
    <li>Debugボタンを押します。</li>
    <li>EclipseのConsoleに起動メッセージが出ればOKです。</li>
    <li>さっそくソースコードにブレークポイントを置いて、正しくデバッグできるかどうかみてみましょう。ブレークポイントはソースファイルの左の余白のところをダブルクリックすると置けます。</li>
    <li>Webブラウザから <code>http://localhost:8080/</code> にアクセスしてみてください。それからEclipseの画面を確認すると、ちゃんとブレークポイントが機能していますね。</li>
</ol>

<img alt="stop_on_break_point.jpg" src="/img/postfile/stop_on_break_point.jpg" width="400" height="172"  style="clear:both;margin-left:1em;margin-bottom:1em; margin-top:1em;" align="center" />

<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  7. Google App Engine 本番環境 (appspot.com)へのアップロード
</h4>

<p>
  開発が終了したら、Google App Engine 本番環境へアップロードします。<br />
  Google App Engine LauncherにあるDeploy機能を使うのが簡単です。
</p>


<!-- ///////////////////////////////////////////////////////////////////////////// -->
<h4>
  おわりに
</h4>

<p>
  いかがでしたでしょうか。<br />
  今回は、Google App Engineの開発環境の構築方法を中心にご紹介しました。
</p>

<p>
  Google App Engineを利用すると小規模なサイトなら無料で運営でき、開発環境も整っていて、とても簡単にサービスが作れます。
</p>

<p>
  是非、お試しください。
</p>
</div>
]]>
      
   </content>
</entry>
<entry>
   <title>MySQL &quot;SHOW ENGINE INNODB STATUS&quot;の読み方　その2</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2010/11/mysql_show_engine_innodb_statu_1.html" />
   <id>tag:yumewaza.yumemi.co.jp,2010://5.428</id>
   
   <published>2010-11-30T04:47:11Z</published>
   <updated>2010-11-30T04:49:03Z</updated>
   
   <summary> kouです。 だいぶ間が空いてしまいましたが、続きを書いてみたいと思います。 ...</summary>
   <author>
      <name></name>
      
   </author>
         <category term="MySQL" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="28" label="innodb" scheme="http://www.sixapart.com/ns/types#tag" />
   <category term="29" label="mysql" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[
kouです。


だいぶ間が空いてしまいましたが、<a href="http://yumewaza.yumemi.co.jp/2010/06/mysql_show_engine_innodb_statu.html" target="_blank">続き</a>を書いてみたいと思います。


<b>トランザクションセクション</b>

<pre>
14 ------------
15 TRANSACTIONS
16 ------------
17 Trx id counter 0 487637614
18 Purge done for trx's n:o < 0 487637607 undo n:o < 0 0
19 History list length 1
20 Total number of lock structs in row lock hash table 0
21 LIST OF TRANSACTIONS FOR EACH SESSION:
22 ---TRANSACTION 0 0, not started, process no 3424, OS thread id 1169332544
23 MySQL thread id 11166, query id 14476345 localhost root
24 SHOW ENGINE INNODB STATUS
25 ---TRANSACTION 0 487637613, ACTIVE 4799 sec, process no 3424, OS thread id 1089030464 starting index read, thread declared inside InnoDB 239
27 mysql tables in use 2, locked 0
28 MySQL thread id 10794, query id 14474613 172.16.xx.xx batch Sending data
29 SQL文（省略）
30 Trx read view will not see trx with id >= 0 487637614, sees < 0 487637614
</pre>


このセクションには現在実行中のトランザクションに関する情報が出力されます。



17行目の<i>Trx id counter</i>は現在のトランザクションIDカウンターの値を表します。これはトランザクション毎に発行されインクリメントされていきます。"0"と"487637607"と数値が二つありますが、上位32ビットと下位32ビットで区切られているためです。以下のその他の数値も同様です。


18行目の<i>Purge done for trx's n:0</i>はどこまでロールバックセグメントのパージ処理が終わったかを示しています。ここではトランザクションID "487637607"までのパージが終わっています。InnoDBは<a href="http://dev.mysql.com/doc/refman/5.1/ja/innodb-multi-versioning.html" target="_blank">マルチバージョニングデータベース</a>であるため古いバージョンのレコード情報を保持しています。レコード削除した場合でもストレージエンジン内ではすぐにレコードは物理削除されず、削除フラグが立った状態です。この削除フラグが立ったレコードは適切なタイミングでパージされます。ここでは"487637607"までパージされたことを示しています。

<i>undo n:o</i>はUNDOログのレコード数を示していて現在は"0"です。


19行目の<i>History list length</i>はロールバックセグメントでまだパージされていないヒストリ数です。


21行目からは個々のトランザクションの状態が表示されています。
この例ではトランザクションが二つです。22?24行目、25?30行目がそれぞれのトランザクションです。

22?24行目のトランザクションはこのコマンド（SHOW ENGINE INNODB STATUS）を発行している自身のトランザクションです。25?30行目が実際に発行されているSQLのトランザクションになります。


ここでは25?30行目のトランザクションをみていきます。


25行目の<i>TRANSACTION 0 487637613</i>は当トランザクションIDです。<i>ACTIVE 4799 sec</i>はこのトランザクションのステータスと生存時間です。"Active"となっていますので、現在処理中です。重たいクエリを発行していますので大変時間がかかっています。


<i>process no 3432</i>はOSのプロセス番号、<i>OS thread id 1089030464 starting index read</i>はOSのスレッドIDでインデックスデータを読み込んでいます。gdbなどでデバッグする際にこれらの情報が役に立ちます。

<i>thread declared inside InnoDB 239</i>はInnoDB内部でのスレッド割当状況です。


27行目の<i>mysql tables in use 2, locked 0</i>はこのトランザクションが利用しているテーブル数（"2"）と取得しているロックの数（"0"）を示します。


28行目の<i>MySQL thread id 10794, query id 14474613</i>はこのクエリ（"14474613"）がどのスレッド（"10794"）で実行されているかを表示しています。SHOW PROCESSLISTで表示される情報と同様です。


29行目は実際に実行されているSQL文を表示。


30行目の<i>Trx read view will not see trx with id >= 0 487637614</i>はこのトランザクション開始後に開始されたトランザクションIDです。また<i>sees < 0 487637614</i>はこのトランザクション開始前に終了していたトランザクションです。利用している<a href="http://dev.mysql.com/doc/refman/5.1/ja/innodb-transaction-isolation.html" target="_blank">トランザクション分離レベル</a>と前後のトランザクションの情報からこのトランザクション内でどのようなデータの見え方をしているかが推測できるため、トラブルシュート時に役立つことがあるかもしれません。





「その3」へ続く。





参考サイト/書籍：
<a href="http://www.mysqlperformanceblog.com/2006/07/17/show-innodb-status-walk-through/" target="_blank">MySQL Performance Blog - SHOW INNODB STATUS walk through</a>
<a href="http://dev.mysql.com/doc/refman/5.0/en/innodb-monitors.html" target="_blank">MySQL 5.0 Reference Manual - 13.2.13.2. SHOW ENGINE INNODB STATUS and the InnoDB Monitors</a>
<a fred="" target="_blank">実践ハイパフォーマンスMySQL 第2版</a>


]]>
      
   </content>
</entry>
<entry>
   <title>Android で LINQ を実現！</title>
   <link rel="alternate" type="text/html" href="http://yumewaza.yumemi.co.jp/2010/10/android_linq.html" />
   <id>tag:yumewaza.yumemi.co.jp,2010://5.423</id>
   
   <published>2010-10-20T04:09:19Z</published>
   <updated>2010-10-20T04:13:12Z</updated>
   
   <summary>AndroidプラットフォームにおけるSQLite、JSON、XML、その他コレクションを統一的記法で操作可能としました。
Javaでコレクション操作を簡略化するためのライブラリ「Collection Context」。
Javaで.NET FrameworkのLINQの機能を実現。
</summary>
   <author>
      <name></name>
      
   </author>
         <category term="Android" scheme="http://www.sixapart.com/ns/types#category" />
   
   <category term="22" label="android" scheme="http://www.sixapart.com/ns/types#tag" />
   
   <content type="html" xml:lang="ja" xml:base="http://yumewaza.yumemi.co.jp/">
      <![CDATA[<p>
<strong>はじめに</strong>
</p>
<p>
こんにちは。お久しぶりですね。hiroです。
</p>
<p>
昨今の、オブジェクト指向の考え方の普及に伴い、プログラマの誰もがオブジェクト指向言語を用いてソフトウェアを開発するようになりました。しかし同時にリレーショナルデータベースや、といった、オブジェクト指向では表されていないデータ構造へのアクセスが容易ではない事が課題として挙がっています。
</p>
<p>
この「Collection Context」ライブラリでは、これらの非オブジェクト指向なデータ構造を持つデータソースを、オブジェクト指向言語であるJava言語でいかに容易に扱うかをテーマに、コレクション操作が容易になるようなを提供しています。
</p>
<p>
<strong>1.JavaにLINQのエッセンスを</strong>
</p>
<p>
&nbsp;&nbsp;Microsoft社の.NET Frameworkでは、C# 3.0およびVisual Basic 9.0よりサポートされたLINQ式およびLINQ関連ライブラリのAPIセットによってコレクション操作を容易にしています。特にC#、VB言語においては、言語に統合されたクエリ式が記述できるように言語機能が拡張されています。これにより、メモリ上のオブジェクト、データベース上のオブジェクト、XMLなどがほぼ同一のクエリ記法によって操作が可能です。このように、.NET FrameworkはAPIが徐々にモダンに置き換わります。
</p>
<p>
&nbsp;&nbsp;一方、Javaにはこのような言語拡張はありません。コレクション関連のAPIも、Java Collection Frameworkがジェネリクスに対応したJ2SE 5.0以降、これといった拡張もありません。また、データベースアクセスはJDBCとして不動、XML操作はStAXがJava 6で加わりましたが、これらを横断して「データの集合」という概念で扱えるフレームワークやライブラリはありません。
</p>
<p>
&nbsp;&nbsp;最近、Javaには新しいプラットフォームが増えました。Androidです。Androidで採用されているJavaは、正確にはSun Microsystems社のJavaではありませんが、Java SEの主要なAPIをサポートし、さらにAndroidに特化したAPIを提供しています。このプラットフォーム上においても、コレクション操作に関するライブラリはかつてのJavaのままです。<br />
ということで、「JavaにLINQのエッセンスを」をテーマに、モダンなコレクション操作機能を提供するためのライブラリ、それが「Collection Context」です。
</p>
<p>
<strong>2..NET FrameworkのLINQとの違い</strong>
</p>
<p>
&nbsp;&nbsp;.NET FrameworkのLINQの実装は、LINQプロバイダと呼ばれるプロバイダによって機能が提供されるようになっています。メモリ上のオブジェクト用LINQプロバイダ、データベース向けLINQプロバイダ、XML向けLINQプロバイダといった調子です。Collection Contextもこの概念に則っています。
</p>
<p>
&nbsp;&nbsp;違いは、Javaにクエリ式を記述できる言語拡張が無いという点と、ラムダ式が無いという点です。LINQは専用の文法を使用しても、メソッドをコールしても利用できます。Collection Contextは、Java向けの専用の文法が無いため、メソッドをコールして使用します。
</p>
<p>
<strong>3.Collection Contextのライブラリセットとプラットフォーム</strong>
</p>
<p>
&nbsp;&nbsp;Collection Contextは単一のライブラリではありません。現在、以下の2種類にて分かれています。<br />
(1)Collection Contextのフレームワーク部分<br />
&nbsp;&nbsp;Collection Contextの名を表す根幹です。コレクション操作の文脈（処理の流れ）をAPIで提供します。<br />
また、XML文字列を簡単に操作するための、DOMに類似した（ただし、別のアプローチからの）モダンなXML関連APIを提供します。このAPIは各プラットフォームで用意されている標準のパーサを使用するように作られており、パーサ自体は含んでいません。
</p>
<p>
(2)プラットフォームのAPIによるリソースマネジメントの実装<br />
&nbsp;&nbsp;実際に、プラットフォームで扱えるリソースをマネジメントするためのAPIが準備されています。2010年10月現在、Androidプラットフォーム向けの実装が準備されており、このプラットフォーム上では以下のリソースがCollection Context上で扱えるようになります。
</p>
<ol>
<li>SQLiteデータベース</li>
<li>JSONオブジェクト</li>
<li>XML（org.xmlpull.v1パッケージに含まれるプル型パーサと、SAXパーサの両方）</li>
<li>メモリ上のオブジェクト</li>
</ol>
これにより、現在Collection Contextが稼働可能なプラットフォームはAndroid 1.6以降となります。サーバサイドJava開発に必要なライブラリの実装は今後の課題としています。
<p>
&nbsp;
</p>
<p>
※ダウンロードしてご利用いただけるjarファイルは、このAndroid 1.6以降でご利用頂けるもので、上記(1), (2)のすべてをワンパッケージにしています。
</p>
<p>
<strong>4.CollectionContext&lt;T&gt;クラスの基礎</strong>
</p>
<p>
&nbsp;&nbsp;まずはじめに、Collection Contextを用いたXML処理の例を以下に示します。これは、AndroidプラットフォームにおいてXMLデータをJavaのオブジェクトに変換しています。
</p>
<pre>
String uri = <span style="color:#009900;">&quot;http://www.example.com/example-resource&quot;</span>;
Iterable&lt;Person&gt; persons = XElement
    .from(uri, <span style="color:#000099;">new</span> XmlSaxParserWrapper(), <span style="color:#009900;">&quot;UTF-8&quot;</span>)
    .descendants(<span style="color:#009900;">&quot;person&quot;</span>)
    .where(<span style="color:#000099;">new</span> F1&lt;XElement, Boolean&gt;() { <span style="color:#000099;">public</span> Boolean f(XElement node) {
        <span style="color:#000099;">return</span> node.attribute(<span style="color:#009900;">&quot;person-id&quot;</span>).intValue() &gt; <span style="color:#009900;">10</span>;
     }})
    .select(<span style="color:#000099;">new</span> F1&lt;XElement, Person&gt;() { <span style="color:#000099;">public</span> Person f(XElement node) {
        Person person = <span style="color:#000099;">new</span> Person();
        person.setID(node.attribute(<span style="color:#009900;">&quot;id&quot;</span>).longValue());
        person.setName(node.element(<span style="color:#009900;">&quot;name&quot;</span>).stringValue());
        person.setAddress(node.element(<span style="color:#009900;">&quot;address&quot;</span>).stringValue());
        person.setBirthday(node.element(<span style="color:#009900;">&quot;birthday&quot;</span>).dateTimeValue(<span style="color:#009900;">&quot;yyyy-MM-dd&quot;</span>));
        <span style="color:#000099;">return</span> person;
    }});
</pre>
<p>
&nbsp;&nbsp;XElementクラスのstaticメソッドfrom()に、XMLのURIと、XMLパーサのインスタンス、XMLの文字コードを指定し、XMLをパースする方法を指定するところから記述が始まっています。次のdesendants()メソッドは、ルートタグの次の階層の「person」という名称のタグ情報をコレクション化するメソッドです。 そのコレクションにwhere()メソッドにてフィルタ条件を指定しています。少し見にくいかもしれませんが、関数を表すオブジェクトが匿名クラスとして実装されています。さらに、select()メソッドにて射影操作を指定しています。XElementのオブジェクトであるnodeをPersonクラスのオブジェクトに変換しています。select()メソッドが返すインスタンスはjava.lang.Iterable型を実装しています。従ってpersonsオブジェクトは拡張for文で逐次参照が可能です。
</p>
<p>
&nbsp;&nbsp;このように、Collection Contextフレームワークを用いると、ネットワーク処理とXMLパース処理における各種リソースのマネジメントが自動化されます。これによって、プログラマはデータの変換処理を容易に記述でき、この処理の前後のビジネスロジックの記述に専念できます。
</p>
<p>
&nbsp;&nbsp;上記where()メソッドやselect()メソッドはCollectionContext&lt;T&gt;クラスのメソッドです。CollectionContext&lt;T&gt;クラスは、Collection Contextフレームワークの中核をなすクラスで、配列やIterable&lt;S&gt;を実装するオブジェクトに対するフィルタ処理や射影処理等をメソッドチェーン方式で記述できる機能を提供します。
</p>
<p>
CollectionContext&lt;T&gt;クラスにもstaticなfrom()メソッドが用意されており、配列やIterable&lt;T&gt;を実装するオブジェクトをCollectionContext&lt;T&gt;オブジェクトに変換することができます。
</p>
<p>
CollectionContext&lt;T&gt;クラスで指定できる機能とメソッドは以下の通りです。
</p>
<p>
&nbsp;
</p>
<ol>
<li>from()：既存の配列やIterable&lt;T&gt;で表されるコレクションをCollectionContext&lt;T&gt;オブジェクトに変換する。</li>
<li>where()：フィルタを指定する。指定されたオブジェクトは遅延実行される。</li>
<li>select()：射影を指定する。このメソッドで返されるCollectionContextは、射影後のコレクションを表す。遅延実行される。</li>
<li>comparator()、orderByAsc()、orderByDesc()：ソート条件を指定する。遅延実行される。</li>
<li>foreach()：コレクションに上記フィルタ条件、ソート条件を適用しながら1件毎に、指定された処理ルーチンをコールする。メソッドが呼ばれた時点で即時実行される。</li>
<li>first()：コレクションにフィルタ条件、ソート条件を適用し、最初の1件を取得する。オーバーライドされたメソッドに、射影を行うことができるタイプもある。即時実行される。</li>
<li>appendTo()：既存のList&lt;T&gt;オブジェクトで表されるコレクションの末尾に、このCollectionContext&lt;T&gt;オブジェクトで表されるコレクションをコピーする。フィルタおよびソート条件が適用される。即時実行される。</li>
<li>toList()：内包するコレクションにフィルタおよびソート条件が適用され、List&lt;T&gt;オブジェクトを生成する。即時実行される。</li>
</ol>
<br />
&nbsp;&nbsp;つまり、CollectionContext&lt;T&gt;クラスのみを用いてコレクション操作を行う場合、from()メソッドの取りうるタイプの都合上、メモリ上のコレクションか、独自にIterable&lt;S&gt;を実装したクラスのオブジェクトを指定する必要があります。逆に、このインターフェイスに則ったクラスを作成することで、Collection Contextフレームワークがサポートしていないリソース経由でデータを取得し、コレクション操作を行うことも可能です。
<p>
&nbsp;
</p>
<p>
&nbsp;&nbsp;また、CollectionContext&lt;T&gt;クラスは、Iterable&lt;T&gt;インターフェイスを実装しています。従って、このクラスのオブジェクトは直接、拡張for文で使用できます。
</p>
<p>
<strong>5.CollectionContext&lt;T&gt;オブジェクトを生成できる各クラス</strong>
</p>
<p>
&nbsp;&nbsp;CollectionContext&lt;T&gt;オブジェクトを生成できるのは、自身のfrom()メソッドだけではありません。上記(1)の末尾でも述べられているように、Iterable&lt;S&gt;を実装したクラスのオブジェクトを生成できるクラスも以下のように提供されています。ただし、2010年10月現在、これらが稼働するのはAndroidプラットフォームの、バージョン1.6以上のみです。
</p>
<p>
(1)XML中のデータをコレクション化するXElementクラス<br />
サンプルコードは既出のとおりです。
</p>
<p>
(2)SQLiteデータベースの参照処理を生成するSQLiteContextBuilderクラス
</p>
<p>
&nbsp;&nbsp;Androidプラットフォームには、組込データベースとして、SQLiteが採用されており、プラットフォームの標準APIとしても実装されています。ただし、このAPIはJDBCではありません。独自に実装されたAPIとなっています。
</p>
<p>
Collection Contextフレームワークでは、SQLite関連クラスとして、DatabaseCursorIterableクラスと、SQLiteContextBuilderクラスが標準で提供されています。DatabaseCursorIterableはIterableを実装しているため、CollectionContext&lt;T&gt;クラスのfrom()メソッドで使用できます。しかし、通常はSQLiteContextBuilderを使用し、メソッドチェーンの記法を用いて「簡単に」データベースの参照コードを記述します。これにより、コードがより宣言型に近づき、バグも潜みにくくなります。
</p>
<pre>
CollectionContext&lt;Shop&gt; shops = SQLiteContextBuilder
        .newBuilder(<span style="color:#000099;">new</span> SQLiteOpenHelperImpl())
        .query(   <span style="color:#009900;">&quot;SELECT &quot;</span> +
            <span style="color:#009900;">&quot;  ID, &quot;</span> +
            <span style="color:#009900;">&quot;  SHOP_ID, &quot;</span> +
            <span style="color:#009900;">&quot;  NAME, &quot;</span> +
            <span style="color:#009900;">&quot;  LATITUDE, &quot;</span> +
            <span style="color:#009900;">&quot;  LONGITUDE, &quot;</span> +
            <span style="color:#009900;">&quot;  URL &quot;</span> +
            <span style="color:#009900;">&quot;FROM &quot;</span> +
            <span style="color:#009900;">&quot;  SHOP &quot;</span> +
            <span style="color:#009900;">&quot;WHERE &quot;</span> +
            <span style="color:#009900;">&quot;  ? &lt;= REGISTERED AND &quot;</span> +
            <span style="color:#009900;">&quot;  REGISTERED &lt; ? &quot;</span>
            registeredBegin, registeredEnd)
        .contextToRead()
        .select(<span style="color:#000099;">new</span> F1&lt;Cursor, Shop&gt;() { <span style="color:#000099;">public</span> Shop f(Cursor cursor) {
            Shop result = <span style="color:#000099;">new</span> Shop();
            
            result.id = cursor.getInt(<span style="color:#009900;">0</span>);
            result.shopID = cursor.getInt(<span style="color:#009900;">1</span>);
            result.name   = cursor.getString(<span style="color:#009900;">2</span>);
            result.latitude   = cursor.getDouble(<span style="color:#009900;">3</span>);
            result.longitude    = cursor.getDouble(<span style="color:#009900;">4</span>);
            result.url  = cursor.getString(<span style="color:#009900;">5</span>);
            
            <span style="color:#000099;">return</span> result;
         }});

</pre>
<p>
&nbsp;&nbsp;このコードでは、query()メソッドでSQL文とパラメータを指定しています。contextToRead()メソッドをコールすると、それ以降はCollectionContextクラスのメソッドとなります。select()メソッドを使い、CursorオブジェクトからShopオブジェクトへ射影しています。最終的に得られる値はCollectionContextオブジェクトです。
</p>
<p>
(3)JSON文字列を処理するorg.json.JSONObjectクラスをコレクションとみなすJSONObjectIterableクラス
</p>
<p>
&nbsp;&nbsp;最近は、Web上のAPIのデータ送受信の表現として、JSONを用いることも多くなってきました。Facebookは特に、全般的にJSONでデータをやり取りし、ソーシャルグラフを操作します。AndroidプラットフォームにもJSONを操作するAPIが用意されています。これをCollectionContext&lt;T&gt;オブジェクトに変換している例が以下の例です。
</p>
<pre>
String result = facebook.request(url, parameters, method);
    CollectionContext&lt;FacebookFriend&gt; friends = JSONObjectIterable
        .from(result, <span style="color:#009900;">&quot;data&quot;</span>)
        .select(<span style="color:#000099;">new</span> F1&lt;JSONObject, FacebookFriend&gt;() {
            <span style="color:#000099;">public</span> FacebookFriend f(JSONObject friend) {
            FacebookFriend result = <span style="color:#000099;">new</span> FacebookFriend();
            result.facebookID = friend.optString(<span style="color:#009900;">&quot;id&quot;</span>);
            result.name   = friend.optString(<span style="color:#009900;">&quot;name&quot;</span>);
            <span style="color:#000099;">return</span> result;
        }});
</pre>
<p>
&nbsp;&nbsp;「友達」を表すJSONObject &ldquo;friend&rdquo;を、FacebookFriendクラスのオブジェクトに変換しています。JSONObjectIterableクラスのfrom()メソッドは、JSON文字列をJSONObjectに変換する処理を行い、CollectionContextを生成します。そして、select()メソッドで射影を行い、得られるのはCollectionContextオブジェクトです。
</p>
<p>
(4)内部イテレータの表現を外部イテレータへ変換するOuterIterable&lt;T&gt;クラス
</p>
<p>
&nbsp;&nbsp;OuterIterable&lt;T&gt;クラスは特殊用途を想定しています。通常、CollectionContext&lt;T&gt;オブジェクトを生成するためには、from()メソッドにIterable&lt;T&gt;オブジェクトを渡すのですが、場合によってはIterable&lt;T&gt;を実装したクラスを作成しなければならない場合があります。この実装が外部イテレータの表現を強いられるため、扱うリソースによっては実装が容易でないケースもあります。
</p>
<p>
例えば、ストリームから文字列を行単位で入力し、その文字列を1行単位で参照できるIterableを実装すると、以下のようになります。
</p>
<pre>
<span style="color:#000099;">public</span> <span style="color:#000099;">class</span> OuterIterableImpl
        <span style="color:#000099;">implements</span> Iterable&lt;String&gt;, Iterator&lt;String&gt; {
        
        <span style="color:#000099;">private</span> <span style="color:#000099;">boolean</span> initialized = false;
        <span style="color:#000099;">private</span> String filePath = null;
        <span style="color:#000099;">private</span> BufferedReader reader = null;
        <span style="color:#000099;">private</span> String next = null;
        <span style="color:#000099;">public</span> OuterIterableImpl1(String filePath) {
            <span style="color:#000099;">this</span>.filePath = filePath;
        }
        
        @Override       
        <span style="color:#000099;">public</span> Iterator&lt;String&gt; iterator() {
            <span style="color:#000099;">return</span> <span style="color:#000099;">this</span>;
        }
        <span style="color:#000099;">private</span> <span style="color:#000099;">void</span> initialize() <span style="color:#000099;">throws</span> IOException {
            <span style="color:#000099;">this</span>.reader =
                <span style="color:#000099;">new</span> BufferedReader(<span style="color:#000099;">new</span> InputStreamReader(
                    <span style="color:#000099;">new</span> FileInputStream(<span style="color:#000099;">this</span>.filePath)));
        }
        <span style="color:#000099;">private</span> <span style="color:#000099;">void</span> dispose() {
            <span style="color:#000099;">try</span> {
                <span style="color:#000099;">this</span>.reader.close();
            }
            <span style="color:#000099;">catch</span> (IOException ex) {
                <span style="color:#990000;">// something to do</span>
            }
        }
        
        @Override
        <span style="color:#000099;">public</span> <span style="color:#000099;">boolean</span> hasNext() {
            <span style="color:#000099;">if</span> (!<span style="color:#000099;">this</span>.initialized) {
                <span style="color:#000099;">try</span> {
                    initialize();
                }
                <span style="color:#000099;">catch</span> (IOException ex) {
                    <span style="color:#000099;">return</span> false;
                }
            }
            <span style="color:#000099;">try</span> {
                String nextLine = <span style="color:#000099;">this</span>.reader.readLine();
                <span style="color:#000099;">if</span> (nextLine != null) {
                    <span style="color:#000099;">this</span>.next = nextLine;
                    <span style="color:#000099;">return</span> true;
                }
                <span style="color:#000099;">else</span> {
                    dispose();
                    <span style="color:#000099;">return</span> false;
                }
            }
            <span style="color:#000099;">catch</span> (IOException ex) {
                dispose();
                <span style="color:#000099;">return</span> false;
            }
        }
        
        @Override
        <span style="color:#000099;">public</span> String next() {
            <span style="color:#000099;">return</span> <span style="color:#000099;">this</span>.next;
        }
        
        @Override
        <span style="color:#000099;">public</span> <span style="color:#000099;">void</span> remove() {
            <span style="color:#000099;">throw</span> <span style="color:#000099;">new</span> UnsupportedOperationException();
        }
    }

</pre>
<p>
&nbsp;&nbsp;非常に長い上、ストリームというリソースを扱うライフサイクルがメソッド間で分離され、メンテナンスが容易でなくなります。一方、context.OuterIterable&lt;T&gt;を使用してIterableを実装すると以下のようになります。
</p>
<pre>
 <span style="color:#000099;">public</span> <span style="color:#000099;">class</span> OuterIterableImpl2 <span style="color:#000099;">extends</span> OuterIterable&lt;String&gt; {
        <span style="color:#000099;">private</span> String filePath = null;
        <span style="color:#000099;">public</span> OuterIterableImpl2(String filePath) {
            <span style="color:#000099;">this</span>.filePath = filePath;
        }
        
        @Override       
        <span style="color:#000099;">protected</span> <span style="color:#000099;">void</span> iterate() {
            BufferedReader reader = null;
            <span style="color:#000099;">try</span> {
                reader = <span style="color:#000099;">new</span> BufferedReader(
                    <span style="color:#000099;">new</span> InputStreamReader(
                    <span style="color:#000099;">new</span> FileInputStream(<span style="color:#000099;">this</span>.filePath)));
            }
            <span style="color:#000099;">catch</span> (IOException ex) {
                <span style="color:#000099;">return</span>;
            }
            <span style="color:#000099;">try</span> {
                String nextLine = reader.readLine();
                <span style="color:#000099;">while</span> (nextLine != null) {
                    yield(nextLine);
                }
            }
            <span style="color:#000099;">catch</span> (IOException ex) {
                <span style="color:#990000;">// something to do</span>
            }
            <span style="color:#000099;">finally</span> {
                <span style="color:#000099;">try</span> { reader.close(); } <span style="color:#000099;">catch</span> (IOException ex) { }
            }
        }
    }

        
</pre>
<p>
特徴はyield()メソッドにあります。このメソッドへ文字列を指定することで、このIterableオブジェクトは指定された順番で文字列を取り出すことができます。このOuterIterable&lt;T&gt;オブジェクトをCollectionContext&lt;T&gt;クラスのfrom()メソッドに指定することで、ストリームから得た文字列のコレクションにフィルタをかけたり射影したりできるようになります。
</p>
<p>
<strong>6.Collection Contextフレームワークのご利用にあたって</strong>
</p>
<p>
このCollection Contextフレームワークは、当社実験サイト「YUMEMI Labs」からダウンロードしてご利用頂けます。ただし、自己責任でご利用ください。当社はいかなる損害も補償できませんのであしからずご了承ください。
</p>
<p>
初期リリースはバイナリのみの配布とさせていただきます。オープンソース化は今後検討いたします。
</p>
<p>
開発用ドキュメントは、この「ゆめ技」の記事と、javadocです。ご意見・ご要望・ご質問のすべてにお答えできるかどうかは分かりませんが、皆さんのご意見・ご要望がこのフレームワークの成長を加速させる原動力であることは確かです。また、頂いたご質問を元に、ゆめ技の記事を書いていこうと考えています。どうぞよろしくお願いします。
</p>
■アプリケーション開発と実行に必要なライブラリファイルのダウンロード<a href="http://yumewaza.yumemi.co.jp/img/postfile/CollectionContextAndroid1.0.0.jar">CollectionContextAndroid1.0.0.jar</a>
<br />
■アプリケーションの開発時に参照するドキュメントファイルをダウンロード<a href="http://yumewaza.yumemi.co.jp/img/postfile/CollectionContextAndroid1.0.0_javadoc.zip">CollectionContextAndroid1.0.0_javadoc.zip</a>
</a>]]>
      
   </content>
</entry>

</feed>

