2011年1月22日土曜日

Repo と Git の使い方 (Using Repo and Git)


Android コードで作業するには, Git と Repo の両方を使う必要が出てくるでしょう。

  • Git は, 複数のリポジトリに分散した非常に大きなプロジェクト群を扱うために設計された, オープンソースのバージョン管理システムです. Android では, ローカルでのブランチ作成, コミット, 編集等のローカル操作で Git を使用します.

  • Repo は Git 上に構築したツールです. Repo により, 多くの Git リポジトリ群の管理が容易になり, 私たちのリビジョン管理システムへアップロードが行え, Android 開発の作業手順が自動化できます. Repo は Git を置き換えることを意図したものではなく, Android 開発において, Git での作業をより容易にするためのものです. repo コマンドは, 実行可能 Python スクリプトで, 検索パスのどこにでも置くことができます.

Android ソースファイルでの作業中, ネットワーク越しの操作では Repo を使うことになるでしょう. 例えば, 一回の repo コマンドで, ローカルの作業ディレクトリへ複数のリポジトリからファイル群をダウンロードすることができます.


Git について

なぜ Git なのか?

Android プロジェクトの立ち上げ時の課題の一つは, 外部コミュニティー(趣味の一環で活動する集団から大衆市場の家電機器を製造する大企業にいたるまで)をサポートする最も良い方法を考え出すことでした. 私たちは, コンポーネント群を置き換え可能なものにしたかったですし, 良いコンポーネントは Android から離れて独自に成長していけるようにもしたかったのです. 私たちは始めに分散バージョン管理システムを選択し, そして Git へと絞り込んでいきました.

既に Git ユーザーですか?

ほとんどの状況では Repo のかわりに Git を使用することができます. また, Repo コマンドと Git コマンドを混ぜて複雑なコマンドを作ることもできます. しかし, 基本的なネットワーク越しの操作においては Repo を使うようにすれば, 作業はかなり単純になります.


作業リファレンス

下記の作業リストは, Repo および Git でのよくある作業のやり方をまとめたものです. すぐに作業を始めるための完全な情報と例については, 「Get source」を参照してください.

Repo のインストール

  $ curl http://android.git.kernel.org/repo > ~/bin/repo
  $ chmod a+x ~/bin/repo
  $ mkdir 作業ディレクトリ名
  $ cd 作業ディレクトリ名
  $ repo init-u git://android.git.kernel.org/platform/manifest.git

クライアントの同期

入手可能な全てのプロジェクトのファイルを同期する:

  $ repo sync

選択したプロジェクトのファイルを同期する:

  $ repo sync プロジェクト1 プロジェクト2 ...

なぜトピックブランチを使うのか?

変更を始めるとき, 例えばデバッグ作業や新機能の作業を始めるとき, 常にローカル作業環境でトピックブランチを開始してください.

トピックブランチは元ファイル群のコピーではなく, ある時点のコミットを指すものです. これにより, ローカルブランチの作成や, ローカルブランチ間での切替え操作が軽くなります. ブランチを使用することにより, 自分の作業を他から分離することができます. トピックブランチの使用に関する良い文章をお探しなら, 「Separating topic branches」を参照してください.

トピックブランチの作成

Repo を使用してトピックブランチを開始する:

  $ repo start ブランチ名

新しいブランチが作成されたことを確認する:

  $ repo status

トピックブランチの使用

あるプロジェクトにブランチを割り当てる:

  $ repo start ブランチ 名称 プロジェクト

ローカル作業環境に作成してあるブランチ間の移動を行う:

  $ git checkout ブランチ名

存在するブランチのリストを見る:

  $ git branch

もしくは

  $ repo branches

現在のブランチの名前の前にはアスタリスクが付きます.

注意:
不具合のせいで repo sync がローカルのトピックブランチをリセットしてしまうかもしれません. repo sync 実行後の git branch の出力が「* (no branch)」の場合は git checkout を再度実行してください.

クライアントの状態確認

ファイルの状態を見る:

  $ repo status

まだコミットされていない変更を見る:

  $ repo diff

repo diff コマンドが表示するのは, 全ての変更のうち, もし現時点でコミットを実行するとした場合に, そのコミットには含まれないもののリストです.

コミットを今実行するとした場合に, そのコミットに含まれることになる全ての変更を見るためには Git コマンド git diff が必要です. このコマンドを実行する前に, プロジェクトのディレクトリに移動しておいてください:

  $ cd ~/作業ディレクトリ/プロジェクト
  $ git diff --cached

同期による衝突からの回復

