2011年2月27日日曜日

Channel API ドキュメント日本語要約 (Google App Engine)

 

概要

Channel APIはアプリケーションとgoogleサーバーの間に持続的な接続を作り、JavaScriptクライアントにポーリングを使わずにリアルタイムにメッセージを送ることができる。

Channel APIの要素

JavaScriptクライアント

クライアントの役割

  • チャネルに接続してサーバからチャネルのユニークなトークンを受け取る。
  • ほかのクライアントのアップデートを待ち受けて、データを使用する。
  • アップデートをサーバーに送信する。
サーバ

サーバの役割

  • 個々のJavaScriptクライアントにユニークなチャネルを作る。
  • ユニークトークンを作りJavaScriptクライアントに送る。
  • クライアントからのアップデートをPOSTで受ける。
  • チャネルを使ってクライアントにアップデートメッセージを送る。
クライアントID

クライアントIDはサーバでそれぞれのJavaScriptクライアントを識別するためのもの。クライアントIDはアプリケーションでどのように設計しても良い。

トークン

トークンはJavaScriptクライアントがチャネルに接続して待ち受けることを可能にする役割がある。

サーバはそれぞれのクライアントのためにクライアントIDと有効期限などの情報を使って トークンを作成する。

チャネル

チャネルはサーバーがクライアントIDで識別されるJavaScriptクライアントにアップデートを送るための片方向の通信路。

サーバはクライアントからPOSTによってアップデートを受信すると、該当するクライアントにチャネルを使ってメッセージを送る。

メッセージ

メッセージはクライアントからサーバにPOSTによって送られる。

受信すると、サーバはクライアントIDで識別されたクライアントにチャネルを使ってメッセージを渡す。

メッセージは32KBまで。

URLを送るときに生のメッセージで送るのは避ける。その代わりにメッセージが損なわれずに到達するようにJSONエンコーディングを使う。

ソケット

JavaScriptクライアントはサーバから提供されたトークンを使ってソケット開く。チャネルで更新を待ち受けるためにソケットを使う。

Channel APIを使うためのコード

チャネルを作る(サーバ)

channelServiceを取得して、チャネルを作成。トークンを生成する。

ChannelService channelService =
  ChannelServiceFactory.getChannelService();
String token = channelService.createChannel(clientId);

チャネルに接続する(クライアント)

サーバからもらったトークンを使ってチャネルを開く。

channel = new goog.appengine.Channel(token);
socket = channel.open();
socket.onopen = onOpened;
socket.onmessage = onMessage;
socket.onerror = onError;
socket.onclose = onClose;

サーバーからクライアントへのメッセージ送信(サーバ)

channelService.sendMessage(
  new ChannelMessage(clientId, messageString));

トークンとセキュリティ

createChannel()によって返されるトークンは他に知られないように秘密にしておく。もしトークンが盗まれたらチャネルに送られたメッセージを待ち受けできてしまう。

トークンは2時間で期限切れになる。クライアントが2時間以上チャネルに接続したままでいると、ソケットのonerror(), onclose()がコールバックされる。

注意

クライアントID毎に1つのクライアント

1つのクライアントIDで一度にチャネルに接続できるのは1つのクライアントだけ。アプリケーションは1つのクライアントIDでメッセージを多数のクライアントにばら撒くことはできない。

ページ毎に1つのチャネル、チャネル毎に1つのクライアント

クライアントは1ページにつき1つのチャネルにだけ接続できる。

もしアプリケーションがクライアントに複数の種類のデータを送る必要があるならサーバサイドでデータを集めてクライアントのソケットのonmessageハンドラに送る。

クライアントの接続と切断の追跡

チャネルに接続したクライアントには"プレゼンス"の概念がない。これはクライアントのチャネルへの接続または切断についてアプリケーションに通知されないことを意味する。

もし、アプリケーションがクライアントの接続を追跡する必要があるなら、1つの方法としてクライアントの状態を通知するために適当な間隔でPOSTメッセージをサーバに送るよう設計された関数をソケットのonopenプロパティにセットする。

2011年2月10日木曜日

Slim3でDatastoreを使う - データ操作編

 

