tag:blogger.com,1999:blog-34737439084524780232024-03-09T03:14:24.402+09:00ProgrammingLife.jppljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comBlogger38125tag:blogger.com,1999:blog-3473743908452478023.post-69787374758166107082017-03-07T12:57:00.003+09:002017-03-07T13:00:14.184+09:00Kotlinコルーチンの翻訳を始めました<a href="https://github.com/pljp/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md" target="_blank">https://github.com/pljp/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md</a> の日本語訳を開始しました。少しずつ進めていきたいと思います。<br />
<br />
<ul>
<li><a href="https://github.com/pljp/kotlin-coroutines/blob/japanese_translation/kotlin-coroutines-informal.md" target="_blank">Kotlinのコルーチン</a></li>
</ul>
<br />
<br />pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-54988634058503710072013-05-20T16:12:00.000+09:002013-05-20T16:29:07.592+09:00ListViewで無限スクロール (Android)<p>ListViewで最後までスクロールしたら続きのデータを読み込んで、いくらでもスクロールできるようにするコード例です。実際のところ無限ということはあり得ないのですけど、データがどれだけ大量にあっても対応できるということです。</p> <p>大量のデータをListViewで表示したければ普通はContentProviderを作ってLoaderManagerを使ってCursorAdapterにCursorをセットして作ればいいのですが、ContentProviderというものは何かと文字列に持っていこうとするので個人的にあまり好んでいません。引数にSQL断片を渡されても、ここから値を取り出して値域のチェックなんてやってられません。アプリ内部で使うなら引数のチェックなんてしないでSQLにぶち込んでしまえ、という考え方もあるかもしれませんが。</p> <p>そこでContentProviderのかわりにServiceでデータアクセスを提供し、ListViewに常に一定以下のデータだけを持たせて端までスクロールしたら続きのデータをロードする、そしてあふれたデータはリストから削除する、というプログラムを作ってみました。</p> <a name='more'></a> <h4>肝はSortedMapAdapter</h4> <p>データの追加、削除ができて、且つ常にソートされた状態にするデータホルダーが欲しいのでSortedMapをベースにしたSortedMapAdapterを作りました。長いので下のコードにはありません。GitHubにあります。<br>[<a title="https://github.com/pljp/libpljp-android" href="https://github.com/pljp/libpljp-android" target="_blank">https://github.com/pljp/libpljp-android</a>]</p> <h4>大まかな流れ</h4> <p>リストをスクロールするとOnScrollListener_#onScroll()が呼ばれます。ListViewの最後の10行が表示されたら続きのデータをreadMore()でリクエストします。</p> <p>readMore()メソッドではデータアクセスを提供するsvcオブジェクトにデータの検索を依頼します(getLogs())。getLogs()メソッドはワーカースレッドでDBを検索して、結果を引数で渡したGetLogsListenerにメインスレッドで返してきます。</p> <p>結果を受け取ったGetLogsListener#done()メソッドではadapterにデータを追加する前にListViewのスクロール位置をとっておき、データの追加・削除後にさっきまでListViewの先頭に表示していた行を探し出してスクロール位置を復元します。</p> <p class="brush:java">private static final int MAX_LIST_ITEMS = 200;<br>private static final int MAX_READ_ITEMS = 100;<br>private PkgLog head;<br>private PkgLog tail;<br>private Adapter_ adapter;<br>private boolean busy;<br><br>/**<br>* PkgLogを保持するAdapter。<br>* キーの降順にソートされる。<br>*/<br>private final class Adapter_ extends SortedMapAdapter<Integer, PkgLog> {<br><br> private final LayoutInflater inflater;<br><br> Adapter_() {<br><br> super(new Comparator<Integer>() {<br> @Override<br> public int compare(Integer lhs, Integer rhs) {<br> return rhs - lhs;<br> }<br> });<br> inflater = LayoutInflater.from(getActivity());<br><br> }<br><br> @Override<br> protected View getView(int position, Integer key, PkgLog item, View convertView, ViewGroup parent) {<br><br> … 省略 …<br> return convertView;<br><br> }<br>}<br><br>private final class OnScrollListener_ implements OnScrollListener {<br><br> @Override<br> public void onScrollStateChanged(AbsListView view, int scrollState) {<br> }<br><br> @Override<br> public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {<br><br> if ( adapter.getCount() == 0 ) return;<br><br> int firstItemId = adapter.getFirstItem().getId();<br> int lastItemId = adapter.getLastItem().getId();<br><br> if ( !busy ) {<br><br> if ( totalItemCount <= firstVisibleItem + visibleItemCount + 10 && lastItemId != tail.getId() ) {<br> readMore(Direction.OLDER);<br> }<br> else if ( firstVisibleItem <= 10 && firstItemId != head.getId() ) {<br> readMore(Direction.NEWER);<br> }<br><br> }<br> } // onScroll<br>} // OnScrollListener_<br><br><br>private enum Direction { NEWER, OLDER }<br>private void readMore(final Direction dir) {<br><br> if ( svc == null || busy ) return;<br> busy = true;<br><br> // svc.getLogs()の結果を受け取るListener。<br><br> GetLogsListener getLogsListener = new GetLogsListener() {<br><br> // データの読み込みが終わったらこのメソッドで通知される。<br> // head_, tail_はDBにあるデータ全体の先頭と最後。<br> @Override<br> public void done(List<PkgLog> logs, PkgLog head_, PkgLog tail_) {<br><br> // スクロール位置をとっておく<br><br> ListView view = getListView();<br> int pos = view.getFirstVisiblePosition();<br> int firstVisibleId = -1;<br> int yOffset = 0;<br> if ( adapter.getCount() > pos ) {<br><br> firstVisibleId = adapter.getKey(pos);<br> yOffset = view.getChildAt(0).getTop();<br><br> }<br><br> // 読み込んだデータをadapterに取り込む<br><br> for (PkgLog log : logs)<br> adapter.put(log.getId(), log);<br> head = head_;<br> tail = tail_;<br><br> // データが多すぎたら捨てる<br><br> int n = adapter.getCount();<br> if ( n > MAX_LIST_ITEMS ) {<br><br> if ( dir == Direction.OLDER )<br> removeLogs(0, n - MAX_LIST_ITEMS);<br> else<br> removeLogs(MAX_LIST_ITEMS, n - MAX_LIST_ITEMS);<br><br> }<br><br> // データの更新を通知<br><br> adapter.notifyDataSetChanged();<br><br> // スクロール位置を戻す<br><br> if ( firstVisibleId > -1 ) {<br><br> int pos = adapter.positionOf(firstVisibleId);<br> if ( pos > -1 ) {<br> view.setSelectionFromTop(pos, yOffset);<br> }<br><br> }<br><br> busy = false;<br><br> }<br> };<br><br> if ( dir == Direction.OLDER ) {<br><br> int id = adapter.getLastItem().getId();<br><br> // svc.getLogs()はDBからデータを非同期でロードするメソッド。<br> // ロードが終わったらGetLogsListenerに通知される。<br> svc.getLogs(query, id, MAX_READ_ITEMS, getLogsListener);<br><br> }<br> else {<br><br> int id = adapter.getFirstItem().getId();<br> svc.getLogs(query, id, -MAX_READ_ITEMS, getLogsListener);<br><br> }<br>}</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-90995656169666897902011-12-18T19:26:00.000+09:002011-12-31T13:31:44.195+09:00Storyboardでテーブルビューのセルをカスタマイズする (iOS)<p>StoryboardではTableViewのセルのカスタマイズも1画面でできるようになりました。</p> <h4>セルのプロトタイプから動的にセルを生成する</h4> <p>カスタムセルのプロトタイプを作って、プログラムで動的にセルの内容を決定するTableViewを作ってみます。</p> <p>最初に、プロジェクトを作るところから始めます。Master Detail Applicationテンプレートから新規プロジェクトを作ります。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard2/storyboard2-1.png"><img src="http://file.programminglife.jp/image/storyboard2/storyboard2-1-tn.png"></a></p> <a name='more'></a> <p>Storyboardを開くと、すでにテーブルビューとセルがあります。このセルをカスタマイズします。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard2/storyboard2-2.png"><img src="http://file.programminglife.jp/image/storyboard2/storyboard2-2-tn.png"></a></p> <p>TableViewのAttribute inspectorでContent属性をDynamic Prototypesに変更します。ここをStatic Cellsにするとセルのインスタンス1つ1つをInterface Builderで作ることになります。今回はデータを動的に埋め込むのでDynamic Prototypesにします。</p> <p>セルのAttribute inspectorでStyle属性をCustomにします。Identifierは”CustomCell”と名づけました。このカスタムセルにラベルを2つ乗せて、左のラベルのタグを1に、右のラベルのタグを2にします。</p> <p>セルをタップしたらDetail Viewに遷移するようにします。</p> <p>セルから遷移先のビュー(Detail View)にCtrlキーを押しながら名マウスをドラッグしてSegueを作成します。こうするとコードを書かなくてもセルからの画面遷移が作成できます。この場合、どのセルも同じ画面への遷移になります。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard2/storyboard2-3.png"><img src="http://file.programminglife.jp/image/storyboard2/storyboard2-3-tn.png"></a></p> <p>各セルの表示内容を決定するためのコードを書きます。cellForRowAtIndexPath:メソッドでセルの値を設定します。</p> <p class="brush:oc">-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {<br> return 1;<br>}<br><br>-(NSInteger)tableView:(UITableView *)tableView<br> numberOfRowsInSection:(NSInteger)section {<br> return 3;<br>}<br><br>-(UITableViewCell *)tableView:(UITableView *)tableView<br> cellForRowAtIndexPath:(NSIndexPath *)indexPath {<br> <br> UITableViewCell *cell = [tableView<br> dequeueReusableCellWithIdentifier:@"CustomCell"];<br> UILabel *label1 = (UILabel*)[cell viewWithTag:1];<br> UILabel *label2 = (UILabel*)[cell viewWithTag:2];<br> <br> label1.text = [[NSString alloc] initWithFormat:@"Section %d", indexPath.section];<br> label2.text = [[NSString alloc] initWithFormat:@"Row %d", indexPath.row];<br><br> return cell;<br> <br>}</p> <p>以前はUITableViewのdequeueReusableCellWithIdentifier:でセルのインスタンスがなければ新規にallocするコードを書きましたが、Storyboardでセルのプロトタイプを作ると最初からインスタンスがあるようなので、再利用可能なセルがなかった場合のコードは省くことができます。</p> <h4>複数のセルプロトタイプを使い分ける</h4> <p>Object libraryの中からTable View CellをTable Viewにドラッグしてセルのプロトタイプを新しく追加します。</p> <p>セルのIdentifierを”CustomCell2”にします。</p> <p>最初のプロトタイプと同様、ラベルを2つ追加します。少しレイアウトを変えて縦に2つ並べてみます。上のラベルから1, 2とタグを付けます。</p> <p>いま作成したセルのプロトタイプからDetail ViewにSegueを作成します。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard2/storyboard2-4.png"><img src="http://file.programminglife.jp/image/storyboard2/storyboard2-4-tn.png"></a></p> <p>セクションを2つにして、セクションのヘッダを付けます。</p> <p class="brush:oc">-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {<br> return 2;<br>}<br><br>-(NSInteger)tableView:(UITableView *)tableView<br> numberOfRowsInSection:(NSInteger)section {<br> return 3;<br>}<br><br>- (NSString *)tableView:(UITableView *)tableView<br> titleForHeaderInSection:(NSInteger)section {<br> <br> return [[NSString alloc] initWithFormat:@"Section %d", section];<br> <br>}<br> <p>cellForRowAtIndexPath: をセクションごとに別のプロトタイプを使うように変更します。ラベルの数とタグはどちらも同じなので同じコードを利用します。</p> <p class="brush:oc">-(UITableViewCell *)tableView:(UITableView *)tableView<br> cellForRowAtIndexPath:(NSIndexPath *)indexPath {<br> <br> UITableViewCell *cell;<br> <br> if ( indexPath.section == 0 ) {<br> cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];<br> }<br> else {<br> cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell2"];<br> }<br> UILabel *label1 = (UILabel*)[cell viewWithTag:1];<br> UILabel *label2 = (UILabel*)[cell viewWithTag:2];<br> <br> label1.text = [[NSString alloc] initWithFormat:@"Section %d", indexPath.section];<br> label2.text = [[NSString alloc] initWithFormat:@"Row %d", indexPath.row];<br><br> return cell;<br> <br>}</p> <p>実行すると、セクションごとに別のレイアウトのセルが表示されます。セルをタップするとどのセルも同じ画面に遷移します。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard2/storyboard2-5.png"><img src="http://file.programminglife.jp/image/storyboard2/storyboard2-5-tn.png"></a></p> <h4>セルごとに別の画面に遷移する</h4> <p>上記の方法ではどのセルも同じ画面に遷移していました。しかし、セルごとに別々の画面に遷移したいこともあると思います。セルからの画面遷移を動的に決定するプログラムを作成してみます。</p> <p>先ほど作ったセルのSegueをすべて削除します。代わりにView Controllerから遷移先の画面にSegueを1つ作成します。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard2/storyboard2-6.png"><img src="http://file.programminglife.jp/image/storyboard2/storyboard2-6-tn.png"></a></p> <p>TableViewのdelegete(大抵の場合、この画面のView Controller)にdidSelectRowAtIndexPath:メソッドを実装します。ここでperformSegueWithIdentifierを呼び出して画面遷移をします。下記の例では常に同じSegueを実行していますが、indexPathの値に応じて引数のIdentifierを決定すればセルごとに任意の画面に遷移できます。</p> <p class="brush:oc">-(void)tableView:(UITableView *)tableView<br> didSelectRowAtIndexPath:(NSIndexPath *)indexPath {<br> <br> [self performSegueWithIdentifier:@"detail" sender:self];<br> <br>}</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-27109614204543006052011-12-13T16:15:00.001+09:002011-12-31T13:32:53.987+09:00Storyboardで画面遷移を作る (iOS)<p>Xcode4.2からStoryboadというものが使えるようになりました。いままで画面ごとにnibファイルを作ってプログラムで画面遷移することができましたが、Storyboardを使えば各画面の関係を1画面で見渡すことができます。また、単純な画面遷移ならプログラムを一切書かずに作ることができます。</p> <h4>最も簡単な画面遷移</h4> <p>ボタンを押したら次の画面に移動するだけの簡単な画面遷移を作ってみます。</p> <p>まずはプロジェクトの作成から。</p> <p>File –> New –> New Project でSingle View Applicationを選択します。<br>新規プロジェクトのプロジェクト名などを入力する画面で「Use Storyboard」のチェックを入れて、プロジェクトを作成します。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard1/storyboard1-1.png"><img src="http://file.programminglife.jp/image/storyboard1/storyboard1-1-tn.png"></a></p> <a name='more'></a> <p>次に、Navigation Controllerを追加します。</p> <p>Navigation Controllerをドラッグしてキャンバスに置きます。NavigationControllerとルートビューが画面に出てきます。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard1/storyboard1-2.png"><img src="http://file.programminglife.jp/image/storyboard1/storyboard1-2-tn.png"></a></p> <p>Attribute inspectorでNavigationControllerのIs Initial View Controllerをチェックします。</p> <p>画面遷移を作ります。</p> <p>ルートビューにボタンを配置します。Ctrlキーを押しながらボタンをドラッグして最初からあるView Controllerに線を引きます。Storyboard Seguesのポップアップが出ます。ここはNavigationControllerによる画面遷移なのでpushを選びます。</p> <p>お好みでナビゲーションバーにタイトルを付けたり、2番目の画面にラベルを付けたりします。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard1/storyboard1-3.png"><img src="http://file.programminglife.jp/image/storyboard1/storyboard1-3-tn.png"></a></p> <p>Runボタンで実行します。最初の画面のボタンを押すと次の画面へ遷移します。2番目の画面のナビゲーションバーには戻るボタンが表示され、最初の画面に戻ることができます。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard1/storyboard1-4.png"><img src="http://file.programminglife.jp/image/storyboard1/storyboard1-4-tn.png"></a></p> <h4>プログラムによる画面遷移</h4> <p>ボタンのアクションとしての画面遷移はマウスで線を引くだけで設定できましたが、イベントなどプログラムの任意のタイミングで画面遷移したいこともあります。このようなときはプログラムで画面遷移を呼び出すことができます。</p> <p>先ほどのアプリに新しい画面を追加して、スワイプしたら画面遷移するようにしてみます。</p> <p>新しい画面を追加します。StoryboardのキャンバスにView Controllerをドラッグします。</p> <p>初期画面のViewから新しく追加したViewにCtrlキーを押しながらドラッグしてSegueを作成します。Styleはもちろんpushです。このSegueのIdentifierを設定します(ここでは”konnichiwa”としました)。</p> <p align="center"><a href="http://file.programminglife.jp/image/storyboard1/storyboard1-5.png"><img src="http://file.programminglife.jp/image/storyboard1/storyboard1-5-tn.png"></a></p> <p>初期画面のIdentity inspectorでViewControllerのサブクラスを割り当てます。新規に作ってもいいのですが、ここではプロジェクト作成時に自動的に作られたViewControllerをそのまま使いました(HWViewController)。</p> <p>HWViewControllerにジェスチャを認識するためにUISwipeGestureRecognizerを追加します。</p> <p class="brush:oc">- (void)viewWillAppear:(BOOL)animated<br>{<br> [super viewWillAppear:animated];<br> <br> swipe = [[UISwipeGestureRecognizer alloc] <br> initWithTarget:self action:@selector(handleSwipeGesture:)];<br> swipe.direction = UISwipeGestureRecognizerDirectionLeft;<br> swipe.numberOfTouchesRequired = 1;<br> [self.view addGestureRecognizer:swipe];<br><br>}<br><br>- (void)viewWillDisappear:(BOOL)animated<br>{<br> <br> [self.view removeGestureRecognizer:swipe];<br> swipe = nil;<br> <br> [super viewWillDisappear:animated];<br> <br>}<br></p> <p>スワイプされたときのハンドラを作成します。</p> <p>self(つまりViewController)のperformSegueWithIdentifier:sender:で先ほど”konnichiwa”というIdentifierを付けたSegueを実行します。</p> <p class="brush:oc">- (void)handleSwipeGesture: (UISwipeGestureRecognizer*)sender {<br> [self performSegueWithIdentifier:@"konnichiwa" sender:self]; <br>}</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-46552489101714925722011-12-01T00:00:00.000+09:002011-12-18T19:27:36.178+09:00iOS 目次<ul> <li><a href="http://www.programminglife.jp/2011/12/storyboard-ios_18.html">Storyboardでテーブルビューのセルをカスタマイズする</a> (2011/12/18) <li><a href="http://www.programminglife.jp/2011/12/storyboard-ios.html">Storyboardで画面遷移を作る</a> (2011/12/15)</li></ul> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-1616962209403465622011-09-26T16:32:00.001+09:002011-09-26T16:58:08.403+09:00Wi-Fiでゲートウェイにpingする (Android)<p>AndroidのWi-Fiを使ってpingする方法です。</p> <p>まずはゲートウェイのアドレスを調べます。DhcpInfoのgatewayフィールドに入っています。</p> <p class="brush:java">WiFiManager wifi = (WifiManager)getSystemService(WIFI_SERVICE);<br>DhcpInfo dhcpInfo = wifi.getDhcpInfp();<br>int gateway = dhcpInfo.gateway;</p> <p>ここにはリトルエンディアンで入っています。これをビッグエンディアンの配列にします。</p> <p class="brush:java">byte[] gatewayAddr = new byte[] {<br> (byte)(gateway&255),<br> (byte)((gateway>>8)&255),<br> (byte)((gateway>>16)&255),<br> (byte)((gateway>>24)&255)<br>};<br></p> <p>あとは普通のJavaプログラムと同様にpingします。</p> <p class="brush:java">InetAddress addr = InetAddress.getByAddress(gatewayAddr);<br>if ( addr.isReachable(3000) ) {<br> …<br>}</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-10753307059980198682011-07-07T11:33:00.001+09:002011-07-07T11:49:49.049+09:00GalaxySを2.3.3にアップデートしたらバッテリー消費が激しくなった<p>GalaxyS Android2.3.3のバッテリー問題。突然バッテリーの消費が激しくなり、8時間くらいでバッテリーを完全消費してしまう。</p> <p>この問題、どうやら日本だけで起こっているわけではないらしい。<br><a title="http://www.google.mk/support/forum/p/Google+Mobile/thread?tid=1ea61a8c2fffdb2e&hl=en" href="http://www.google.mk/support/forum/p/Google+Mobile/thread?tid=1ea61a8c2fffdb2e&hl=en">http://www.google.mk/support/forum/p/Google+Mobile/thread?tid=1ea61a8c2fffdb2e&hl=en</a></p> <p>システムモニタのアプリで調べてみたら、原因はシステムプロセスのsuspend。アプリではない。</p> <p>下のスクリーンショットを見ると、端末がスリープに入るとsuspendプロセスがCPUを使い始めていることがわかる。</p> <p><a href="http://lh3.ggpht.com/-l7EensRDMnM/ThUay0uWPjI/AAAAAAAAABE/bgPgtL690aU/s1600-h/device%25255B2%25255D.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="バッテリー履歴1日" border="0" alt="バッテリー履歴1日" src="http://lh3.ggpht.com/-yVv5zucsTn0/ThUazpYHBtI/AAAAAAAAABI/3DbpGnd6G_Q/device_thumb.png?imgmax=800" width="148" height="244"></a><a href="http://lh5.ggpht.com/-16ALovxl534/ThUa0YVKdWI/AAAAAAAAABM/k9tAPubNuH0/s1600-h/device2%25255B2%25255D.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="CPU消費量" border="0" alt="CPU消費量" src="http://lh4.ggpht.com/-9I-3E50v0Dc/ThUa0xL3wRI/AAAAAAAAABQ/F2egIV49l-c/device2_thumb.png?imgmax=800" width="148" height="244"></a><a href="http://lh6.ggpht.com/-CManD8A_LHg/ThUa1xwRqRI/AAAAAAAAABU/zumtVeNHbfU/s1600-h/device3%25255B2%25255D.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="システムプロセスのCPU使用量" border="0" alt="システムプロセスのCPU使用量" src="http://lh3.ggpht.com/-iAec1Q-nI0k/ThUa2QPUWMI/AAAAAAAAABY/Aj1xfbPG6ks/device3_thumb.png?imgmax=800" width="148" height="244"></a><a href="http://lh5.ggpht.com/-PuvzRStUvkk/ThUa3P-nYYI/AAAAAAAAABc/lDC8E16h-Gs/s1600-h/device4%25255B2%25255D.png"><img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="suspendプロセスのグラフ" border="0" alt="suspendプロセスのグラフ" src="http://lh5.ggpht.com/-KCl-ZGwNuPY/ThUa3uTF7_I/AAAAAAAAABg/PLPPAU0XXP4/device4_thumb.png?imgmax=800" width="148" height="244"></a></p> <p>いまのところ、バッテリー消費が激しくなったら再起動するしかないようだ。</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-28959908451272562152011-07-03T18:57:00.001+09:002011-07-03T19:10:52.700+09:00Flash Playerのバージョンテスト<p>下のFlashでFlash Playerのバージョン、デバッグ版か否か、プレイヤーのタイプを調べることができる。</p> <div id="flashversionPlace"></div><script type="text/javascript" src="http://file.programminglife.jp/flashversion/swfobject.js"></script><script type="text/javascript" src="http://file.programminglife.jp/flashversion/pljp_flashversion.js"></script> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-63008241110658530792011-06-06T13:32:00.000+09:002011-06-06T13:33:26.242+09:00ASPIRE 1830Z-F52Cの消費電力<p>ワットチェッカーで測ってみた。ワットチェッカーは1W単位でしか測れないので測定値はかなり大雑把な値。仕事に使っていても大体4~5時間くらいは使えるので夏の電力不足の時期には重宝しそう。</p> <h4>アイドル時</h4> <p>ディスプレイ輝度最大 10W<br>ディスプレイ輝度最小 7W<br>ディスプレイ輝度中間(10段階のうち下から5番目) 8W</p> <h4>バイオハザード5ベンチ</h4> <p>最大26W (ディスプレイ輝度中間)</p> <h4>スリープ時</h4> <p>0W (小さすぎて測れませんでした。1W未満ということ。)</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-39211712505417827772011-06-01T12:15:00.001+09:002013-10-12T13:42:33.377+09:00Slim3でDatastoreを使う<div id="toc"></div> <p> </p> <h4 id="teigi">モデルの定義</h4> <p>データのクラスに@Modelアノテーションを付ける。<br>永続化するプロパティには getter, setter が必要。<br>永続化しないフィールドには@Attribute(persistent = false) アノテーションを付ける。</p> <h4 id="fieldtype">フィールドの型</h4> <p>フィールドで使える型はDatastoreの基本型、基本型のコレクション、シリアライズ可能なオブジェクト。<br>フィールドで使える基本型 → <a href="http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/core-value-types" target="_blank">Core Value Types</a><br>シリアライズ可能なオブジェクトはBlob型として格納される(@Attribute(lob = true)を付ける)。<br>500文字(byte[]は500バイト)を超えるフィールドには@Attribute(lob = true)を付ける。<br>配列はコレクションとして扱われない(シリアライズしてBlobになる?)。<br>インデックスに対応した型はデフォルトでインデックスが付く。<br>インデックスを付けたくないフィールドには@Attribute(unindexed = true)を付ける。</p> <h4 id="collectiontype">サポートするコレクション型</h4> <p>ArrayList, LinkedList, HashSet, LinkedHashSet, TreeSet, List (ArrayList), Set (HashSet), SortedSet (TreeSet)</p> <p>()内はプロパティから返される実際のインスタンスの型。</p> <h4 id="jidou">自動的なプロパティ値の更新</h4> <p>@Attribute(listener = ???) アノテーションで値を自動更新するリスナを指定できる。</p> <p>保存するたびに更新するときは、</p> <p class="brush:java">@Attribute(listener = ModificationDate.class)<br>Date updatedAt;</p> <p>最初の保存のときだけ更新するときは、</p> <p class="brush:java">@Attribute(listener = CreationDate.class)<br>Date createdAt;</p> <h4 id="key">キー</h4> <p>モデルクラスに1つだけ主キープロパティを持たなければならない。<br>キーのフィールドには @Attribute(primaryKey = true) を付ける。<br>キーは パス、kind、ID によって構成される。ネームスペースを使っているときはネームスペース名もキーに保存される。詳しくは「<a href="http://www.programminglife.jp/2011/05/multitenancynamespaces-api-google-appp.html">MultitenancyとNamespaces API</a>」を参照。</p> <p>kind はクラスの単純名から付けられる。kindはクラスの @Model(kind = "...") アノテーションで変更可能。</p> <p>IDはアプリケーションで指定した文字列か、Datastoreが付けた数値(キーをnullのまま保存)。<br>自分で作ったIDからキーを作るとはき Datastore.createKey(…)。IDの自動割り当てでキーを作るときは Datastore.allocateId(…)。キーを作成したらモデルオブジェクトの主キープロパティにセットする。主キーをセットせずに Datastore.put するとキーは自動生成される。</p> <h4 id="relationship">リレーションシップ</h4> <h5 id="unidirectional-one-to-one">片方向1対1関連</h5> <p>ModelRef型のプロパティを作って次のように初期化とgetterメソッドを作成。</p> <p class="brush:java">@Model public class Address { … }<br><br>@Model public class Employee {<br> private ModelRef<Address> addressRef = <br> new ModelRef<Address>(Address.class);<br> public ModelRef<Address> getAddressRef() { return addressRef; }<br>}</p> <p>インスタンスを作成して関連付けして保存</p> <p class="brush:java">Address address = new Address();<br>Employee employee = new Employee();<br>employee.getAddressRef().setModel(address);<br>Datastore.put(address, employee);</p> <p>ロードするときは、</p> <p class="brush:java">Employee employee = Datastore.get(Employee.class, employeeKey);<br>Address address = employee.getAddressRef().getModel();</p> <p>参照先はレイジーロードされる。</p> <h5 id="bidirectional-one-to-one">双方向1対1関連</h5> <p>先ほどの関連に逆方向の参照を追加。逆方向はInverseModelRef型にする。逆方向の参照は永続化しない。Addressクラスに以下のコードを追加。</p> <p class="brush:java">@Attribute(persistent = false)<br>private InverseModelRef<Employee, Address> employeeRef =<br> new InverseModelRef<Employee, Address><br> (Employee.class, "addressRef", this);<br><br>public InverseModelRef<Employee, Address> getEmployeeRef() {<br> return employeeRef;<br>}</p> <p>これでAddressからEmployeeへの参照ができる。</p> <p class="brysh:java">address.getEmployeeRef.getModel();</p> <p>プロパティに関連をセットするには正方向側(Employee側)のプロパティにセットする。逆方向にはsetModel()メソッドが無い。</p> <h5 id="unidirectional-many-to-one">片方向多対1関連</h5> <p>多 → 1 の参照を持つ関連の場合、片方向1対1の場合と同じ。</p> <h5 id="bidirectional-many-to-one">双方向多対1関連</h5> <p>多 → 1への参照に加え、1 → 多 への参照を追加。1側から多側への参照は永続化しない。</p> <p class="brush:java">@Attribute(persistent = false)<br>private InverseModelListRef<Employee, Department> employeeListRef =<br> new InverseModelListRef<Employee, Department><br> (Employee.class, "departmentRef", this);<br><br>public InverseModelListRef<Employee, Department> getEmployeeListRef()<br>{…}</p> <p>1側から多側を取得する場合は次のようにする。</p> <p class="brush:java">List<Employee> employeeList = department.getEmployeeListRef()<br> .getModelList();</p> <h5 id="bidirectional-many-to-many">双方向多対多関連</h5> <p>関連クラスを作って、1 ← 多(関連クラス) → 1 のようにする。</p> <p>関連クラスに1側への参照プロパティをそれぞれ作る。</p> <p class="brush:java">@Model<br>public class EmployeeProject {<br> …<br> private ModelRef<Employee> employeeRef =<br> new ModelRef<Employee>(Employee.class);<br> private ModelRef<Project> projectRef =<br> new ModelRef<Project>(Project.class);<br><br> public ModelRef<Employee> getEmployeeRef() {…}<br> public ModelRef<Project> getProjectRef() {…}<br> …<br>}</p> <p>1側は"双方向多対1"の逆参照と同様。EmployeeProjectに対して永続化しない InverseModelListRef<EmployeeProject, 1側クラス> のプロパティとgetterを作る。</p> <p>ロードするときは片側(たとえばProjectオブジェクト)から関連オブジェクトのリストを取得して、関連オブジェクトから反対側のオブジェクトを得る。</p> <p class="brush:java">relObject = project.getEmployeeProjectRef().getModelList();<br>relObject.getEmployeeRef().getModel();</p> <h4 id="create-key">キーの作成</h4> <p>アプリ指定の値で作成</p> <p class="brush:java">Datastore.createKey(…);</p> <p>値を自動割り当て</p> <p class="brush:java">Datastore.allocateId(…);</p> <p>親エンティティを指定して作成</p> <p class="brush:java">Datastore.createKey(parentKey, Child.class, value);<br>Datastore.allocateId(parentKey, Clind.class);</p> <h4 id="put">データオブジェクトの新規追加</h4> <p class="brush:java">org.slim3.datastore.DataStore.put(object);</p> <p>非同期での保存も可能。</p> <p class="brush:java">DataStore.putAsync(object);</p> <p>複数のオブジェクトの保存はコレクションか配列をputに渡すか、putに複数のパラメータを並べる</p> <h4 id="get">オブジェクトのロード</h4> <p class="brush:java">Datastore.get(Class, Key);</p> <p>複数のオブジェクトの取り出しもできる。</p> <p class="brush:java">DataStore.get(Class, コレクション or 配列);<br>Datastore.get(Class, key1, key2, …);</p> <h4 id="update">オブジェクトの更新</h4> <p>オブジェクトを更新するときは、ロード(get)、オブジェクトの値の変更、保存(put)の順で行う。</p> <h4 id="delete">オブジェクトの削除</h4> <p class="brush:java">Datastore.delete(Key);</p> <p>ロードや更新と同様、複数オブジェクトを同時に削除できる。<br>子孫エンティティごと削除するには</p> <p class="brush:java">Datastore.deleteAll(Key);</p> <h4 id="query">クエリ</h4> <h5 id="metadata">メタデータ</h5> <p>データクラスをEclipseで作成すると、自動的にメタデータが作成される。クエリの作成の際にこのメタデータを使う。</p> <p>DataClassのメタデータのインスタンスは次のように取得できる。</p> <p class="brush:java">DataClassMeta meta = DataClassMeta.get();</p> <h5 id="filter">フィルタ</h5> <p class="brush:java">EntityQuery query = Datastore.query(meta);<br>List results = query.filter(<br> meta.property1.equal(value1),<br> meta.progerty2.greaterThan(value2), … <br> ).asList();</p> <p>インメモリでフィルタ。</p> <p>endsWith や contains が使える。</p> <p class="brush:java">query.filterInMemory(meta.property.endsWith("XYZ"))</p> <h5 id="sort">ソート</h5> <p class="brush:java">query.sort(meta.property1.asc, meta.property2.desc)</p> <p>インメモリでソート</p> <p class="brush:java">query.sortInMemory(meta.property.asc)</p> <h5 id="offset-limit">offset, limit</h5> <p class="brush:java">query.offset(5).limit(10)</p> <h5 id="executing-query">クエリの実行と結果</h5> <p class="brush:java">query.asList()<br>query.asIterator()<br>query.asSingle()<br>query.asKeyList()<br>query.asKeyIterator()</p> <h5 id="ancestor-query">祖先クエリ</h5> <p>[TODO] 後で調べる</p> <p class="brush:java">List<Child> list = Datastore.query(Child.class, ancestorKey)<br> .asList();</p> <h5 id="kindnasi">kind なしの祖先クエリ</h5> <p>[TODO] 後で調べる</p> <p class="brush:java">List<Entity> list = Datastore.query(ancestorKey).asList();</p> <h5 id="limitation-query">クエリの制限</h5> <p>不等式フィルタはクエリ中で1つのプロパティだけにしか使用できない。<br>不等式で使われたプロパティは最初のソート項目に無ければならない(ソートを指定しなければならない)。</p> <h4 id="index">インデックス</h4> <p>フィルタで指定したプロパティを持っていないエンティティは、クエリに引っかからない。<br>インデックスされないプロパティはクエリに引っかからない。<br>Text、Blob、@Attribute(unindexed = true) アノテーションをつけたプロパティはインデックスされない。<br>同じプロパティ名で別の型の値を持つエンティティがあると、型でソートされたあと値でソートされる。<br>対応するインデックス定義が無いクエリは失敗する。</p> <h5 id="index-declaration">インデックスの定義</h5> <p>単純なクエリに対するインデックスは自動作成される。</p> <ul> <li>フィルタとソート順を使用しないクエリ <li>等式フィルタと祖先フィルタのみを使用するクエリ <li>単一のプロパティに対する不等式フィルタのみを使用するクエリ <li>フィルタなしで、プロパティに昇順か降順のどちらかのソート順が設定されているクエリ <li>等式フィルタをプロパティに使用して、不等式または範囲フィルタをキーに使用しているクエリ</li></ul> <p>インデックスはxmlで定義。<br>クエリに必要なインデックス定義は開発環境でクエリを実行したときに自動的に作成される。</p> <p>手作業で定義するときは WEB-INF/datastore-indexes.xml に書く。</p> <p>datastore-indexes.xml の autoGenerate が true になっている場合、WEB-INF/appengine-generated/datastore-indexes-auto.xml にインデックスが自動生成される。</p> <h4 id="query-cursor">クエリカーソル</h4> <p>カーソルとは、フェッチ処理後の次の位置を表す文字列。<br>S3QueryResultList を通じてカーソルを使用する。</p> <p class="brush:java">S3QueryResultList results = query.limit(20).asQueryResultList();<br>results.getEncodedCursor();<br>results.getEncodedFilters();<br>results.getEncodedSorts();<br>results.hasNext();<br><br>query.encodedCursor(encodedCursor)<br> .encodedFilters(encodedFilters)<br> .encodedSorts(encodedSorts)<br> .limit(20).asQueryResultList();</p> <h5 id="limitation-cursor">カーソルの制限</h5> <ul> <li>in や != フィルタのクエリには使えない <li>カーソルは同じクエリにしか使えない。kind、フィルタ、フィルタ値、祖先フィルタ、ソートが同じであること。 <li>インデックス設定を変更するとクエリは無効になる</li></ul> <p> </p> <h4 id="entitygroup">エンティティグループ</h4> <p>エンティティの親子関係はキーを作成するときに親エンティティのIDを指定することで決定する<br>プロパティに他のエンティティの参照を持ってもエンティティグループに入れるわけではない<br>エンティティの親子関係はあとで変えることはできない<br>先祖エンティティが削除されても子孫エンティティは削除されない</p> <h4 id="transaction">トランザクション</h4> <p class="brush:java">Transaction tx = Datastore.beginTransaction();<br>DataClass data = Datastore.get(tx, Dataclass.class, key);<br>data.setProperty(value);<br>Datastore.put(tx, data);<br>tx.commit();</p> <p>単一のトランザクションでできることは</p> <ul> <li>1つのグループ内の複数のエンティティを変更すること <li>グループに新しいエンティティを追加すること</li></ul> <p>1つのトランザクション内では1つのエンティティグループの操作だけができる<br>(エンティティのロードも含めて)。<br>楽観的並列処理なのでトランザクションが失敗したらアプリ側で何度かリトライする。<br>トランザクションの外側のトランザクション分離性はRead committedに近いがトランザクションはSerializableで実行される。<br>トランザクション内の祖先クエリやgetはトランザクション内での変更があってもトランザクション開始時点のスナップショットを返す。</p> <h5 id="versioning">バージョンによる楽観的ロック</h5> <p>トランザクションを超える更新(Web画面のためのGETリクエストの後、更新のためのPOSTリクエストでDatastoreにputするなど)を行う場合、データのバージョンにより競合を検出できる。</p> <p>エンティティに @Attribute(version = true) を付けたプロパティを用意することで、書き込みためのgetの際にすでに他の書き込みによってバージョンが変更されていたら ConcurrentModificationException が投げられる。</p> <p class="brush:java">Datastore.get(Transaction, Class, Key, version)</p> <h5 id="globaltransaction">クロスグループ(XG)トランザクション (2013/10/12追加)</h5> <p>まず、Datastore が HRD で動作するよう設定する。<br>次にappengine-web.xml の system-properties要素に</p> <p class="brush:xml"><property name="slim3.useXGTX" value="true"/></p> <p>を加える。これがないとローカル環境でXGが使えない。<br>あとは通常通りトランザクションを使用するだけで1つのトランザクションで複数のグループを扱うことができる。</p> <h5><strike>グローバルトランザクション</strike> (2013/10/12削除)</h5> <p><strike>複数のエンティティグループにまたがったトランザクションを実行できる。</strike></p> <p class="brush:java">GlobalTransaction gtx = Datastore.beginGlobalTransaction();<br>gtx.get(…);<br>gtx.put(…);<br>gtx.commit();</p> <p><strike>同じエンティティグループに対してローカルトランザクションとグローバルトランザクションを混ぜて使ってはいけない。グローバルトランザクションはパフォーマンス的にはローカルトランザクションと大きな違いはないそうなので、グローバルトランザクションを使う可能性があればそのエンティティグループの操作には常にグローバルトランザクションを使うと良い。</strike></p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-11314680386946583512011-05-31T12:29:00.001+09:002011-06-08T01:47:30.616+09:00MultitenancyとNamespaces API (Google App Engine)<div id="toc"></div> <p> </p> <h4 id="multitenancy">Multitenancy</h4> <p>Multitenancyとは1つのアプリケーションインスタンスが複数のクライアント(ユーザーグループ)に対してサービスを行うソフトウェアアーキテクチャ。</p> <p>ドキュメントによは、次のような用途が記載されている。</p> <ul> <li>ユーザーインフォメーションを分類する <li>アドミニストレーターデータをアプリケーションデータから分離する <li>テストと完成品のために、分離されたデータストアインスタンスを作り出す <li>多数のアプリケーションを一つのAppEngineのインスタンス上で走らせる</li></ul> <p>つまりうっかり混ざってほしくないデータを分けて保存したい場合に使えるようだ。Namespace APIを使ってネームスペース設定することでデータをネームスペースごとに分けて保存することができる。</p> <h4 id="appengine-apis-that-use-namespaces">ネームスペースに対応したAppEngine API</h4> <p>ネームスペースが使えるのは</p> <ul> <li>Datastore <li>Memcache <li>Task Queue</li></ul> <p>だけ。</p> <p>Blobstoreではネームスペースは使えない。</p> <h4 id="namespace-api">Namespace APIを使う</h4> <p>Multitenancyは Namespaces APIを使って実現する。使い方は簡単で、NamespaceManager.set(String) メソッドを呼ぶだけでカレントネームスペースを設定できる。</p> <p>ネームスペースに設定できる文字列は100文字までの英数字、'-'、'_'、'.'で構成された文字列。アンダーバーで始まる文字列はシステム予約なので使えない。</p> <p>ネームスペースを設定しなければカレントネームスペースはnullで、この場合各種AppEngine APIは空文字列("")のネームスペースを使用する。</p> <p>ネームスペースはリクエストごとに設定する必要がある。各リクエストが開始された時点ではカレントネームスペースは設定されていない。</p> <h4 id="avoid-data-leaks">データ漏洩を避ける</h4> <p>ネームスペースで分離したデータがネームスペースを超えて漏洩しないように注意しなければならない。ネームスペースは文字列ひとつで設定できるのでうっかりすればネームスペースを超えたデータにアクセスできてしまう。</p> <p>Blobsoreはネームスペースをサポートしないので、ネームスペースを使うアプリケーションでは、blobキーをネームスペースつきのDatastoreに保存してこのDatastoreデータを経由してblobにアクセスする。ブラウザから渡されたblobキーで直接blobにアクセスしてしまうと分離したいデータにアクセスできてしまうかもしれない。</p> <h4 id="datastore">Datastore</h4> <p>Datastore APIを呼び出す前にNamespaceManagerでカレントネームスペースを設定しておくだけで良い。</p> <p>KeyやQueryを作るとき、APIはカレントネームスペースを参照してKey、Queryオブジェクトにネームスペースを設定する。先祖を指定してキーを作成すると、新しいキーは先祖キーのネームスペースを継承する。<br>なお、KeyやQueryに明示的にネームスペースを設定するJava APIは無い。</p> <p>Key、Queryオブジェクトをシリアライズしたデータにはネームスペースも含まれる。これらをデシリアライズして使うときにはネームスペースが適切かどうか注意する必要がある。</p> <p>カレントネームスペースが”a”のときに作られたキーは、シリアライズされてカレントネームスペース”b”でデシリアライズされてもキーのネームスペースは”a”のままで復元される。これをDatastoreにストアするとネームスペース”a”にストアされる。</p> <p>信用できないソース(ブラウザなど)から渡されたKeyでDatastoreにアクセスするとネームスペースを超えてデータにアクセスしてしまう危険があるので、受け取ったキーのネームスペースが適切かどうか検証してから使用すること。</p> <h4 id="memcache">Memcache</h4> <p>MemcacheService を作成するときに明示的にネームスペースを設定しなければ、MemcacheService はメソッドが呼ばれるときにカレントネームスペースの設定を参照して memcache にアクセスする。</p> <p>下のコード例はドキュメントから引用。</p> <p class="brush:java">// Create a MemcacheService that uses the current namespace by<br>// calling NamespaceManager.get() for every access.<br>MemcacheService current =<br> MemcacheServiceFactory.getMemcacheService();<br><br>// stores value in namespace "abc"<br>String oldNamespace = NamespaceManager.get();<br>NamespaceManager.set("abc");<br>try {<br> current.put("key", value); // stores value in namespace “abc”<br>} finally {<br> NamespaceManager.set(oldNamespace);<br>}</p> <p>MemcacheServiceFactory.getMemcacheService(String) で明示的にネームスペースを指定すると、カレントネームスペースを無視して指定したネームスペースで memcache にアクセスする。</p> <p class="brush:java">MemcacheService boundMemcache =<br> MemcacheServiceFactory.getMemcacheService("specific-namespace");<br>NamespaceManager.set("whatever-namespace");<br>// このレコードはネームスペース"specific-namespace"でストアされる。<br>boundMemcache.put("key3", "value3");</p> <h4 id="task-queue">Task Queue</h4> <p>タスクが作られるときにNamespaceManagerにセットしたカレントネームスペースとGoogle Appsドメインも(もしあれば)共にキューにプッシュされる。</p> <p>タスクが実行されるとき、カレントネームスペースとGoogle Appsドメインが復元される。もしタスクをaddするときにカレントネームスペースが設定されていなければ、タスクが実行されるとき空のネームスペースが設定される。</p> <p>タスク名はネームスペースで分離されないのですべてのネームスペースに渡ってユニークな名前にしないとぶつかる。</p> <p>Queueからタスクをプルする場合はネームスペースの機能は提供されない。自前でタスクのネームスペースを復元できるようにペイロードにネームスペースを入れるなどの策が必要。</p> <h4 id="api">API</h4> <h5>Class NamespaceManager</h5> <p>カレントネームスペースを操作する機能を提供する。</p> <p>カレントネームスペースはget()によって返される文字列。Datastore、Memcache、Task QueueのAPIで使われる。</p> <p>ネームスペースに関連したクラス(例えば、Key、Query、MemcacheService)が作られるとき、ネームスペースが決まっていなければget()の呼び出しで決定される。もしget()がnullを返したらカレントネームスペースはセットされておらずこれらのAPIは空の("")ネームスペースを使う。</p> <p>例:</p> <p class="brush:java">NamespaceManager.set("a-namespace");<br>MemcacheService memcache =<br> MemcacheServiceFactory.getMemcacheService();<br>// Store record in namespace "a-namespace"<br>memcache.put("key1", "value1");<br><br>NamespaceManager.set("other-namespace");<br>// Store record in namespace "other-namespace"<br>memcache.put("key2", "value2");<br><br>MemcacheService boundMemcache =<br> MemcacheServiceFactory.getMemcacheService("specific-namespace");<br>NamespaceManager.set("whatever-namespace");<br>// このレコードはネームスペース"specific-namespace"でストアされる。<br>boundMemcache.put("key3", "value3");</p> <p>MemcacheService memcache (上記の例で)はカレントネームスペースを使う。そして key1 がネームスペース「a-namespace」にストアされる。key2 はネームスペース「other-namespace」にストアされる。カレントネームスペースに優先してデータを特定のネームスペースにストアもできる。上記の例で key3 はネームスペース「specific-namespace」にストアされる。</p> <p>Task Queueの Queue.add() メソッドは追加されるタスクの中にNamaspaceManagerの設定を転送し、追加されたタスクはタスクを作ったものと同じカレントネームスペースで実行される。</p> <p>例外的に、カレントネームスペースが設定されていないとき(つまりget()がnullを返すとき)は空のネームスペース("")が作られたタスクのリクエストに転送される。</p> <p><strong>メソッド</strong></p> <p><strong>public static void set(java.lang.String newNamespace)</strong></p> <p> ネームスペースに関連したサービスのネームスペース初期化に使われる値をセットする。</p> <p> Parameters:<br> <strong>newNamespace</strong> - 新しいネームスペース。<br> Throws:<br> java.lang.IllegalArgumentException - ネームスペース文字列が正しくないとき。</p> <p id="NamespaceManager-get"><strong>public static java.lang.String get()</strong></p> <p> カレントネームスペース設定またはnull(設定されていないとき)返す。</p> <p> もしカレントネームスペースが設定されていなければ、呼び出し元は ネームスペースに関連したすべてのサービスで空のネームスペース(“”)を使うべきである。</p> <p id="NamespaceManager-getGoogleAppsNamespace"><strong>public static java.lang.String getGoogleAppsNamespace()</strong></p> <p> このリクエストのGoogle Appsドメインを返す。または、空の文字列を返す。</p> <p id="NamespaceManager-validateNamespace"><strong>public static void validateNamespace(java.lang.String namespace)</strong></p> <p> ネームスペース文字列の妥当性を検証する。</p> <p> Throws:<br> java.lang.IllegalArgumentException - ネームスペース文字列のフォーマットが正しくないとき。</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-87273685001712444142011-05-28T15:36:00.001+09:002011-06-06T12:52:53.434+09:00アプリケーションへのアクセスを制限する (Google App Engine)<h4>URLパターンでアクセスを制限する</h4> <p>特定のURLパターンに対してアクセスを制限するには web.xml のsecurity-constraint要素で定義できる。</p> <p>web-app要素(ルートの要素)の下にsecurity-constraint要素を追加する。</p> <p class="brush:xml"><security-constraint><br><br> <web-resource-collection><br> <web-resource-name>admin tool</web-resource-name><br> <url-pattern>/admin-tool*</url-pattern><br> </web-resource-collection><br><br> <user-data-constraint><br> <role-name>admin</role-name><br> </user-data-constraint><br><br></security-constraint></p> <p>アクセス可能なロールをadminとしておくと、AppEngineアプリの設定画面で管理者として設定したGoogleアカウントでしかアクセスできなくなる。Googleにログインせずに該当のURLにアクセスした場合は自動的にGoogleアカウントへのログイン画面へリダイレクトされる。</p> <h4>プログラムで動的にアクセスを制限する</h4> <p>開発環境とGAEのサーバー環境でアクセス制限方法を変えたり、非公開バージョンだけにアクセス制限をかけたい場合など、security constraint では柔軟性に欠ける場合にはアプリ自前のコードでアクセス制限をかけることができる。ただし、静的ファイルにはこの方法は使えない。</p> <p>ここで言う”管理者”もsecurity-constraintの場合と同じ、アプリの設定画面で設定したGoogleアカウントのことを指す。</p> <p>次のAPIはアクセス制限の役に立つ。</p> <p>SystemProperty.applicationId</di> <br>リクエストされたホスト名がアプリのホスト名かどうか判断するときに使う。</p> <p>SystemProperty.environment</di> <br>アプリが稼動する環境が開発環境かGAEサーバー環境かを判断するときに使う。</p> <p>UserService#isUserAdmin()</di> <br>Googleアカウントにログイン中のユーザーがアプリの管理者かどうかを判断するときに使う。</p> <p><di>HttpServletRequest#getUserPrincipal()</di> <br>ユーザーがログインしているかを判断するときに使う。 </p> <p>コード例</p> <p class="brush:java">UserService userService = UserserviceFactory.getUserService();<br>if ( request.getUserPrincipal() != null <br> && userService.isUserAdmin() ) {<br> // アクセス許可<br>}<br>else {<br> // アクセス拒否<br>}</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-34065114855845739372011-04-25T11:24:00.000+09:002011-06-06T13:04:02.706+09:00Apache Antを使ってビルドする (Google App Engine)<div id="toc"></div> <p> </p> <p>App EngineのアプリのビルドはEclipseプラグインを使うと簡単だが、Antを使うこともできる。</p> <p>以下、Google App Engineのドキュメントに沿って説明する。コード例などはドキュメントから引用。</p> <h4>プロジェクトのディレクトリ構成</h4> <p>App Engineアプリの典型的なディレクトリ構成は次のとおり。これ以降このディレクトリ構成を前提にする。</p> <blockquote> <p>プロジェクトルート/<br> src/<br> パッケージ/Javaソースコード<br> META-INF/<br> 設定ファイルなど<br> war/<br> Web用のファイル (JSP, 画像などの静的ファイル)<br> WEB-INF/<br> アプリの設定ファイルなど<br> classes/<br> classファイル<br> lib/<br> ライブラリのJAR</p></blockquote> <h4>ビルドファイル</h4> <p>build.xmlには以下の設定をする。</p> <p>App EngineのSDKの場所。自分の環境に合わせて設定する。</p> <p class="brush:xml"><property name="sdk.dir" location="../appengine-java-sdk" /></p> <p>Antマクロのインポート。</p> <p class="brush:xml"><import file="${sdk.dir}/config/user/ant-macros.xml" /></p> <h4>コンパイルのためのクラスパスの定義</h4> <p>コンパイルに必要なJARをクラスパスに追加する。以下の場所をクラスパスに追加する。</p> <ul> <li>プロジェクトに自分で追加したjarファイル <li>App Engine SDKのJARファイル</li></ul> <p class="brush:xml"><path id="project.classpath"><br> <pathelement path="war/WEB-INF/classes" /><br> <fileset dir="war/WEB-INF/lib"><br> <include name="**/*.jar" /><br> </fileset><br> <fileset dir="${sdk.dir}/lib"><br> <include name="shared/**/*.jar" /><br> </fileset><br></path></p> <h4>JARのコピー</h4> <p>App Engine SDKのJARをwar/WEB-INF/libにコピーする。</p> <p class="brush:xml"><target name="copyjars"<br> description="Copies the App Engine JARs to the WAR."><br> <copy<br> todir="war/WEB-INF/lib"<br> flatten="true"><br> <fileset dir="${sdk.dir}/lib/user"><br> <include name="**/*.jar" /><br> </fileset><br> </copy><br></target></p> <h4>ソースファイルのコンパイル</h4> <p>普通にコンパイルするだけ。</p> <p>まず、コンパイルしたclassファイルを入れるディレクトリを作成。次に出力ディレクトリにリソースをコピー(Javaのソースファイルはコピーから除外)。そしてコンパイル。</p> <p class="brush:xml"><target name="compile" depends="copyjars"<br> description="Compiles Java source and copies other source files to the WAR."><br><br> <mkdir dir="war/WEB-INF/classes" /><br> <copy todir="war/WEB-INF/classes"><br> <fileset dir="src"><br> <exclude name="**/*.java" /><br> </fileset><br> </copy><br> <javac<br> srcdir="src"<br> destdir="war/WEB-INF/classes"<br> classpathref="project.classpath"<br> debug="on" /><br></target></p> <h4>JDOファイルの拡張</h4> <p>JDOを使わないならこのプロセスは不要。</p> <p>JDOを使うためにはJDOアノテーションをつけたクラスに対してコンパイル後のclassファイルに後処理が必要。</p> <p>App EngineのAntマクロ<enhance_war>を使う。</p> <p class="brush:xml"><target name="datanucleusenhance" depends="compile"<br> description="Performs JDO enhancement on compiled data classes."><br> <enhance_war war="war" /><br></target></p> <h4>開発用サーバの起動</h4> <p>開発用サーバもAntマクロで起動できる。</p> <p class="brush:xml"><target name="runserver" depends="datanucleusenhance"<br> description="Starts the development server."><br> <dev_appserver war="war" /><br></target></p> <p>より詳細な設定もできる。サーバの起動ポート、JavaVMの引数を指定する方法はドキュメントによると以下のとおりだが、このデバッグオプションを指定するとなぜか正常にJettyが起動しなかった。</p> <p class="brush:xml"><dev_appserver war="war" port="8888" ><br> <options><br> <arg value="--jvm_flag=-Xdebug"/><br> <arg value="--jvm_flag=-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9999"/><br> </options><br></dev_appserver></p> <p>サーバを停止するには Ctrl + C を押すとドキュメントにはあるが、これではAndのプロセスが止まるだけでサーバのプロセスは止まっていないようなので、Windowsの場合ならタスクマネージャーでプロセスを終了させる必要がある。</p> <h4>App Engine へのアップロード</h4> <p><appcfg>マクロを使う。これはApp Engine SDKのツールAppCfgを呼び出すためのマクロ。パラメータなど詳細はAppCfgのドキュメントを参照。</p> <p class="brush:xml"><target name="update" depends="datanucleusenhance"<br> description="Uploads the application to App Engine."><br> <appcfg action="update" war="war" /><br></target></p> <h4>AppCfgタスク</h4> <p>アプリのアップデート以外のAppCfgは次のとおり。</p> <p>datastoreのインデックスを更新する。アプリをアップデートする前にインデックスだけ更新するときに使う。</p> <p class="brush:xml"><target name="update_indexes" depends="datanucleusenhance"<br> description="Uploads just the datastore index configuration to App Engine."><br> <appcfg action="update_indexes" war="war" /><br></target></p> <p>ロールバック。アプリの更新が部分的に完了した場合に元に戻す。</p> <p class="brush:xml"><target name="rollback" depends="datanucleusenhance"<br> description="Rolls back an interrupted application update."><br> <appcfg action="rollback" war="war" /><br></target></p> <p>ログのダウンロード。</p> <p class="brush:xml"><target name="request_logs"<br> description="Downloads log data from App Engine for the application."><br> <appcfg action="request_logs" war="war"><br> <options><br> <arg value="--num_days=5"/><br> </options><br> <args><br> <arg value="logs.txt"/><br> </args><br> </appcfg><br></target></p> <h4>ターゲットの依存関係</h4> <p>ここまでの説明で登場したターゲットの実行順は次のようになっている。</p> <p>copyjars → compile → datanucleusenhance → runserver</p> <p>[省略] → datanucleusenhance → update</p> <p>[省略] → datanucleusenhance → update_indexes</p> <p>[省略] → datanucleusenhance → rollback</p> <p>request_logs</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-8493035937833754812011-03-02T12:40:00.001+09:002011-06-06T12:13:17.470+09:00Blobstoreドキュメント日本語要約 (Google App Engine)<div id="toc"></div> <p> </p> <h4 id="overview">概要</h4> <p>BlobstoreはDatastoreより大きなblobオブジェクトを扱うことができる。BlobはHTTPリクエストでファイルをアップロードすることで作られる。ファイルがアップロードされるとBlobstoreがblob keyを返す。blogを操作するためにこのキーを使う。</p> <p>blobのサイズは最大で2GB。<br>Blobstoreのblobは、Datastoreのblobプロパティとは別のもの。<br>blobは削除することはできるが変更することはできない。</p> <p>blobの作成時刻やコンテントタイプなどの情報はDatastoreに記録される。blobキーはblobの情報を読み出すクエリに使うことができる。</p> <h4 id="using-the-blobstore">Blobstoreの使い方</h4> <p>アプリはblobに直接アクセスできない。かわりにDatastoreのblob情報エンティティを通してblobを扱える。</p> <p>ユーザはHTMLフォームの1つまたは複数のファイル入力フィールドからblobを作ることができる。</p> <p>アプリはblobstoreService.createUploadUrl()で得られるURLをフォームのactionにセットする。このメソッドにはアプリのハンドラのパスを渡す。ユーザはBlobstoreに直接ファイルをアップロードする。</p> <p>Blobstoreはユーザのリクエストを書き換えてアップロードされたファイルをストアし、blobキーに対応するファイルデータを置き換える。そして書き換えられたリクエストをあなたがcreateUploadUrl()に提供したURLパスのハンドラに渡す。</p> <p>ハンドラはblobキーに基づいた追加の処理ができる。</p> <p>最後に、ハンドラはヘッダのみのリダイレクトレスポンス(301, 302, 303)を返して、典型的にはブラウザは、blobアップロードのステータスを示す別のページにリダイレクトする。</p> <p>アプリはファイルのようなストリーミングインタフェースを使ってBlobstoreのデータを読むことができる(BlobstoreInputStream)。</p> <h4 id="uploading-a-blob">blobをアップロードする</h4> <p>フォームの例。</p> <p class="brush:html"><form action="<%= blobstoreService.createUploadUrl("/upload") %>"<br> method="post" enctype="multipart/form-data"><br> <input type="file" name="myFile"><br> <input type="submit" value="Submit"><br></form> </p> <p>actionのURLはcreateUploadUrl()で作成。メソッドにはアップロード完了で呼ばれるハンドラのURLを渡す。</p> <p>フォームにはファイルinputのフィールドを含む必要がある。enctypeは"multipart/form-data"にする。</p> <p>ハンドラが呼ばれるときにはすでにblobは保存され、Datastoreにblob情報ができている。</p> <p>ハンドラでアップロードされたblobのリストを取り出す例。Mapには<ファイル名,BlobKey>の組で入っている。ファイル名はアップロードの際にフォームに書いた値。</p> <p>Map<String, BlobKey> blobs = blobstoreService.getUploadedBlobs(req);</p> <p>blobstoreがユーザリクエストを書き換えるとき、アップロードされたファイルのMIMEパートはbodyが空にして、MIMEパートのヘッダにblobキーを追加する。他のすべてのフォームフィールドとパートはそのままハンドラに渡される。</p> <p>コンテントタイプを指定しない場合、Blobstoreはファイルの拡張子からコンテントタイプを推定する。コンテントタイプが決定されないときは、新しく作られたblobは application/octet-streamが割り当てられる。</p> <h4 id="serving-a-blob">blobを供給する</h4> <p>blobを供給するためにはアプリでblobのダウンロードハンドラを用意する必要がある。ハンドラでは欲しいblobのキーをblobstoreService.serve(blobKey, res)メソッドに渡す。</p> <p>例:</p> <p class="brush:java">BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));<br>blobstoreService.serve(blobKey, res);</p> <p>blobはアプリのどんなURLからも供給できる。アプリでblobを供給するために、blobキーを含んだ特別なヘッダをレスポンスに含める。App Engineはレスポンスのボディをblobの内容で置き換える。</p> <h5 id="blob-byte-ranges">blobのバイト範囲</h5> <p>レスポンスにX-AppEngine-BlobRangeヘッダを含めることで大きなデータの一部だけを標準的なHTTPバイトレンジで返すことができる。</p> <p>X-AppEngine-BlobRangeをブランクにするとレンジヘッダを無視して完全なblobを返す。</p> <p>範囲指定の例:</p> <ul> <li>0-499 最初の500バイト <li>500-999 501バイト目から500バイト <li>500- 501バイト目から最後まで <li>-500 最後の500バイト</li></ul> <p>バイト範囲が正しければBlobstoreはクライアントに"206 Partial Content"のステータスコードと要求されたバイト範囲を送る。もしレンジが正しくなければBlobstoreは"416 Requested Range Not Satisfiable"を送る。</p> <p>Blobstoreは1つのリクエストで複数のバイト範囲をサポートしない。</p> <h4 id="using-the-image-service-with-the-blobstore">Blobstoreでイメージサービスを使う</h4> <p>イメージサービスはBlobstoreのデータを変換のソースとして使用できる。ソースイメージはBlobstoreデータの最大サイズまで扱える。変換されたイメージは1MBより小さくなければならない。</p> <p>詳しくはイメージサービスのドキュメントを参照。</p> <h4 id="writing-files">ファイルをBlobstoreに書き込む (実験的)</h4> <p>プログラムからblobデータを読み書きするためのAPIが用意されている。この機能によってデータをエクスポートしたり、生成したバイナリデータを保存したりできる。</p> <p>つきのようにして書き込み可能な空のファイルを作成し、書き込み可能な Channel をオープンする。</p> <p class="brush:java">AppEngineFile file = <br> fileService.createNewBlobFile("text/plain");<br><br>FileWriteChannel writeChannel = <br> fileService.openWriteChannel(file, lock);</p> <p>たとえ1バイトを書くだけでも、それぞれの書き込みが最小のCPUクォータを消費する。書き込みをバッチ処理することによって、効率的にクォータを使うことができる。<br>(訳注:この「書き込み」の単位がメソッド呼び出しなのかファイルをクローズするまでなのかは不明。)</p> <h4 id="quotas-and-limits">クォータとリミット</h4> <p>Blobstoreデータに使われたスペースは保存データのクォータ(課金対象)に計上される。<br>Datastoreに含まれるblob情報エンティティがDatastore関連のクォータに計上される。<br>Blobstoreの操作によって消費されたCPU時間はCPU時間のクォータ(課金対象)に計上される。</p> <p>詳しくはクォータの情報はクォータと管理コンソールの「クォータ詳細情報」のドキュメントを参照。</p> <p>リミット</p> <ul> <li>最大オブジェクトサイズ 2GB <li>1回のAPI呼び出しで読めるBlobstoreデータの最大のサイズ 1MB</li></ul> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-78115978725914189552011-02-27T15:34:00.001+09:002011-06-06T13:09:25.835+09:00Channel API ドキュメント日本語要約 (Google App Engine)<div id="toc"></div> <p> </p> <h4 id="overview"><strong>概要</strong></h4> <p>Channel APIはアプリケーションとgoogleサーバーの間に持続的な接続を作り、JavaScriptクライアントにポーリングを使わずにリアルタイムにメッセージを送ることができる。</p> <h4 id="yo"><strong>Channel APIの要素</strong></h4> <h5 id="yo-01"><strong>JavaScriptクライアント</strong></h5> <p>クライアントの役割</p> <ul> <li>チャネルに接続してサーバからチャネルのユニークなトークンを受け取る。 <li>ほかのクライアントのアップデートを待ち受けて、データを使用する。 <li>アップデートをサーバーに送信する。</li></ul> <h5 id="yo-02"><strong>サーバ</strong></h5> <p>サーバの役割</p> <ul> <li>個々のJavaScriptクライアントにユニークなチャネルを作る。 <li>ユニークトークンを作りJavaScriptクライアントに送る。 <li>クライアントからのアップデートをPOSTで受ける。 <li>チャネルを使ってクライアントにアップデートメッセージを送る。</li></ul> <h5 id="yo-03"><strong>クライアントID</strong></h5> <p>クライアントIDはサーバでそれぞれのJavaScriptクライアントを識別するためのもの。クライアントIDはアプリケーションでどのように設計しても良い。</p> <h5 id="yo-04"><strong>トークン</strong></h5> <p>トークンはJavaScriptクライアントがチャネルに接続して待ち受けることを可能にする役割がある。</p> <p>サーバはそれぞれのクライアントのためにクライアントIDと有効期限などの情報を使って トークンを作成する。</p> <h5 id="yo-05"><strong>チャネル</strong></h5> <p>チャネルはサーバーがクライアントIDで識別されるJavaScriptクライアントにアップデートを送るための片方向の通信路。</p> <p>サーバはクライアントからPOSTによってアップデートを受信すると、該当するクライアントにチャネルを使ってメッセージを送る。</p> <h5 id="yo-06"><strong>メッセージ</strong></h5> <p>メッセージはクライアントからサーバにPOSTによって送られる。</p> <p>受信すると、サーバはクライアントIDで識別されたクライアントにチャネルを使ってメッセージを渡す。</p> <p>メッセージは32KBまで。</p> <p>URLを送るときに生のメッセージで送るのは避ける。その代わりにメッセージが損なわれずに到達するようにJSONエンコーディングを使う。</p> <h5 id="yo-07"><strong>ソケット</strong></h5> <p>JavaScriptクライアントはサーバから提供されたトークンを使ってソケット開く。チャネルで更新を待ち受けるためにソケットを使う。</p> <h4 id="co"><strong>Channel APIを使うためのコード</strong></h4> <h5 id="co-01"><strong>チャネルを作る(サーバ)</strong></h5> <p>channelServiceを取得して、チャネルを作成。トークンを生成する。</p> <p class="brush:java">ChannelService channelService = <br> ChannelServiceFactory.getChannelService();<br>String token = channelService.createChannel(clientId);</p> <h5 id="co-02"><strong>チャネルに接続する(クライアント)</strong></h5> <p>サーバからもらったトークンを使ってチャネルを開く。</p> <p class="brush:java">channel = new goog.appengine.Channel(token);<br>socket = channel.open();<br>socket.onopen = onOpened;<br>socket.onmessage = onMessage;<br>socket.onerror = onError;<br>socket.onclose = onClose;</p> <h5 id="co-03"><strong>サーバーからクライアントへのメッセージ送信(サーバ)</strong></h5> <p class="brush:java">channelService.sendMessage(<br> new ChannelMessage(clientId, messageString));</p> <h5 id="co-04"><strong>トークンとセキュリティ</strong></h5> <p>createChannel()によって返されるトークンは他に知られないように秘密にしておく。もしトークンが盗まれたらチャネルに送られたメッセージを待ち受けできてしまう。</p> <p>トークンは2時間で期限切れになる。クライアントが2時間以上チャネルに接続したままでいると、ソケットのonerror(), onclose()がコールバックされる。</p> <h4 id="ca">注意</h4> <h5 id="ca-01">クライアントID毎に1つのクライアント</h5> <p>1つのクライアントIDで一度にチャネルに接続できるのは1つのクライアントだけ。アプリケーションは1つのクライアントIDでメッセージを多数のクライアントにばら撒くことはできない。</p> <h5 id="ca-02">ページ毎に1つのチャネル、チャネル毎に1つのクライアント</h5> <p>クライアントは1ページにつき1つのチャネルにだけ接続できる。</p> <p>もしアプリケーションがクライアントに複数の種類のデータを送る必要があるならサーバサイドでデータを集めてクライアントのソケットのonmessageハンドラに送る。</p> <h5 id="ca-03">クライアントの接続と切断の追跡</h5> <p>チャネルに接続したクライアントには"プレゼンス"の概念がない。これはクライアントのチャネルへの接続または切断についてアプリケーションに通知されないことを意味する。</p> <p>もし、アプリケーションがクライアントの接続を追跡する必要があるなら、1つの方法としてクライアントの状態を通知するために適当な間隔でPOSTメッセージをサーバに送るよう設計された関数をソケットのonopenプロパティにセットする。</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-25323329599343247002011-02-10T12:27:00.001+09:002011-06-01T12:42:21.556+09:00Slim3でDatastoreを使う - データ操作編<p><strong></strong></p> <p><strong></strong> </p> <p><strong>この記事はこちらへ移動しました。<br>「</strong><a href="http://www.programminglife.jp/2011/06/slim3datastore.html"><strong>Slim3でDatastoreを使う</strong></a><strong>」</strong></p> <p><strong></strong> </p> <h4><strong>キーの作成</strong></h4> <p>アプリ指定の値で作成<br>Datastore.createKey(…);</p> <p>値を自動割り当て<br>Datastore.allocateId(…);</p> <p>親エンティティを指定して作成<br>Datastore.createKey(parentKey, Child.class, value);<br>Datastore.allocateId(parentKey, Clind.class);</p> <h4>データオブジェクトの新規追加</h4> <p>org.slim3.datastore.DataStore.put(object);</p> <p>非同期での保存も可能。<br>DataStore.putAsync(object);</p> <p>複数のオブジェクトの保存はコレクションか配列をputに渡すか、putに複数のパラメータを並べる</p> <h4><strong>オブジェクトのロード</strong></h4> <p>Datastore.get(Class, Key);</p> <p>複数のオブジェクトの取り出しもできる。<br>DataStore.get(Class, コレクション or 配列);<br>Datastore.get(Class, key1, key2, …);</p> <h4><strong>オブジェクトの更新</strong></h4> <p>オブジェクトを更新するときは、ロード(get)、オブジェクトの値の変更、保存(put)の順で行う。</p> <h4><strong>オブジェクトの削除</strong></h4> <p>Datastore.delete(Key);</p> <p>ロードや更新と同様、複数オブジェクトを同時に削除できる。<br>子孫エンティティごと削除するには</p> <p>Datastore.deleteAll(Key);</p> <h4><strong>クエリ</strong></h4> <h5><strong>メタデータ</strong></h5> <p>データクラスをEclipseで作成すると、自動的にメタデータが作成される。クエリの作成の際にこのメタデータを使う。</p> <p>DataClassのメタデータのインスタンスは次のように取得できる。</p> <p>DataClassMeta meta = DataClassMeta.get();</p> <h5><strong>フィルタ</strong></h5> <p>EntityQuery query = Datastore.query(meta);<br>List results = query.filter( meta.property1.equal(value1), meta.progerty2.greaterThan(value2), … ).asList();</p> <p>インメモリでフィルタ。<br>endsWith や contains が使える。<br>query.filterInMemory(meta.property.endsWith("XYZ"))<br></p> <h5><strong>ソート</strong></h5> <p>query.sort(meta.property1.asc, meta.property2.desc)</p> <p>インメモリでソート<br>query.sortInMemory(meta.property.asc)</p> <h5><strong>offset, limit</strong></h5> <p>query.offset(5).limit(10)</p> <h5><strong>クエリの実行と結果</strong></h5> <p>query.asList()<br>query.asIterator()<br>query.asSingle()<br>query.asKeyList()<br>query.asKeyIterator()</p> <h5><strong>祖先クエリ</strong></h5> <p>[TODO] 後で調べる<br>List<Child> list = Datastore.query(Child.class, ancestorKey).asList();</p> <h5><strong>kind なしの祖先クエリ</strong></h5> <p>[TODO] 後で調べる<br>List<Entity> list = Datastore.query(ancestorKey).asList();</p> <h5><strong>クエリの制限</strong></h5> <p>不等式フィルタはクエリ中で1つのプロパティだけにしか使用できない。<br>不等式で使われたプロパティは最初のソート項目に無ければならない(ソートを指定しなければならない)。</p> <h4><strong>インデックス</strong></h4> <p>フィルタで指定したプロパティを持っていないエンティティは、クエリに引っかからない。<br>インデックスされないプロパティはクエリに引っかからない。<br>Text、Blob、@Attribute(unindexed = true) アノテーションをつけたプロパティはインデックスされない。<br>同じプロパティ名で別の型の値を持つエンティティがあると、型でソートされたあと値でソートされる。<br>対応するインデックス定義が無いクエリは失敗する。</p> <h5><strong>インデックスの定義</strong></h5> <p>単純なクエリに対するインデックスは自動作成される。</p> <ul> <li>フィルタとソート順を使用しないクエリ <li>等式フィルタと祖先フィルタのみを使用するクエリ <li>単一のプロパティに対する不等式フィルタのみを使用するクエリ <li>フィルタなしで、プロパティに昇順か降順のどちらかのソート順が設定されているクエリ <li>等式フィルタをプロパティに使用して、不等式または範囲フィルタをキーに使用しているクエリ</li></ul> <p>インデックスはxmlで定義。<br>クエリに必要なインデックス定義は開発環境でクエリを実行したときに自動的に作成される。</p> <p>手作業で定義するときは WEB-INF/datastore-indexes.xml に書く。</p> <p>datastore-indexes.xml の autoGenerate が true になっている場合、WEB-INF/appengine-generated/datastore-indexes-auto.xml にインデックスが自動生成される。</p> <h4><strong>クエリカーソル</strong></h4> <p>カーソルとは、フェッチ処理後の次の位置を表す文字列。<br>S3QueryResultList を通じてカーソルを使用する。</p> <p>S3QueryResultList results = query.limit(20).asQueryResultList();<br>results.getEncodedCursor();<br>results.getEncodedFilters();<br>results.getEncodedSorts();<br>results.hasNext();</p> <p>query.encodedCursor(encodedCursor)<br>.encodedFilters(encodedFilters)<br>.encodedSorts(encodedSorts)<br>.limit(20).asQueryResultList();</p> <h5><strong>カーソルの制限</strong></h5> <ul> <li>in や != フィルタのクエリには使えない <li>カーソルは同じクエリにしか使えない。kind、フィルタ、フィルタ値、祖先フィルタ、ソートが同じであること。 <li>インデックス設定を変更するとクエリは無効になる</li></ul> <p> </p> <h4><strong>トランザクション</strong></h4> <p>Transaction tx = Datastore.beginTransaction();<br>DataClass data = Datastore.get(tx, Dataclass.class, key);<br>data.setProperty(value);<br>Datastore.put(tx, data);<br>tx.commit();</p> <p>単一のトランザクションでできることは</p> <ul> <li>1つのグループ内の複数のエンティティを変更すること <li>グループに新しいエンティティを追加すること</li></ul> <p>1つのトランザクション内では1つのエンティティグループの操作だけができる<br>(エンティティのロードも含めて)。<br>楽観的並列処理なのでトランザクションが失敗したらアプリ側で何度かリトライする。<br>トランザクションの外側のトランザクション分離性はRead committedに近いがトランザクションはSerializableで実行される。<br>トランザクション内の祖先クエリやgetはトランザクション内での変更があってもトランザクション開始時点のスナップショットを返す。</p> <h5><strong>バージョンによる楽観的ロック</strong></h5> <p>トランザクションを超える更新(Web画面のためのGETリクエストの後、更新のためのPOSTリクエストでDatastoreにputするなど)を行う場合、データのバージョンにより競合を検出できる。</p> <p>エンティティに @Attribute(version = true) を付けたプロパティを用意することで、書き込みためのgetの際にすでに他の書き込みによってバージョンが変更されていたら ConcurrentModificationException が投げられる。</p> <p>Datastore.get(Transaction, Class, Key, version)</p> <h5><strong>グローバルトランザクション</strong></h5> <p>複数のエンティティグループにまたがったトランザクションを実行できる。</p> <p>GlobalTransaction gtx = Datastore.beginGlobalTransaction();<br>gtx.get(…);<br>gtx.put(…);<br>gtx.commit();</p> <p>同じエンティティグループに対してローカルトランザクションとグローバルトランザクションを混ぜて使ってはいけない。グローバルトランザクションはパフォーマンス的にはローカルトランザクションと大きな違いはないそうなので、グローバルトランザクションを使う可能性があればそのエンティティグループの操作には常にグローバルトランザクションを使うと良い。</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-18673832940719847712011-02-10T12:14:00.001+09:002011-06-01T12:26:32.540+09:00Slim3でDatastoreを使う - データ定義編<p><strong></strong></p> <p><strong></strong></p> <p><strong></strong></p> <p><strong></strong> </p><strong> <p><strong>この記事はこちらへ移動しました。<br></strong><strong>「</strong><a href="http://www.programminglife.jp/2011/06/slim3datastore.html"><strong>Slim3でDatastoreを使う</strong></a><strong>」</strong></p> <p> </p></strong> <p><strong></strong></p> <h4><strong>モデルの定義</strong></h4> <p>データのクラスに@Modelアノテーションを付ける<br>永続化するプロパティには getter, setter が必要<br>永続化しないフィールドには@Attribute(persistent = false) アノテーションを付ける</p> <h4><strong>フィールドの型</strong></h4> <p>フィールドで使える型はDatastoreの基本型、基本型のコレクション、シリアライズ可能なオブジェクト<br>フィールドで使える基本型 → <a href="http://sites.google.com/site/slim3appengine/slim3-datastore/defining-data-classes/core-value-types" target="_blank">Core Value Types</a><br>シリアライズ可能なオブジェクトはBlob型として格納される(@Attribute(lob = true)を付ける)<br>500文字(byte[]は500バイト)を超えるフィールドには@Attribute(lob = true)を付ける<br>配列はコレクションとして扱われない(シリアライズしてBlobになる?)<br>インデックスに対応した型はデフォルトでインデックスが付く<br>インデックスを付けたくないフィールドには@Attribute(unindexed = true)を付ける</p> <h4><strong>サポートするコレクション型</strong></h4> <p>ArrayList, LinkedList, HashSet, LinkedHashSet, TreeSet, List (ArrayList), Set (HashSet), SortedSet (TreeSet)</p> <p>()内はプロパティから返される実際のインスタンスの型</p> <h4><strong>自動的なプロパティ値の更新</strong></h4> <p>@Attribute(listener = ???) アノテーションで値を自動更新するリスナを指定できる</p> <p>保存するたびに更新</p> <p>@Attribute(listener = ModificationDate.class)<br>Date updatedAt;</p> <p>最初の保存のときだけ更新</p> <p>@Attribute(listener = CreationDate.class)<br>Date createdAt;</p> <h4><strong>キー</strong></h4> <p>モデルクラスに1つだけキーを持たなければならない<br>キーのフィールドには @Attribute(primaryKey = true) を付ける<br>キーは パス、kind、ID によって構成される<br>kind はクラスの単純名から付けられる<br>kindはクラスの @Model(kind = "...") アノテーションで変更可能<br>IDはアプリケーションで指定した文字列か、Datastoreが付けた数値(キーをnullのまま保存)<br>キーは Datastore.createKey(Class, String) で作成してキープロパティにセットする</p> <h4><strong>エンティティグループ</strong></h4> <p>エンティティの親子関係はキーを作成するときに親エンティティのIDを指定することで決定する<br>プロパティに他のエンティティの参照を持ってもエンティティグループに入れるわけではない<br>エンティティの親子関係はあとで変えることはできない<br>先祖エンティティが削除されても子孫エンティティは削除されない</p> <h4><strong>リレーションシップ</strong></h4> <h5><strong>片方向1対1関連</strong></h5> <p>ModelRef型のプロパティを作って次のように初期化とgetterメソッドを作成。</p> <p>@Model public class Address { … }</p> <p>@Model public class Employee {<br> private ModelRef<Address> addressRef = new ModelRef<Address>(Address.class);<br> public ModelRef<Address> getAddressRef() { return addressRef; }<br>}</p> <p>インスタンスを作成して関連付けして保存</p> <p>Address address = new Address();<br>Employee employee = new Employee();<br>employee.getAddressRef().setModel(address);<br>Datastore.put(address, employee);</p> <p>ロードするときは、</p> <p>Employee employee = Datastore.get(Employee.class, employeeKey);<br>Address address = employee.getAddressRef().getModel();</p> <p>参照先はレイジーロードされる。</p> <h5><strong>双方向1対1関連</strong></h5> <p>先ほどの関連に逆方向の参照を追加。逆方向はInverseModelRef型にする。逆方向の参照は永続化しない。Addressクラスに以下のコードを追加。</p> <p>@Attribute(persistent = false)<br>private InverseModelRef<Employee, Address> employeeRef = new InverseModelRef<Employee, Address>(Employee.class, "addressRef", this);</p> <p>public InverseModelRef<Employee, Address> getEmployeeRef() { return employeeRef;}</p> <p>これでAddressからEmployeeへの参照ができる。</p> <p>address.getEmployeeRef.getModel();</p> <p>プロパティに関連をセットするには正方向側(Employee側)のプロパティにセットする。逆方向にはsetModel()メソッドが無い。</p> <h5><strong>片方向多対1関連</strong></h5> <p>多 → 1 の参照を持つ関連の場合、片方向1対1の場合と同じ。</p> <h5><strong>双方向多対1関連</strong></h5> <p>多 → 1への参照に加え、1 → 多 への参照を追加。1側から多側への参照は永続化しない。</p> <p>@Attribute(persistent = false)<br>private InverseModelListRef<Employee, Department> employeeListRef = new InverseModelListRef<Employee, Department>(Employee.class, "departmentRef", this);</p> <p>public InverseModelListRef<Employee, Department> getEmployeeListRef() {…}</p> <p>1側から多側を取得する場合は次のようにする。</p> <p>List<Employee> employeeList = department.getEmployeeListRef().getModelList();</p> <h5><strong>双方向多対多関連</strong></h5> <p>関連クラスを作って、1 ← 多(関連クラス) → 1 のようにする。</p> <p>関連クラスに1側への参照プロパティをそれぞれ作る。</p> <p>@Model<br>public class EmployeeProject {<br> …<br> private ModelRef<Employee> employeeRef = new ModelRef<Employee>(Employee.class);<br> private ModelRef<Project> projectRef = new ModelRef<Project>(Project.class);</p> <p> public ModelRef<Employee> getEmployeeRef() {…}<br> public ModelRef<Project> getProjectRef() {…}<br> …<br>}</p> <p>1側は"双方向多対1"の逆参照と同様。EmployeeProjectに対して永続化しない InverseModelListRef<EmployeeProject, 1側クラス> のプロパティとgetterを作る。</p> <p>ロードするときは片側(たとえばProjectオブジェクト)から関連オブジェクトのリストを取得して、関連オブジェクトから反対側のオブジェクトを得る。</p> <p>relObject = project.getEmployeeProjectRef().getModelList();<br>relObject.getEmployeeRef().getModel();</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-74662352329092798942011-02-09T18:42:00.002+09:002013-10-12T13:44:26.862+09:00Google App Engine 目次<ul> <li><a href="http://www.programminglife.jp/2011/03/blobstore.html">Blobstore ドキュメント日本語要約</a> (2011/6/1更新) <li><a href="http://www.programminglife.jp/2011/02/channel-api.html">Channel API ドキュメント日本語要約</a> (2011/2/27) <li><a href="http://www.programminglife.jp/2011/02/datastore.html">Datastore 概要まとめ</a> (2011/2/9) <li><a href="http://www.programminglife.jp/2011/05/multitenancynamespaces-api-google-appp.html">MultitenancyとNamespaces API</a> (2011/5/31) <li><a href="http://www.programminglife.jp/2011/05/google-app-engine.html">アプリケーションへのアクセスを制限する</a> (2011/5/28) <li><a href="http://www.programminglife.jp/2011/04/apache-ant-google-app-engine.html">Apache Antを使ってビルドする</a> (2011/4/25) <li><a href="http://www.programminglife.jp/2011/02/gppgle-app-engine.html">Google App Engineのメンテナンスモード</a> (2011/2/8)</li></ul> <p><strong>slim3</strong></p> <ul> <li><a href="http://www.programminglife.jp/2011/06/slim3datastore.html">Slim3でDatastoreを使う</a> (2013/10/12更新)</li></ul> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-43120962858951841102011-02-09T18:42:00.001+09:002013-05-20T16:28:15.002+09:00Android 目次<ul> <li><a href="http://www.programminglife.jp/2013/05/listview.html">ListViewで無限スクロール</a> (2013/5/20) <li><a href="http://www.programminglife.jp/2011/09/wi-fiping-android.html">Wi-Fiでゲートウェイにpingする</a> (2011/9/26) <li><a href="http://www.programminglife.jp/2011/07/galaxys233.html">GalaxySを2.3.3にアップデートしたらバッテリー消費が激しくなった</a> (2011/7/7) <li><a href="http://www.programminglife.jp/2011/02/android_05.html">GalaxySの解像度をAPIと実寸で比較</a> (2011/2/5) <li><a href="http://www.programminglife.jp/2011/01/android_16.html">インテントを受け取れるアクティビティのリストを取得する</a> (2011/1/16) <li><a href="http://www.programminglife.jp/2011/01/android.html">アクティビティとタスク</a> (2011/1/12)</li></ul> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-52764464136072472462011-02-09T18:42:00.000+09:002011-02-09T18:42:27.333+09:00Flash 目次<ul> <li><a href="http://www.programminglife.jp/2010/04/flex4airsystemchrome.html">Flex4のAIRアプリでsystemChrome "none" でもタイトルバーを表示したいとき</a> <li><a href="http://www.programminglife.jp/2009/11/flash-player-101.html">Flash Player 10.1 ベータ版公開</a> <li><a href="http://www.programminglife.jp/2008/08/flashcs3blazeds.html">FlashCS3からBlazeDSを呼び出す</a></li></ul> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-54267944567550231732011-02-09T18:41:00.000+09:002011-02-09T18:42:09.536+09:00Java 目次<ul> <li><a href="http://www.programminglife.jp/2009/11/jaxb20091125.html">JAXBの使い方メモ(2009/11/25更新)</a> <li><a href="http://www.programminglife.jp/2008/06/joda-time.html">Joda Time</a> <li><a href="http://www.programminglife.jp/2008/06/ejb30beanjndi.html">EJB3.0のセッションBeanをJNDIで取得する方法</a></li></ul> <p><strong>Apache Ivy</strong></p> <ul> <li><a href="http://www.programminglife.jp/2008/07/ivyxmlconf.html">ivy.xmlのconfの使い方</a> <li><a href="http://www.programminglife.jp/2008/07/ivy.html">Ivyの設定</a> <li><a href="http://www.programminglife.jp/2008/06/ivymaven.html">IvyはMavenアレルギーの特効薬かもしれない</a></li></ul> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-57933425608724430282011-02-09T16:30:00.001+09:002011-06-01T11:00:40.447+09:00Datastore 概要まとめ (Google App Engine)<p>「<a href="http://code.google.com/intl/ja/appengine/docs/java/datastore/overview.html" target="_blank">Datastore Java API 概要</a>」のまとめ</p> <p><strong>エンティティ</strong></p> <p>エンティティ単位でデータを操作<br>エンティティはプロパティを持つ<br>プロパティは別のエンティティへの参照を持つこともできる<br>トランザクションが使える<br>トランザクションで複数の操作ができる<br>エンティティにはスキーマがない<br>JDO, JPAでアクセスできる<br>低レベルAPIもある<br>エンティティにはキーがある<br>キーか、プロパティに適合するクエリでエンティティをフェッチできる<br>クエリの結果はプロパティ値で並び替えることができる<br>プロパティは複数の値を持つこともできる</p> <p><strong>クエリとインデックス</strong></p> <p>クエリは指定されたクラスに対してキーとプロパティに対するフィルタと並び替えを指定できる<br>すべてのクエリはインデックスを使用する<br>インデックスは設定ファイルで定義する</p> <p><strong>トランザクションとエンティティグループ</strong></p> <p>エンティティの作成、更新、削除のすべての操作はトランザクションで実行される<br>1つのトランザクションで複数のエンティティを変更できる<br>同じエンティティグループに属するエンティティは同時に変更できる<br>エンティティグループに参加するには、そのエンティティグループのエンティティの子になればいい<br>親を持たないエンティティはルートエンティティになる<br>トランザクションはオプティミスティック・コンカレンシで実行される<br>エンティティグループの変更中はそのグループへのほかの更新はすぐにエラーになる<br>リトライはアプリケーションが行う</p> <p><strong>SQLとの違い</strong></p> <p>join が使えない<br>複数プロパティでの不等式のフィルタリングやサブクエリが使えない<br>データが一貫したプロパティを持っている必要はない<br>クエリで取得できるのはキーまたはエンティティ全体だけ</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-69475324955230992032011-02-08T19:08:00.001+09:002011-06-06T13:15:08.454+09:00Google App Engineのメンテナンスモード<div id="toc"></div> <p> </p> <p>書きかけメモ。書かれていることはすべて未検証。<br>コードはすべてJava。</p> <h4>稼働中にメンテナンスモードになったら</h4> <p>Google App Engineは時折、計画されたメンテナンスを行うことがある。もしアプリケーションが稼働中にメンテナンスに入ったらどうなるか。</p> <h4>Datastore</h4> <p>読み取り専用モードになる。メンテナンスの間、新規のデータ追加や更新はできない。書き込みに関するAPIを呼び出すと com.google.apphosting.api.ApiProxy.CapabilityDisabledException が投げられる。</p> <p>コード例などはここに書いてある。<br><a href="http://code.google.com/intl/ja-JP/appengine/docs/java/howto/maintenance.html" target="_blank">Gracefully Degrading During Scheduled Maintenance</a></p> <h4>Memcache</h4> <p>put も get も静かに失敗する。put しても何も書き込まれないし、get しても何も返ってこない。もしメンテナンス中の put や get 呼び出しで例外を出してほしいなら</p> <p class="brush:java">memcacheService.setErrorHandler(new StrictErrorHandler());</p> <p>としておくと、com.google.appengine.api.memcache.MemcacheServiceException が投げられる。</p> <h4>メンテナンスの予定を取得するAPI</h4> <p>CapabilityState から取れる。</p> <p class="brush:java">CapabilitiesService cs =<br> CapabilitiesServiceFactory.getCapabilitiesService();<br>CapabilityState state= cs.getStatus(Capability.DATASTORE);<br>Date メンテナンス日 = state.getScheduledDate();</p> <p>現在の稼働状況は、</p> <p class="brush:java">Capability capability = state.getCapability();<br>String 稼動状況 = capability.getName();</p> <p>というコードで取れる。Capability から得られるものがよくわからないが getName() で返ってくる文字列はCapabilityStatus という列挙型に入っている値のString表現だと思う。</p> <p>CapabilityStatusには DISABLED, ENABLED, SCHEDULED_MAINTENANCE, UNKNOWN の4つの値が定義されている。</p> <h4>メンテナンス予定をメールで受け取る</h4> <p>専用のGoogleグループがあってそこで通知されている。Googleグループにはメールでの通知機能もあるのでメンテナンス予定をメールで受け取ることができる。(たぶん…)<br><a href="http://groups.google.com/group/google-appengine-downtime-notify" target="_blank">Google App Engine Downtime Notify</a></p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-86186875792357480482011-02-05T13:02:00.001+09:002011-02-05T13:07:30.789+09:00GalaxySの解像度をAPIと実寸で比較 (Android)<p>Anddroid端末の解像度を取得する方法。<br>下のコードで画面のいろいろな情報を取得できる。</p> <p>DisplayMetrics mtr = new DisplayMetrics();<br>context.getWindowManager().getDefaultDisplay().getMetrics(mtr);</p> <p>GalaxySの画面の実寸サイズは長辺86mm 800px, 短辺52mm 480pxなので、長辺234.46153846153846153846153846154 dpi, 短辺 236.27906976744186046511627906977 dpi。</p> <p>GalaxyS上でAPIで得られる数値はXdpi 234.46153, Ydpi 236.27907 なので実寸と同じ。こういうのはあまり信用できない数値が返ってくることが多いけど、試してみたGalaxySでは正確な数値が得られるようだ。</p> <p><strong>参考ページ</strong></p> <p><a href="http://sakaneya.blogspot.com/2010/11/is03-dpigalaxy-s.html" target="_blank">IS03、色々な機種のDpi情報が欲しいので、こちらからはGalaxy Sの情報を晒します</a></p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.comtag:blogger.com,1999:blog-3473743908452478023.post-823167740198163882011-02-03T15:59:00.001+09:002011-02-05T13:03:44.273+09:00コリャ英和!で訳してみた<p>JBoss Cacheのドキュメントをコリャ英和! で翻訳してみた例。</p> <p>自動翻訳の後、単語のグループ化と翻訳候補の選択をしただけでここまで訳せる。まだおかしい部分はあるけど、原文も参照すれば英語が苦手でも意味はわかる程度の訳になっている。最後の文はちょっとあれだけど。</p> <p>[日本語訳]</p> <p>スタンドアローンの Java プログラムで使われるとき、される必要があるすべては、ユーザ API と Configuration 章で論じられるように、 CacheFactory と Configuration インスタンスあるいは XML ファイルを使ってキャッシュのインスタンスを作ることです。 </p> <p>同じテクニックは、アプリケーションサーバで稼働しているアプリケーションがアプリケーションサーバの配置機能に頼るよりむしろプログラム的にキャッシュを配置することを望むとき、使われることができます。 これの例は javax.servlet.ServletContextListener によってキャッシュを実装する webapp でしょう。 </p> <p>生成の後に、あなたは、スプリング、 JBoss 極小コンテナなどのようなIOCコンテナを使うことによって、あるいは、 JNDI にそれをバインドするか、あるいはただキャッシュへのスタティックな 参照を持つことによって、異なったアプリケーションコンポーネントの間であなたのキャッシュインスタンスを共有することができました。 </p> <p>もし、あなたのキャッシュを配置した後で、あなたは JMX でそれへの管理インタフェースを見せることを望んで、 JMX でプログラムの登録で項を見ます。</p> <p>[原文]<br><a title="http://docs.jboss.org/jbosscache/3.2.1.GA/userguide_en/html/deployment.html" href="http://docs.jboss.org/jbosscache/3.2.1.GA/userguide_en/html/deployment.html">http://docs.jboss.org/jbosscache/3.2.1.GA/userguide_en/html/deployment.html</a></p> <p>When used in a standalone Java program, all that needs to be done is to instantiate the cache using the CacheFactory and a Configuration instance or an XML file, as discussed in the User API and Configuration chapters. </p> <p>The same techniques can be used when an application running in an application server wishes to programatically deploy a cache rather than relying on an application server's deployment features. An example of this would be a webapp deploying a cache via a javax.servlet.ServletContextListener. </p> <p>After creation, you could share your cache instance among different application components either by using an IOC container such as Spring, JBoss Microcontainer, etc., or by binding it to JNDI, or simply holding a static reference to the cache. </p> <p>If, after deploying your cache you wish to expose a management interface to it in JMX, see the section on Programatic Registration in JMX.</p> pljphttp://www.blogger.com/profile/09492565341558269632noreply@blogger.com