もしも repo sync が同期による衝突を示した場合,

  1. マージされていない (状態コード = U) ファイルを見て,

  2. 必要に応じて衝突部分を編集し,

  3. 該当プロジェクトのディレクトリへ移動し, 対象ファイルに対して
     git add と git commit を実行し, それからその変更を rebase
     してください. 例えば次のようにします.

       $ cd bionic
       $ git add bionic/*
       $ git commit
       $ git rebase --continue

  4. rebase が完了したら, 再度全ての同期を開始してください.

       $ repo sync bionic プロジェクト2 プロジェクト3 ... プロジェクトN

クライアント側ファイル群のクリーンアップ

変更を Gerrit へマージした後にローカル作業ディレクトリを更新する:

  $ repo sync

古いトピックブランチを安全に削除する:

  $ repo prune

クライアントの削除

クライアントを削除すると, まだレビューにあげていない全ての変更を恒久的に削除することになります. 全ての状態情報はクライアント側に保持されているので, 手元のファイルシステムからディレクトリを削除するだけで済みます.

  $ cd ~
  $ rm -rf 作業ディレクトリ名

共通タスクのスクリプト化

全てのプロジェクトに対して同じプログラムを実行するために Repo を使用することができます.

  $ repo forall [プロジェクト1 プロジェクト2 ... プロジェクトN] \
                -c 'echo $REPO_PROJECT $@' [引数1 引数2 ... 引数N]

-c 引数は /bin/sh により解釈され, それより後の引数群はシェルの位置パラメーターとして渡されます.


Repo コマンドリファレンス

Repo の使用方法は次の形式を取ります.

  repo コマンド オプション群

任意指定の要素は角括弧 [ ] で囲んで示します. Repo をインストールすれば, 下記のように実行することでコマンドに関する情報を得られます.

  repo help コマンド

init

repo init -u url [オプション群]

カレントディレクトリに Repo をインストールします. これにより, Repo ソースコードと標準 Android マニフェストファイル群を含む .repo/ ディレクトリが作成されます. また, .repo/ ディレクトリには manifest.xml が含まれ, これは .repo/manifests/ ディレクトリ内にあるマニフェストファイルのうち選択されたものへのシンボリックリンクになっています.

-u 引数には, マニフェストリポジトリの取得先の URL を指定します. 例えば次のようになります.

  $ repo init -u git://android.git.kernel.org/platform/manifest.git

リポジトリ内のマニフェストファイルを選択するには, -m オプションを使います. (マニフェスト名が選択されていない場合, デフォルトは default.xml です.) 例えば次のようになります.

  $ repo init -u git://android.git.kernel.org/platform/manifest.git -m dalkvik-plus.xml

リビジョンを指定するには, つまり, 特定のマニフェストブランチを指定するには, -b オプションを使います. 例えば次のようになります.

  $ repo init -u git://android.git.kernel.org/platform/manifest.git -b release-1.0

その他の repo init のオプションを見るには, 次のコマンドを実行します.

  $ repo help init

注意: 以降の全ての Repo コマンドについては, カレント作業ディレクトリは .repo/ ディレクトリの親ディレクトリか, もしくはその親ディレクトリのサブディレクトリのいずれかでなければなりません.

sync

repo sync [プロジェクトリスト]

新しい変更をダウンロードし, ローカル環境の作業ファイル群を更新します. repo sync が成功すると, 指定されたプロジェクト内のコードはリモートリポジトリのコードで更新されています.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo sync [プロジェクト1 プロジェクト2 ... プロジェクトN]

引数を指定せずに repo sync を実行した場合は全てのプロジェクトの同期を行います.

Repo による同期の動作

repo sync を実行したときに起こることは下記の通りです.

  1. プロジェクトが過去に一度も同期されていなければ, repo sync は
     git clone と同じ動作となります. リモートリポジトリ内の全ての
     ブランチがローカルプロジェクトディレクトリにコピーされます.

  2. プロジェクトが過去に一度でも同期されていれば, repo sync は
     次のコマンドと同じです.

       git remote update
       git rebase origin/ブランチ

     ここで「ブランチ」は, ローカルプロジェクトディレクトリに
     現在チェックアウトされているブランチです. 対象のローカル
     ブランチがリモートリポジトリ内のブランチを追跡するものでは
     ない場合, そのプロジェクトに対して同期は行われません.

     git rebase 操作がマージで衝突を起こした場合, 衝突を解決する
     ため, (例えば git rebase --continue などの) 通常の Git
     コマンドを使う必要があります.

repo sync コマンドは, .repo/ ディレクトリ内のプライベートなリポジトリも更新します.

upload

repo upload [プロジェクトリスト]

Repo は, 指定されたプロジェクトについて, ローカルブランチと, 最後の repo sync の間に更新されたリモートブランチとを比較します. Repo は, まだレビュー用にアップロードされていないブランチを一つ以上選択するよう要求してきます.

一つ以上のブランチを選択すると, 選択されたブランチの全てのコミットが, SSH コネクションを通じて Gerrit に転送されます. アップロードの認証を有効にするため, SSH キーを設定する必要があるでしょう. 自分の公開キーを Gerrit に登録するには, ユーザー設定パネルの「SSH Keys」を開いてください. パスワード無しでのアップロードを有効にするには, クライアント側で SSH エージェントの使用を検討してください.

Gerrit は, SSH サーバを通じてオブジェクトデータを受け取ると, 各コミットをチェンジに変換し, レビューワーが各コミットに対して個別にコメントできるようにします.

複数のチェックポイントコミットを一つのコミットに統合するには, repo upload を実行する前に git rebase -i を使用してください.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo upload [プロジェクト1 プロジェクト2 ... プロジェクトN]

引数を付けずに repo upload を実行すると, アップロードする変更を見つけるために全てのプロジェクトが検索されます.

編集したものをアップロード後にチェンジへと変換するためには, ローカルコミットを更新するときに git rebase -i もしくは git commit --amend といったツールを使用してください.

編集が完了したら,

  1. 更新したブランチが現在チェックアウトされているブランチで
     あることを確認し,

  2. チェンジマッチングエディターを開くために「repo upload
     --replace プロジェクト」を使用し,

  3. 一連の各コミットに対して, 角括弧内に Gerrit チェンジ ID を
     入力してください.

# ブランチ foo を置き換える
[ 3021 ] 35f2596c Refactor part of GetUploadableBranches to lookup one specific...
[ 2829 ] ec18b4ba Update proto client to support patch set replacments
[ 3022 ] c99883fe Teach 'repo upload --replace' how to add replacement patch se...
# 新しいパッチセットを追加するには角括弧内にチェンジ番号を挿入する.
# 新しいチェンジレコードを作成するには, 角括弧は空のままにする.

アップロードが完了すると, 新しいパッチセット (例: Patch Set 2, Patch Set 3, ...) がチェンジに追加されます.

diff

repo diff [プロジェクトリスト]

コミットと作業ツリー間の変更を表示します.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo diff [プロジェクト1 プロジェクト2 ... プロジェクトN]

オプション:

-h, --help はヘルプメッセージを表示して終了します.

download

repo download ターゲット チェンジ

指定したチェンジを指定したローカルディレクトリにダウンロードします. (バージョン 1.0.4 の Repo から追加されました.)

例えば, チェンジ 1241 を platform/frameworks/base ディレクトリにダウンロードするには次のようにします.

  $ repo download platform/frameworks/base 1241

repo download 経由で取得されたコミットは repo sync により取り除かれます. リモートブランチをチェックアウトすることもできます. 例: git checkout m/master

注意: 2009 年 1 月 26 日以降, Gerrit のウェブサイトで変更が可視になるのと repo download がそれを見つけられるようになるまでの間には, 約 5 分のミラーリングラグがあります. なぜなら, チェンジは実際には git://android.git.kernel.org/ ミラーからダウンロードされるからです. Gerrit は新しくアップロードされたチェンジをミラーにプッシュするので, 若干のミラーリングラグが常に発生します.

forall

repo forall [プロジェクトリスト] -c コマンド [引数 ...]

各プロジェクトに対してシェルコマンドを実行します.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

help

repo help [コマンド]

コマンドに関する詳細なヘルプを表示します.

prune

repo prune [プロジェクトリスト]

マージ済みのトピックを削除します.

プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo prune [プロジェクト1 プロジェクト2 ... プロジェクトN]

start

repo start 新しいブランチ名 [プロジェクトリスト]

開発用の新しいブランチを開始します.

新しいブランチ名」引数は, プロジェクトに加えようとしている変更の簡易説明とすべきでしょう. 分からなければ, 名前として default を使用することを検討してください.

プロジェクトリスト」には, 対象のトピックブランチに参加させるプロジェクト群を指定します. プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo start default [プロジェクト1 プロジェクト2 ... プロジェクトN]

"." は, カレント作業ディレクトリのプロジェクトを指し示すための速記法です.

status

repo status [プロジェクトリスト]

カレント作業ディレクトリの状態を表示します. プロジェクトリストは, 名称のリストか, もしくは, プロジェクトのローカルソースディレクトリのパスのリストです.

  repo status [プロジェクト1 プロジェクト2 ... プロジェクトN]

現在のブランチの状態のみを見たい場合は, repo status . を実行します.

状態情報はプロジェクトごとにリストされます. プロジェクト内の各ファイルごとに二文字のコードが使用されます.

左のカラムは, 最後のコミット状態と比較したときにインデックス (ステージされたファイル群) で何が起こっているかを大文字で示します.

次のカラムは, インデックス (ステージされたもの) と比較したときに作業ディレクトリで何が起こっているかを小文字で示します.

  文字: A
  意味: ファイルは追加されている (新規). 一番目のカラムにしか表示されない.

  文字: M もしくは m
  意味: ファイルは既に存在するが, 何らかの変更がなされている.

  文字: D もしくは d
  意味: ファイルは削除されている.

  文字: R
  意味: ファイル名が変更されている. 一番目のカラムにしか表示されない.
        新しい名前もライン上に表示される.

  文字: C
  意味: ファイルは他のファイルからコピーされたものである. 一番目のカラムに
        しか表示されない. 元ファイルも表示される.

  文字: T
  意味: ファイルのモード (実行可能であるか否か) のみが変更されている.
        一番目のカラムにしか表示されない.

  文字: U
  意味: ファイルにはマージによる衝突が含まれていて, まだマージができて
        いない. 一番目のカラムにしか表示されない.

  文字: -
  意味: ファイルの状態に変更はない. ハイフンが両方のカラムに表示されて
        いる場合, 新しいファイルだが Git により認識されていないという
        ことを意味する. このファイルに対して git add を実行すると,
        repo status は A- と表示し, そのファイルが追加されたことを示す.

例えば, appeng プロジェクト内の main.py というファイルを変更し, その変更をステージせずにいる場合, repo status は次のように表示します.

  project appeng/
  -m main.py

git add を実行して main.py に対する変更をステージすると, repo status は次のように表示します.

  project appeng/
  M- main.py

続けて, 既にステージされた main.py と, プロジェクト内の他のファイル app.yaml を編集すると, repo status は次のように表示します.

  project appeng/
  -m app.yaml
  Mm main.py


Git と Repo の早見表





用語

ステージされた変更

次回のコミットのスナップショットに含めるよう, git add でマークされた変更.

コミット

ステージされたファイル群のスナップショットと, 変更内容を説明するログメッセージを保存するため, 間隔を置いて git commit を使用します.

マニフェスト

リポジトリのリストと, それらのリポジトリのファイル群を作業ディレクトリのどこに配置するかの対応情報を含むマニフェストファイル. ファイルの同期をおこなうと, マニフェストにリストされたリポジトリに含まれるファイルは作業ディレクトリへとプルされます.

cacheflush が undefined reference で dalvik のコンパイルに失敗 (解決)

次の条件が成り立っているとき:

  (1) Android のソースコードツリーから dalvik だけ取りだして利用しようとして,
  (2) bionic libc (これは Android ソースコードツリーに含まれる) を使わず,
  (3) 使用する外部ツールチェーンが cacheflush システムコールへの C 言語インターフェースを提供していない

dalvik を WITH_JIT=true (JIT を有効にする) でコンパイルすると, dalvik/vm/compiler/codegen/arm/Assemble.c が cacheflush() を呼んでいるため, undefined reference エラーでリンクに失敗する.

こういう場合は, cacheflush システムコールへの C 言語インターフェースを自分で実装しなければならないと思われるが, アセンブリ言語で書くのは面倒くさい. ラッキーなことに, syscall() という関数を使用すると, アセンブリ言語を使わずに任意のシステムコールを呼べるようだ. 確信はないが, おそらく次のようなコードで cacheflush() を実装できると思う.

    /* For the prototype of syscall(). */
    #define _GNU_SOURCE
    #include <unistd.h>

    /* For __ARM_NR_cacheflush */
    #include <asm/unistd.h>

    int cacheflush(long start, long end, long flags)
    {
        return syscall(__ARM_NR_cacheflush, start, end, flags);
    }