この記事はこちらへ移動しました。
Slim3でDatastoreを使う

 

キーの作成

アプリ指定の値で作成
Datastore.createKey(…);

値を自動割り当て
Datastore.allocateId(…);

親エンティティを指定して作成
Datastore.createKey(parentKey, Child.class, value);
Datastore.allocateId(parentKey, Clind.class);

データオブジェクトの新規追加

org.slim3.datastore.DataStore.put(object);

非同期での保存も可能。
DataStore.putAsync(object);

複数のオブジェクトの保存はコレクションか配列をputに渡すか、putに複数のパラメータを並べる

オブジェクトのロード

Datastore.get(Class, Key);

複数のオブジェクトの取り出しもできる。
DataStore.get(Class, コレクション or 配列);
Datastore.get(Class, key1, key2, …);

オブジェクトの更新

オブジェクトを更新するときは、ロード(get)、オブジェクトの値の変更、保存(put)の順で行う。

オブジェクトの削除

Datastore.delete(Key);

ロードや更新と同様、複数オブジェクトを同時に削除できる。
子孫エンティティごと削除するには

Datastore.deleteAll(Key);

クエリ

メタデータ

データクラスをEclipseで作成すると、自動的にメタデータが作成される。クエリの作成の際にこのメタデータを使う。

DataClassのメタデータのインスタンスは次のように取得できる。

DataClassMeta meta = DataClassMeta.get();

フィルタ

EntityQuery query = Datastore.query(meta);
List results = query.filter( meta.property1.equal(value1), meta.progerty2.greaterThan(value2), … ).asList();

インメモリでフィルタ。
endsWith や contains が使える。
query.filterInMemory(meta.property.endsWith("XYZ"))

ソート

query.sort(meta.property1.asc, meta.property2.desc)

インメモリでソート
query.sortInMemory(meta.property.asc)

offset, limit

query.offset(5).limit(10)

クエリの実行と結果

query.asList()
query.asIterator()
query.asSingle()
query.asKeyList()
query.asKeyIterator()

祖先クエリ

[TODO] 後で調べる
List<Child> list = Datastore.query(Child.class, ancestorKey).asList();

kind なしの祖先クエリ

[TODO] 後で調べる
List<Entity> list = Datastore.query(ancestorKey).asList();

クエリの制限

不等式フィルタはクエリ中で1つのプロパティだけにしか使用できない。
不等式で使われたプロパティは最初のソート項目に無ければならない(ソートを指定しなければならない)。

インデックス

フィルタで指定したプロパティを持っていないエンティティは、クエリに引っかからない。
インデックスされないプロパティはクエリに引っかからない。
Text、Blob、@Attribute(unindexed = true) アノテーションをつけたプロパティはインデックスされない。
同じプロパティ名で別の型の値を持つエンティティがあると、型でソートされたあと値でソートされる。
対応するインデックス定義が無いクエリは失敗する。

インデックスの定義

単純なクエリに対するインデックスは自動作成される。

  • フィルタとソート順を使用しないクエリ
  • 等式フィルタと祖先フィルタのみを使用するクエリ
  • 単一のプロパティに対する不等式フィルタのみを使用するクエリ
  • フィルタなしで、プロパティに昇順か降順のどちらかのソート順が設定されているクエリ
  • 等式フィルタをプロパティに使用して、不等式または範囲フィルタをキーに使用しているクエリ

インデックスはxmlで定義。
クエリに必要なインデックス定義は開発環境でクエリを実行したときに自動的に作成される。

手作業で定義するときは WEB-INF/datastore-indexes.xml に書く。

datastore-indexes.xml の autoGenerate が true になっている場合、WEB-INF/appengine-generated/datastore-indexes-auto.xml にインデックスが自動生成される。

クエリカーソル

カーソルとは、フェッチ処理後の次の位置を表す文字列。
S3QueryResultList を通じてカーソルを使用する。

S3QueryResultList results = query.limit(20).asQueryResultList();
results.getEncodedCursor();
results.getEncodedFilters();
results.getEncodedSorts();
results.hasNext();