Fails to compile dalvik due to undefined reference to cacheflush (solved)

When the following conditions meet:

  (1) You are trying to extract only 'dalvik' from the Android source code tree and use it, and
  (2) 'bionic libc' (which is contained in the Android source code tree) is not used, and
  (3) the external toolchain you are using does not provide the C-language interface to the 'cacheflush' system call.

an attempt to compile 'dalvik' with WITH_JIT=true (to enable Just In Time compilation) will fail in the 'link' stage due to 'undefined reference' errors because dalvik/vm/compiler/codegen/arm/Assemble.c calls cacheflush().

In this case, I think we have to implement the C-language interface to the 'cacheflush' system call ourselves, but it is cumbersome to implement it in assembly language. Luckily, it seems that a function named syscall() allows us to call an arbitrary system call without using assembly language. I'm not sure but code like the following will perhaps work as cacheflush().

    /* For the prototype of syscall(). */
    #define _GNU_SOURCE
    #include <unistd.h>

    /* For __ARM_NR_cacheflush */
    #include <asm/unistd.h>

    int cacheflush(long start, long end, long flags)
    {
        return syscall(__ARM_NR_cacheflush, start, end, flags);
    }

2011年1月10日月曜日

AndroidManifest.xml で宣言されていない Activity を動的に追加する (無理)

AndroidManifest.xml で宣言していない Activity を起動しようとしたが, ActivityNotFoundException が発生し, 「Unable to find explicit activity class ((Intent)intent).getComponent().toShortString(); have you declared this activity in your AndroidManifest.xml?」というエラーメッセージが出力されてしまった. エラーメッセージの内容は, 「AndroidManifest.xml 内で Activity を宣言してあるかい?」ということで, つまりは AndroidManifest.xml で宣言されていない Activity は起動できないよ, ということを示唆している. しかし, こっちはあくまで動的に Activity を追加したい (具体的には, 動的にインストールした OSGi バンドルに含まれている Activity を起動したい) わけなので, あらかじめ AndroidManifest.xml に宣言を書いておくなんていうことはできない. そこで, 動的に Activity を追加する方法があるかどうかを調べて見た.

Android のソースコードを読み込んでの結論は, 残念ながら「AndroidManifest.xml で宣言していない Activity を起動することはできない」である. Activity のクラス名を明示的に指定した Intent を用いて startActivity(Intent) を呼び出すと, その Activity の ActivityInfo を取得するために com.android.server.PackageManagerService クラスの getActivityInfo(ComponentName component, int flags) メソッドが呼ばれることになるが, このメソッドの実装が, 事前に AndroidManifest.xml から取得した情報しか扱えず, 情報の動的追加に対応していない. 動的追加に対応するように Android のソースコードを変更することはそんなに難しくは無い気もするが, 幾ら手元のソースコードを変更しても, 当たり前だが, 世の中に出回っている移動機内の Android の実装は変化しないので, Android のソースコードを変更するという手段を取ることはできない.