query.encodedCursor(encodedCursor)
.encodedFilters(encodedFilters)
.encodedSorts(encodedSorts)
.limit(20).asQueryResultList();

カーソルの制限
  • in や != フィルタのクエリには使えない
  • カーソルは同じクエリにしか使えない。kind、フィルタ、フィルタ値、祖先フィルタ、ソートが同じであること。
  • インデックス設定を変更するとクエリは無効になる

 

トランザクション

Transaction tx = Datastore.beginTransaction();
DataClass data = Datastore.get(tx, Dataclass.class, key);
data.setProperty(value);
Datastore.put(tx, data);
tx.commit();

単一のトランザクションでできることは

  • 1つのグループ内の複数のエンティティを変更すること
  • グループに新しいエンティティを追加すること

1つのトランザクション内では1つのエンティティグループの操作だけができる
(エンティティのロードも含めて)。
楽観的並列処理なのでトランザクションが失敗したらアプリ側で何度かリトライする。
トランザクションの外側のトランザクション分離性はRead committedに近いがトランザクションはSerializableで実行される。
トランザクション内の祖先クエリやgetはトランザクション内での変更があってもトランザクション開始時点のスナップショットを返す。

バージョンによる楽観的ロック

トランザクションを超える更新(Web画面のためのGETリクエストの後、更新のためのPOSTリクエストでDatastoreにputするなど)を行う場合、データのバージョンにより競合を検出できる。

エンティティに @Attribute(version = true) を付けたプロパティを用意することで、書き込みためのgetの際にすでに他の書き込みによってバージョンが変更されていたら ConcurrentModificationException が投げられる。

Datastore.get(Transaction, Class, Key, version)

グローバルトランザクション

複数のエンティティグループにまたがったトランザクションを実行できる。

GlobalTransaction gtx = Datastore.beginGlobalTransaction();
gtx.get(…);
gtx.put(…);
gtx.commit();

同じエンティティグループに対してローカルトランザクションとグローバルトランザクションを混ぜて使ってはいけない。グローバルトランザクションはパフォーマンス的にはローカルトランザクションと大きな違いはないそうなので、グローバルトランザクションを使う可能性があればそのエンティティグループの操作には常にグローバルトランザクションを使うと良い。

Slim3でDatastoreを使う - データ定義編

 

この記事はこちらへ移動しました。
Slim3でDatastoreを使う

 

モデルの定義

データのクラスに@Modelアノテーションを付ける
永続化するプロパティには getter, setter が必要
永続化しないフィールドには@Attribute(persistent = false) アノテーションを付ける

フィールドの型

フィールドで使える型はDatastoreの基本型、基本型のコレクション、シリアライズ可能なオブジェクト
フィールドで使える基本型 → Core Value Types
シリアライズ可能なオブジェクトはBlob型として格納される(@Attribute(lob = true)を付ける)
500文字(byte[]は500バイト)を超えるフィールドには@Attribute(lob = true)を付ける
配列はコレクションとして扱われない(シリアライズしてBlobになる?)
インデックスに対応した型はデフォルトでインデックスが付く
インデックスを付けたくないフィールドには@Attribute(unindexed = true)を付ける

サポートするコレクション型

ArrayList, LinkedList, HashSet, LinkedHashSet, TreeSet, List (ArrayList), Set (HashSet), SortedSet (TreeSet)

()内はプロパティから返される実際のインスタンスの型

自動的なプロパティ値の更新

@Attribute(listener = ???) アノテーションで値を自動更新するリスナを指定できる

保存するたびに更新

@Attribute(listener = ModificationDate.class)
Date updatedAt;

最初の保存のときだけ更新

@Attribute(listener = CreationDate.class)
Date createdAt;

キー

モデルクラスに1つだけキーを持たなければならない
キーのフィールドには @Attribute(primaryKey = true) を付ける
キーは パス、kind、ID によって構成される
kind はクラスの単純名から付けられる
kindはクラスの @Model(kind = "...") アノテーションで変更可能
IDはアプリケーションで指定した文字列か、Datastoreが付けた数値(キーをnullのまま保存)
キーは Datastore.createKey(Class, String) で作成してキープロパティにセットする