Dynamically add an Activity that is not declared in AndroidManifest.xml (impossible)

I tried to start an Activity that was not declared in AndroidManifest.xml, but an ActivityNotFoundException occurred and an error message "Unable to find explicit activity class ((Intent)intent).getComponent().toShortString(); have you declared this activity in your AndroidManifest.xml?" was shown. The error message implies that an Activity that is not declared in AndroidManifest.xml cannot be started. However, what I wanted to do was to dynamically add an Activity (to be concrete, to start an Activity that is contained in a dynamically-installed OSGi bundle), so it cannot be a choice to declare it in AndroidManifest.xml in advance. Therefore, I investigated whether there was a way to add an Activity dynamically.

The conclusion I got after reading Android's source code was, unfortunately, that an Activity that is not declared in AndroidManifest.xml cannot be started. When startActivity(Intent) is called with an Intent which explicitly specifies the class name of the target Activity, getActivityInfo(ComponentName component, int flags) method of com.android.server.PackageManagerService class is called to get the ActivityInfo of the Activity. However, the implementation of the method handles only information that has been obtained from AndroidManifest.xml in advance and has no mechanism to add information dynamically. It does not seem to be difficult to change the Android's source code to support dynamic addition, but I cannot take the option because implementations of Android in mobile terminals shipped around the world won't change however much I change the source code at my hand.

LogCat ビューの表示が更新されない (解決)

Eclipse の LogCat ビューには Android アプリのログが書き出されるが, このビューが更新されなくなることがある. そんな時は, Devices ビューを表示して, 名前の列にある対象デバイス (emulator-5554 など) をクリックして選択すれば, 表示が再開されるようになる. 分かってしまえば何てことないのだが, これを知らずに Eclipse の再インストールまでやってしまう人も世の中にはいるらしい.

LogCat view is not updated (solved)

In Eclipse, logs from Android applications go into LogCat view, but sometimes the content of the view won't be updated. In this case, opening Devices view and clicking & selecting the target device (e.g. emulator-5554) in the name column will resume the display. It is obvious once you know it, but there seems to be some people who don't know this and re-install Eclipse.

2011年1月9日日曜日

102.3 リソースの登録 (Registering Resources)

リソースとは, 画像, 静的 HTML ページ, 音声, 動画, アプレット, 等を含むファイルのことである. リソースはバンドルによる処理を必要としない. リソースは, それが置かれている場所 (通常はバンドルのコードを含んでいる JAR ファイル) から, HTTP を使用して要求元に直接転送される.

A resource is a file containing images, static HTML pages, sounds, movies, applets, etc. Resources do not require any handling from the bundle. They are transferred directly from their source--usually the JAR file that contains the code for the bundle--to the requestor using HTTP.

32 ページの「サーブレットの登録」で説明したように, リソースをサーブレットオブジェクトで処理することもできる. しかしながら, HTTP でリソース転送をおこなうとすると, 各バンドルが非常に似通ったサーブレットオブジェクトを持つことになってしまう. このような重複を避けるため, リソースは, HttpService インターフェースを介して Http サービスに直接登録できるようになっている. HttpService インターフェースには, Http サービスの URI 名前空間にリソースを登録するためのメソッド registerResources(String,String,HttpContext) が定義されている.

Resources could be handled by Servlet objects as explained in Registering Servlets on page 32. Transferring a resource over HTTP, however, would require very similar Servlet objects for each bundle. To prevent this redundancy, resources can be registered directly with the Http Service via the HttpService interface. This HttpService interface defines the registerResources(String,String,HttpContext) method for registering a resource into the Http Service URI name-space.

一番目の引数は外部エイリアスで, Http サービスへの登録に際して, リソースがその場所に置かれる. 二番目の引数は内部プレフィックスで, リソースとバンドルの名前空間の対応をとるものである. 要求を受けた時, HttpService オブジェクトは, URI から外部エイリアスを取り除いてそれを内部プレフィックスで置き換え, そうしてできた新しい名前を用いて, HttpContext オブジェクトの getResource(String) メソッドを呼ばなければならない. HttpContext オブジェクトはさらに, リソースの MIME タイプを取得したり, 要求の認証をおこなうために使用される.

The first parameter is the external alias under which the resource is registered with the Http Service. The second parameter is an internal prefix to map this resource to the bundle's name-space. When a request is received, the HttpService object must remove the external alias from the URI, replace it with the internal prefix, and call the getResource(String) method with this new name on the associated HttpContext object. The HttpContext object is further used to get the MIME type of the resource and to authenticate the request.

リソースは java.net.URL オブジェクトとして返却される. Http サービスはその URL オブジェクトを読み込み, その内容を, 当該 HTTP リクエストの発信元へ転送しなければならない.

Resources are returned as a java.net.URL object. The Http Service must read from this URL object and transfer the content to the initiator of the HTTP request.

この戻り値の型が選ばれたのは, それが java.lang.Class.getResource(String resource) メソッドの戻り値の型と同じだからである. このメソッドは, 対象となっているクラスがロードされたのと同じ場所 (大抵はバンドルの JAR ファイルのパッケージディレクトリー) から, 直接リソースを取得することができる. このメソッドにより, パッケージに含まれているバンドルからのリソース取得が, 非常に使い勝手の良いものになる.

This return type was chosen because it matches the return type of the java.lang.Class.getResource(String resource) method. This method can retrieve resources directly from the same place as the one from which the class was loaded - often a package directory in the JAR file of the bundle. This method makes it very convenient to retrieve resources from the bundle that are contained in the package.

次のコード例は, registerResources メソッドの使用方法を示すものである.

The following example code demonstrates the use of the registerResources method:

    package com.acme;
    ...
    HttpContext context = new HttpContext() {
      public boolean handleSecurity(
        HttpServletRequest request,
        HttpServletResponse response
      ) throws IOException {
        return true;
      }

      public URL getResource(String name) {
        return getClass().getResource(name);
      }

      public String getMimeType(String name) {
        return null;
      }
    };

    getHttpService().registerResources {
      "/files",
      "www",
      context
    );
    ...
    getHttpService().unregister("/files");

この例では, エイリアス /files が Http サービスに登録されている. この名前空間下にあるリソースに対する要求は, www/[名前] という内部名を伴って HttpContext オブジェクトへと転送される. この例では Class.getResource(String) メソッドが使用されている. 内部名が "/" で始まってはいないので, JAR ファイルの "com/acme/www" ディレクトリー内のリソースへとマッピングが行われる. もし内部名が "/" で始まっていたなら, パッケージ名は前置されず, JAR ファイルのルートから検索がおこなわれる. 詳細については java.lang.Class.getResource(String) を参照のこと.

This example registers the alias /files on the Http Service. Requests for resources below this name-space are transferred to the HttpContext object with an internal name of www/[name]. This example uses the Class.getResource(String) method. Because the internal name does not start with a "/", it must map to a resource in the "com/acme/www" directory of the JAR file. If the internal name did start with a "/", the package name would not have to be prefixed and the JAR file would be searched from the root. Consult the java.lang.Class.getResource(String) method for more information.