エンティティグループ

エンティティの親子関係はキーを作成するときに親エンティティのIDを指定することで決定する
プロパティに他のエンティティの参照を持ってもエンティティグループに入れるわけではない
エンティティの親子関係はあとで変えることはできない
先祖エンティティが削除されても子孫エンティティは削除されない

リレーションシップ

片方向1対1関連

ModelRef型のプロパティを作って次のように初期化とgetterメソッドを作成。

@Model public class Address { … }

@Model public class Employee {
    private ModelRef<Address> addressRef = new ModelRef<Address>(Address.class);
    public ModelRef<Address> getAddressRef() { return addressRef; }
}

インスタンスを作成して関連付けして保存

Address address = new Address();
Employee employee = new Employee();
employee.getAddressRef().setModel(address);
Datastore.put(address, employee);

ロードするときは、

Employee employee = Datastore.get(Employee.class, employeeKey);
Address address = employee.getAddressRef().getModel();

参照先はレイジーロードされる。

双方向1対1関連

先ほどの関連に逆方向の参照を追加。逆方向はInverseModelRef型にする。逆方向の参照は永続化しない。Addressクラスに以下のコードを追加。

@Attribute(persistent = false)
private InverseModelRef<Employee, Address> employeeRef = new InverseModelRef<Employee, Address>(Employee.class, "addressRef", this);

public InverseModelRef<Employee, Address> getEmployeeRef() { return employeeRef;}

これでAddressからEmployeeへの参照ができる。

address.getEmployeeRef.getModel();

プロパティに関連をセットするには正方向側(Employee側)のプロパティにセットする。逆方向にはsetModel()メソッドが無い。

片方向多対1関連

多 → 1 の参照を持つ関連の場合、片方向1対1の場合と同じ。

双方向多対1関連

多 → 1への参照に加え、1 → 多 への参照を追加。1側から多側への参照は永続化しない。

@Attribute(persistent = false)
private InverseModelListRef<Employee, Department> employeeListRef = new InverseModelListRef<Employee, Department>(Employee.class, "departmentRef", this);

public InverseModelListRef<Employee, Department> getEmployeeListRef() {…}

1側から多側を取得する場合は次のようにする。

List<Employee> employeeList = department.getEmployeeListRef().getModelList();

双方向多対多関連

関連クラスを作って、1 ← 多(関連クラス) → 1 のようにする。

関連クラスに1側への参照プロパティをそれぞれ作る。

@Model
public class EmployeeProject {
    …
    private ModelRef<Employee> employeeRef = new ModelRef<Employee>(Employee.class);
    private ModelRef<Project> projectRef = new ModelRef<Project>(Project.class);

    public ModelRef<Employee> getEmployeeRef() {…}
    public ModelRef<Project> getProjectRef() {…}
    …
}

1側は"双方向多対1"の逆参照と同様。EmployeeProjectに対して永続化しない InverseModelListRef<EmployeeProject, 1側クラス> のプロパティとgetterを作る。

ロードするときは片側(たとえばProjectオブジェクト)から関連オブジェクトのリストを取得して、関連オブジェクトから反対側のオブジェクトを得る。

relObject = project.getEmployeeProjectRef().getModelList();
relObject.getEmployeeRef().getModel();

2011年2月5日土曜日

GalaxySの解像度をAPIと実寸で比較 (Android)

Anddroid端末の解像度を取得する方法。
下のコードで画面のいろいろな情報を取得できる。

DisplayMetrics mtr = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(mtr);

GalaxySの画面の実寸サイズは長辺86mm 800px, 短辺52mm 480pxなので、長辺234.46153846153846153846153846154 dpi, 短辺 236.27906976744186046511627906977 dpi。

GalaxyS上でAPIで得られる数値はXdpi 234.46153, Ydpi 236.27907 なので実寸と同じ。こういうのはあまり信用できない数値が返ってくることが多いけど、試してみたGalaxySでは正確な数値が得られるようだ。

参考ページ

IS03、色々な機種のDpi情報が欲しいので、こちらからはGalaxy Sの情報を晒します