上記例の場合, http://www.acme.com/files/myfile.html に対する要求は, バンドルの JAR ファイル内の "com/acme/www/myfile.html" にマッピングされなければならない.

In the example, a request for http://www.acme.com/files/myfile.html must map to the name "com/acme/www/myfile.html" which is in the bundle's JAR file.

getResource(String) メソッドの実装をより手の込んだものにするならば, 入力名でフィルタリングをおこなって返却するリソースに制限をかけたり, (そうすることがセキュリティーの実装において許容可能であれば) 入力名をファイルシステムにマッピングしたり, といったことができる.

More sophisticated implementations of the getResource(String) method could filter the input name, restricting the resources that may be returned or map the input name onto the file system (if the security implementation of this action are acceptable).

別の方法として, 下記の registerResources の呼び出しで例示されているように, リソース登録に際してデフォルトの HttpContext オブジェクトを使用することができる.

Alternatively, the resource registration could have used a default HttpContext object, as demonstrated in the following call to registerResources:

    getHttpService().registerResources(
      "/files",
      "/com/acme/www",
      null
    );

この場合, Http サービスの実装は, createDefaultHttpContext() メソッドを呼び, その戻り値を, registerResources メソッドの HttpContext 引数として使用する. デフォルトの実装では, Bundle.getResource(String) を用いて, リソース要求をバンドルのリソースへとマッピングしなければならない. 前出の例の場合, 今度は, リソースファイル群を含む JAR ファイル内のディレクトリーのフルパス名を内部名に指定しなければならない. パッケージ名を自動で前置するという処理は行われない.

In this case, the Http Service implementation would call the createDefaultHttpContext() method and use its return value as the HttpContext argument for the registerResources method. The default implementation must map the resource request to the bundle's resource, using Bundle.getResource(String). In the case of the previous example, however, the internal name must now specify the full path to the directory containing the resource files in the JAR file. No automatic prefixing of the package name is done.

デフォルトの HttpContext オブジェクトの getMimeType(String) メソッドの実装は, null を返すことにより, Http サービスが提供するデフォルトのマッピングを利用するようにすべきである. handleSecurity(HttpServletRequest,HttpServletResponse) では, 実装依存の方法で認証機構を実装することが許されている.

The getMimeType(String) implementation of the default HttpContext object should rely on the default mapping provided by the Http Service by returning null. Its handleSecurity(HttpServletRequest,HttpServletResponse) may implement an authentication mechanism that is implementation-dependent.

2011年1月3日月曜日

102.2 サーブレットの登録 (Registering Servlets)

HttpService インターフェースを用い, javax.servlet.Servlet オブジェクトを Http サービスに登録することができる. この用途のため, HttpService インターフェースには registerServlet(String,javax.servlet.Servlet,Dictionary,HttpContext) メソッドが定義されている.

javax.servlet.Servlet objects can be registered with the Http Service by using the HttpService interface. For this purpose, the HttpService interface defines the method registerServlet(String,javax.servlet.Servlet,Dictionary,HttpContext).

例えば, Http サービスの実装がマシン www.acme.com のポート 80 で動作していて, サーブレットオブジェクトが /servlet という名前に登録されていた場合, そのサーブレットオブジェクトのサービスメソッドは, ブラウザーで次の URL が使用されたときに呼び出される.

For example, if the Http Service implementation is listening to port 80 on the machine www.acme.com and the Servlet object is registered with the name "/servlet", then the Servlet object's service method is called when the following URL is used from a web browser:

  http://www.acme.com/servlet?name=bugs

  http://www.acme.com/servlet?name=bugs

全てのサーブレットオブジェクトとリソース登録は, 同じ名前空間を共有する. 現在登録されているリソースもしくはサーブレットオブジェクトと同じ名前のもとにリソースもしくはサーブレットオブジェクトを登録しようとすると, NamespaceException が投げられる. Http サービスの名前空間の扱いについては, 36 ページの「HTTP リクエストのサーブレットおよびリソースへの関連付け」を参照のこと.

All Servlet objects and resource registration share the same name-space. If an attempt is made to register a resource or Servlet object under the same name as a currently registered resource or Servlet object, a NamespaceException is thrown. See Mapping HTTP Requests to Servlet and Resource Registrations on page 36 for more information about the handling of the Http Service name-space.

サーブレットの登録ごとに, HttpContext オブジェクトが必要となる. このオブジェクトは, リソースやメディアタイプを扱う手段や, 外部リクエストの認証を扱うメソッドを提供する. 39 ページの「認証」を参照のこと.

Each Servlet registration must be accompanied with an HttpContext object. This object provides the handling of resources, media typing, and a method to handle authentication of remote requests. See Authentication on page 39.

使い易いように, Http サービスによりデフォルトの HttpContext オブジェクトが提供され, それは createDefaultHttpContext() で取得することができる. 登録メソッドに null を渡すことでも同じ効果が得られる.

For convenience, a default HttpContext object is provided by the Http Service and can be obtained with createDefaultHttpContext(). Passing a null parameter to the registration method achieves the same effect.

サーブレットオブジェクトは ServletContext オブジェクトを要求する. このオブジェクトは, Http サービスの Java サーブレット環境にアクセスするための多くの機能を提供する. Http サービスの実装により, サーブレットオブジェクトを登録するときに使用した HttpContext オブジェクトごとに, ServletContext オブジェクトが生成される. このため, 同じ HttpContext オブジェクトを用いて登録されたサーブレットオブジェクトは, 同じ ServletContext オブジェクトを共有しなければならない.

Servlet objects require a ServletContext object. This object provides a number of functions to access the Http Service Java Servlet environment. It is created by the implementation of the Http Service for each unique HttpContext object with which a Servlet object is registered. Thus, Servlet objects registered with the same HttpContext object must also share the same ServletContext object.

サーブレットオブジェクトは, 登録されて特定の Http サービスと結合したときに, 当該 Http サービスによって初期化される. 初期化は, サーブレットオブジェクトの Servlet.init(ServletConfig) メソッドを呼ぶことにより行われる. ServletConfig パラメーターにより, サーブレットオブジェクトが登録されたときに指定された初期化パラメーターへのアクセスが提供される.

Servlet objects are initialized by the Http Service when they are registered and bound to that specific Http Service. The initialization is done by calling the Servlet object's Servlet.init(ServletConfig) method. The ServletConfig parameter provides access to the initialization parameters specified when the Servlet object was registered.

そのため, 同一のサーブレットインスタンスを再利用して他の Http サービスに登録してはならず, また, 複数の名前で登録することもできない. 登録に際しては, それぞれ独立したインスタンスが必要となる.

Therefore, the same Servlet instance must not be reused for registration with another Http Service, nor can it be registered under multiple names. Unique instances are required for each registration.

次の例は, registerServlet メソッドの使用方法を示すものである.

The following example code demonstrates the use of the registerServlet method:

    Hashtable initparams = new Hashtable();
    initparams.put("name", "value");

    Servlet myServlet = new HttpServlet() {
        String name = "<not set>";

        public void init(ServletConfig config) {
            this.name = (String)config.getInitParamter("name");
        }

        public void doGet(HttpServletRequest req, HttpServletResponse rsp)
          throws IOException {
            rsp.setContentType("text/plain");
            req.getWriter().println(this.name);
        }
    };

    getHttpService().registerServlet(
        "/servletAlias",
        myServlet,
        initparams,
        null // use default context
    );
    // myServlet が登録され, init メソッドが呼び出し済みである.
    // 外部リクエストは処理され, 当該サーブレットに転送される.
    ...
    getHttpService().unregister("/servletAlias");
    // myServlet は登録解除され, destroy メソッドが呼び出し済みである.

    Hashtable initparams = new Hashtable();
    initparams.put("name", "value");

    Servlet myServlet = new HttpServlet() {
        String name = "<not set>";

        public void init(ServletConfig config) {
            this.name = (String)config.getInitParamter("name");
        }

        public void doGet(HttpServletRequest req, HttpServletResponse rsp)
          throws IOException {
            rsp.setContentType("text/plain");
            req.getWriter().println(this.name);
        }
    };

    getHttpService().registerServlet(
        "/servletAlias",
        myServlet,
        initparams,
        null // use default context
    );
    // myServlet has been registered
    // and its init method has been called. Remote
    // requests are now handled and forwarded to
    // the servlet.
    ...
    getHttpService().unregister("/servletAlias");
    // myServlet has been unregistered and its
    // destroy method has been called

この例では, myServlet サーブレットを /servletAlias というエイリアスに登録している. 以降の http://www.acme.com/servletAlias に対するリクエストは myServlet サーブレットにマップされ, リクエストの処理のために当該サーブレットの service メソッドが呼び出される. (service メソッドは HttpServlet ベースクラスで呼ばれ, 使用されている HTTP リクエストメソッドにもとづき, doGet, doPut, doPost, doOptions, doTrace もしくは doDelete の呼び出しへとディスパッチがおこなわれる.)

This example registers the servlet, myServlet, at alias: /servletAlias. Future requests for http://www.acme.com/servletAlias maps to the servlet, myServlet, whose service method is called to process the request. (The service method is called in the HttpServlet base class and dispatched to a doGet, doPut, doPost, doOptions, doTrace or doDelete call depending on the HTTP request method used.)