レベル: 中級 Nathan A. Good, Senior Information Engineer, Freelance Developer
2009年 02月 24日 今日のアプリケーションは、多くの人が既に使用しているインターフェースである IM (Instant Messaging: インスタント・メッセージ) を利用しています。アプリケーションに IM を統合している理由は、IM によるインターフェースが使いやすく、多くの人がそのインターフェースに慣れていて、しかも既に利用しているからです。IM アプリケーションは多くのモバイル・プラットフォームでも利用可能であり、ユーザーは IM を利用することでモバイル機器からアプリケーションとインターフェースを取れるようになります。
IM (Instant Messaging: インスタント・メッセージ) は、既存のアプリケーションや新規のアプリケーションに対するインターフェースを作成するための素晴らしい手段として利用することができます。多くの人が IM を使用しており、また IM を使用する人達は通常、コンピューターの電源が入っている間は常に、IM アプリケーション (例えば AIM (AOL Instant Messenger) など) を立ち上げています。IM クライアントは PC に搭載されているのみならず、PDA (Personal Digital Assistant) や携帯電話などのモバイル機器にも搭載されています。
アプリケーションのインターフェースとして、ユーザーが IM を使ってアプリケーションに接続できるようなインターフェースを作成すると、大規模な既存のインフラを利用したネットワーク通信が可能になります。これにより、既に IM の ID を持ち、IM クライアントを立ち上げているユーザーにとっては、アプリケーションにアクセスするための便利な手段が提供されることになります。
この記事では、AOL のクライアント SDK (software development kit) ライブラリーを利用してユーザーからのコマンドを受け付け、そのコマンドを処理してユーザーに結果を返す Java™ アプリケーションの作成方法を説明します。その傍らで、アプリケーションを拡張可能かつ保守の容易なものにするためのデザイン・パターンをいくつか紹介します。
システム要件
Eclipse IDE (Integrated Development Environment) V3.4 以降がコンピューターにインストールされていると、この記事で説明する例を読み進めていくうえで効果的です。またここで紹介する例を理解して実行するためには Java プログラミング言語についても十分理解している必要があります。
AIM API の紹介
IM サービスは数多くありますが、この記事では AOL の AIM サービスに焦点を絞ります。AOL が無料で提供している SDK を利用すると、AOL のサービスに接続してそのサービスを利用するアプリケーションを作成することができます (「参考文献」を参照)。
この SDK をダウンロードするためには、ライブラリーの使用に関する AOL の使用条件に同意する必要があります。また API で使用する開発者キーも取得する必要があるため、オンラインの指示手順に従ってカスタムのクライアント・キーを取得します。皆さんは実質的に、自動化されたカスタムの AIM クライアントを作成することになるからです。
ZIP または tar.gz ファイル (accsdk_macosx_univ_1_6_8.tar.gz) の SDK をダウンロードしたら、後で思い出せる場所にそのファイルを保存します。このアーカイブ・ファイルには、後ほど必要となる JAR (Java Archive) ファイルとその他のライブラリー・ファイルが含まれています。また Java API (application program interface) の JavaDoc も含まれているため、このアーカイブ・ファイルを解凍し、ダウンロードした API のバージョンに該当する JavaDoc を読むとよいかもしれません。Eclipse ではライブラリー・ファイルをアーカイブ・ファイルの内部からインポートすることができるため、必ずしもアーカイブ・ファイルを解凍する必要はありません。
AOL の AIM SDK には、Microsoft® Windows® 版、Mac OS® X 版、そして Linux® 版が用意されているため、先に進む前に、ご使用のオペレーティング・システムに対応した SDK をダウンロードしていることを確認してください。あるオペレーティング・システム上で開発したアプリケーションを、別のオペレーティング・システムにデプロイすることを考えている場合には、両方のオペレーティング・システム用のライブラリーが必要になります。これらの他にも AIM と通信するための (Java コードで作成された) オープンソースのライブラリーがあります。私が AOL の SDK を使用することにした理由は、私が AOL の AIM サービスを利用しているためです。
AOL のサイトには AIM の API を理解するための参考となる例がいくつか用意されています。
AIM の Bot ID を取得する
AIM サービスにログオンしてアプリケーションのテストをするには、AIM の ID が必要です。またアプリケーションで使用する ID について、AIM のサイトで「Bot My Screenname」と呼ばれるプロセス (「参考文献」を参照) を行い、Bot ID を作成しておくことをお勧めします。Bot ID を作成しておくと個人用の AIM スクリーン・ネームを使ってアプリケーションと通信できるため、アプリケーションのテストが容易になります。
プロジェクトを作成する
この記事で使用するための Java プロジェクトがない場合には、新しい Java プロジェクトを追加する必要があります。「ファイル (File)」 > 「新規 (New)」 の順に選択して、「新規 Java プロジェクト (New Java Project)」ウィザードを開き、新しい Java プロジェクトを追加するための手順に従います。既存の Java プロジェクト (既に作成しているアプリケーションなど) を使う場合には、このステップを省略することができます。
Java API をインポートし、インストールする
アプリケーションに IM 機能を追加する際に使用する Java クラスと Java インターフェースは、アーカイブ・ファイルの dist/release/lib ディレクトリー内の accwrap.jar ファイルの中にあります。最終的に AccEvents インターフェースを実装するクラスを作成する前に、これらのライブラリーをインポートしてクラスパスに追加する必要があります。
 | ロギング
私はアプリケーションの中でのロギング用に log4j を使いました。依存関係を減らすためには java.util.logging 名前空間の Java Logging を使うこともできます。「参考文献」には、さまざまなロギングの実装を挙げてあります。System.out.println() ではなくロギングによるソリューションを使うように、強くお勧めします。
|
|
私は Java アプリケーションを作成する場合、通常は Java プロジェクトの中に lib ディレクトリーを作成し、すべての JAR ファイルを lib ディレクトリー内のディレクトリーに配置します。そしてディレクトリーの名前には、このディレクトリーの中に含まれるライブラリーの名前とバージョンを含めるようにします。例えば、私は Apache の log4J ロギング・ユーティリティーを使ってデバッグ用のメッセージをログに記録したので、ワークスペースに対する JAR ファイルの相対パスは、lib/apache-log4j-1.2.15/log4j-1.2.15.jar になります。
AOL SDK 用の Java API と依存関係をインポートする場合には、私はその規則を破ることにしました。通常とは異なり、JAR ファイルを他のファイルと一緒に、プロジェクトのルート・ディレクトリーの中にある dist/release/lib ディレクトリーの中に配置しました。このようにしたのは、accwrap.jar をクラスパスに指定しておけば、accwrap.jar がどこにあっても JRE (Java Runtime Environment) がアプリケーションを実行するときに見つけられるからです。その一方で JRE は現在の作業ディレクトリーの中でライブラリー・ファイルを探します。どうしても特定のディレクトリーにファイルを配置したい場合には、アプリケーションの実行構成をそのディレクトリーの場所に更新すれば問題はなくなりますが、この方法だとチーム環境ではカスタムの実行構成が面倒になる可能性があるため、少し問題が残ります。
「パッケージ・エクスプローラー (Package Explorer)」に表示されるライブラリー・ファイルが多すぎて邪魔になる場合には、ビュー・フィルターを設定して表示されないようにすることもできます。
accwrap.jar ファイルをプロジェクトのビルド・パスに追加します。それには「パッケージ・エクスプローラー (Package Explorer)」で、この JAR ファイルを選択し、コンテキスト・メニューを表示させ、「ビルド・パス (Build Path)」 > 「ビルド・パスに追加 (Add to Build Path)」の順に選択します。あるいはプロジェクトの設定からビルド・パスを構成し、accwrap.jar ファイルを追加することもできます。
AIM の Java API を使う
この時点で、accwrap.jar ファイルとライブラリー・ファイルがインポートされた Java プロジェクトができているはずです。
AIM に対するアプリケーションのインターフェースの作成を始めるには、AccEvents インターフェースを実装するクラスを追加する必要があります。この記事のサンプル・クラスには main() 関数も含まれていますが、それが必須というわけではありません。
このクラスを追加する最も簡単な方法は、「ファイル (File)」 > 「新規 (New)」 > 「クラス (Class)」の順に選択して、「新規 Java クラス (New Java Class)」ウィザードを使用する方法です。このウィザードの画面 (図 1) で、パッケージ名とクラス名を入力します。新しいインターフェースを追加するために、「インターフェース (Interfaces)」フィールドの隣にある「追加 (Add)」ボタンをクリックし、インターフェースの名前として AccEvents の名前を入力します。ビルド・パスに accwrap.jar ファイルを追加してあるので、このインターフェースがリストの中に表示されるはずです。
図 1. AccEvents を実装するクラスを追加する
このクラスを追加すると、リスト 1 のようになります。簡単のため、このリストではインターフェースに関するメソッドの多くを省略していますが、これはこの記事ではすべてのメソッドを使用するわけではないためです。
リスト 1. 新しい実装クラス
package com.nathanagood.shopper;
import com.aol.acc.*;
public class MySuperShopperBot implements AccEvents {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void OnImReceived(AccSession session, AccImSession imSession,
AccParticipant participant, AccIm im) {
// TODO: Add implementation
}
public void OnStateChange(AccSession session, AccSessionState state,
AccResult result) {
// TODO: Add implementation
}
/* Many other methods for AccEvents snipped... */
}
|
コードを追加する
main() メソッド (リスト 2) はこのクラスの新しいインスタンスを作成し、signOn() メソッドを呼び出します。
リスト 2. main() メソッド
public static void main(final String[] args) {
logger.info("Starting My Super Shopper bot...");
MySuperShopperBot bot = new MySuperShopperBot();
try {
bot.signOn();
} catch (AccException ae) {
logger.error("An error occurred while trying to sign on.", ae);
}
logger.info("Shutting down bot...");
}
|
このコンストラクターをリスト 3 に示します。このコンストラクターはファクトリー (MessageHandlerFactory) を使って MessageHandler のインスタンスを作成し、そのインスタンスを messageHandler 変数に割り当てます。このオブジェクトは後で AccEvents.OnIMReceived() メソッドの実装が実際の動作を行う際に使われます。
リスト 3. MySuperShopperBot コンストラクター
public MySuperShopperBot() {
messageHandler = MessageHandlerFactory.createMessageHandler();
}
|
signOn() メソッドをリスト 4 に示します。このメソッドは AccSession オブジェクトの新しいインスタンスを作成して設定し、受信メッセージをリッスンするためのループを開始します。
リスト 4. signOn() メソッド
public void signOn() throws AccException {
session = new AccSession();
session.setEventListener(this);
AccClientInfo info = session.getClientInfo();
info.setDescription(AIM_KEY);
session.setIdentity(AIM_USERNAME);
session.setPrefsHook(new MySuperShopperPrefs());
session.signOn(AIM_PASSWORD);
while (isRunning) {
try {
AccSession.pump(50);
} catch (Exception e) {
logger.error("Exception occurred while handling message", e);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
logger.warn("Thread was interrupted", e);
}
}
info = null;
session = null;
System.gc();
System.runFinalization();
}
|
signOn() メソッドはログオンする前に、いくつかのプロパティーを設定します。SDK をダウンロードした際に取得した開発者キーは setDescription() の中で設定されます。AIM スクリーン・ネームは setIdentity() メソッドによって設定され、またパスワードは signOn() の引数として提供されます。
ループの中のコード (AccSession.pump() と Thread.sleep()) によって、サービスの実行とメッセージのリッスン動作が継続的に行われます。
この記事の例では、2 つのメソッドにのみコードを実装しています。その 1 つは OnIMReceived() メソッドです (リスト 5)。このメソッドは受信される IM メッセージのプレーンテキストのストリング値を取得します。次に messageHandler に対して、この IM メッセージを処理できるかどうかを問い合わせ、処理できる場合には handleMessage() メソッドを呼び出します。この方法は、if/else 文を使ってフローを制御する大がかりなメソッドを作成する方法に代わる方法です。ここで使用しているいくつかのパターンについては「デザイン・パターン」セクションで詳しく説明します。
リスト 5. OnIMReceived() メソッド
public void OnImReceived(AccSession session, AccImSession imSession,
AccParticipant participant, AccIm im) {
String message;
try {
message = im.getConvertedText(PLAIN_TEXT);
/* Provide a way to cleanly shut down the client */
if (message.equals(SHUTDOWN_COMMAND)) {
session.signOff();
} else {
if (messageHandler.canHandle(message)) {
String response = messageHandler.handle(message,
participant.getName());
im.setText(response);
imSession.sendIm(im);
}
}
} catch (AccException e) {
logger.error("Error receiving message.", e);
}
}
|
最後に、テストの際に IM サービスをクリーンに終了するために使用できる値をハードコーディングしました。具体的にはリスト 6 に示すように、OnStateChange() メソッドの中にコードを追加して isRunning ステータスを false に設定し、アプリケーションがクリーンにループを終了するようにしました。
リスト 6. OnStateChange() メソッド
public void OnStateChange(AccSession session, AccSessionState state,
AccResult result) {
if (state == AccSessionState.Offline) {
isRunning = false;
}
}
|
デザイン・パターン
この記事ではいくつかのデザイン・パターンを説明します。これらのデザイン・パターンを使用して作成されるアプリケーションは、拡張性があり、保守も容易で、AccEvents の実装コードと他のアプリケーションのコードとを密結合しなくても既存のアプリケーションと連動するように容易に変更することができます。
第 1 のパターンは MessageHandler インターフェースによって表されるストラテジー・パターンです。このパターンを使用すると、どの実装もそれぞれに特有の方法でメッセージを処理することができます。ストラテジー・パターンのバリエーションとして canHandle() メソッドを提供する方法があり、この方法を利用すればメッセージを適切に処理できるかどうかを実装自体が示すことができます。従って、このバリエーションを利用する場合には、ファクトリーはメッセージに対する適切な実装を選択する際に、その実装がメッセージを処理できるかどうかの情報を持っている必要がなくなります。
DelegatingMessageHandler クラスではデコレーター・パターンが使われています (リスト 7)。このクラスはフィルター・チェーンに似た構成体の中で、コンストラクターの引数として MessageHandler オブジェクトのリストを取ります。このクラスは、このクラス自体が実装している MessageHandler インターフェースの handle() メソッドの中で、このクラスに登録されたハンドラーに対して繰り返し処理を行い、受信されたメッセージを処理することができるハンドラーを探します。候補が見つかると、そのハンドラーにメッセージを渡します。
リスト 7. DelegatingMessageHandler クラス
package com.nathanagood.shopper.handlers;
import java.util.List;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
public final class DelegatingMessageHandler implements MessageHandler {
private final static Logger logger = LogManager
.getLogger(DelegatingMessageHandler.class);
private List<MessageHandler> handlers;
public DelegatingMessageHandler(final List<MessageHandler> handlers) {
this.handlers = handlers;
}
/**
*
* @param handler
*/
public void registerHandler(MessageHandler handler)
{
handlers.add(handler);
}
public boolean canHandle(final String message) {
return (handlers != null);
}
public String handle(final String message, final String user) {
String result = "I don't understand that...";
for (MessageHandler handler : handlers) {
if (handler.canHandle(message)) {
logger.debug("Processing message \"" + message
+ "\" from user \"" + user + "\" with handler \""
+ handler.getClass().getCanonicalName() + "\"");
result = handler.handle(message, user);
logger.debug("Returning result \"" + result + "\"");
break;
}
}
return result;
}
}
|
MessageHandlerFactory.createMessageFactory() メソッドではファクトリー・パターンが使われています (リスト 8)。このメソッドは DelegatingMessageHandler のインスタンスを作成して初期化し、そして返します。実装の作成にファクトリーを使うことによって、呼び出し側は初期化に関する詳細を知る必要がなくなります。
リスト8. MessageHandlerFactory クラス
package com.nathanagood.shopper.handlers;
import java.util.ArrayList;
import java.util.List;
/**
* Factory for creating a {@link MessageHandler}.
* @author Nathan A. Good
*/
public class MessageHandlerFactory {
/**
* Creates a {@link MessageHandler} implementation.
* @return MessageHandler.
*/
public static MessageHandler createMessageHandler() {
List<MessageHandler> handlers = new ArrayList<MessageHandler>();
handlers.add(new ShoppingListMessageHandler());
// handlers.add(new EchoMessageHandler()); // useful for testing...
DelegatingMessageHandler handler = new DelegatingMessageHandler(handlers);
return handler;
}
}
|
OnIMReceived() メソッドの中にコードを実装する代わりにこれらのパターンを使うと、いくつかのメリットがあります。例えば少し変更するだけで、何らかのパーシスタンスを持つ状態パターンを導入し、会話の状態を追跡することができます。
メッセージに応答する
実装クラスを使ってメッセージを処理したら、ユーザーに対して何らかのメッセージで応答する必要があります。メッセージの処理と応答を (この記事での例のように) 同期して行う場合には、sendIm() メソッドを使って応答します。
リスト 9. OnImReceived でユーザーに応答する
public void OnImReceived(AccSession session, AccImSession imSession,
AccParticipant participant, AccIm im) {
String message;
try {
message = im.getConvertedText(PLAIN_TEXT);
if (message.equals("goodbye")) {
session.signOff();
} else {
if (messageHandler.canHandle(message)) {
String response = messageHandler.handle(message,
participant.getName());
im.setText(response);
imSession.sendIm(im);
}
}
} catch (AccException e) {
logger.error("Error receiving message.", e);
}
}
|
メッセージの処理を非同期に行う場合には、アプリケーションの応答対象であるユーザーのスクリーン・ネームを使って AccImSession オブジェクトの新しいインスタンスを作成する必要があります (リスト 10)。screenname 変数はユーザーのスクリーン・ネームであり、message は IM の内容のストリングです。非同期のメッセージ処理はメッセージ処理に時間がかかる場合に便利です。
リスト 10. 新しい IM メッセージを作成して送信する
AccImSession imSession = session.createImSession(screenname, AccImSessionType.Im);
imSession.sendIm(session.createIm(message, "text/plain"));
|
MessageHandler の実装を追加する
MessageHandler の実装である ShoppingListMessageHandler (リスト 11) を利用すると、MySuperShopper は買い物リストの項目をデータベースに追加することができ、後でそれらのデータをモバイル機器から取得することができます。
リスト 11. ShoppingListMessageHandler クラス
package com.nathanagood.shopper.handlers;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.nathanagood.shopper.persistence.ShoppingListItem;
import com.nathanagood.shopper.persistence.ShoppingListManager;
public class ShoppingListMessageHandler implements MessageHandler {
private static final Logger logger = LogManager.getLogger(ShoppingListManager.class);
private static final Pattern addCommandPattern = Pattern
.compile("^\\s*add +([\\d]+)[ -]+(.*)$");
public boolean canHandle(final String message) {
return addCommandPattern.matcher(message).matches();
}
public String handle(final String message, final String user) {
String result = "An error occurred while adding your item.";
try {
if ( addCommandPattern.matcher(message).matches() ) {
ShoppingListManager manager = new ShoppingListManager();
manager.addShoppingListItem(user, parse(message));
result = "Successfully added item.";
}
} catch (Exception e) {
logger.error("Error while handling item.", e);
}
return result;
}
private ShoppingListItem parse(final String value) {
int quantity = 0;
String description = "";
Matcher match = addCommandPattern.matcher(value);
if ( match.find() ) {
quantity = Integer.parseInt(match.group(1));
description = match.group(2);
}
logger.debug("Parsed item with \"" + quantity + "\" number of \"" +
description + "\"");
return new ShoppingListItem(quantity, description);
}
}
|
ShoppingListManager クラスは、この記事からダウンロードできるコードの中に含まれています。この例で重要なのは具体的な実装ではなく、ShoppingListManager クラスがユーザーの買い物リストの項目を永続化するために何かをする、という点です。
アプリケーションを実行する
アプリケーションを実行する前に、個人用のスクリーン・ネームを使って AIM にログインし、アプリケーションのスクリーン・ネーム、buddy を追加します。こうすることによって、アプリケーションが起動したときにアプリケーションがオンラインになるのを確認できるので、メッセージを送信してアプリケーションのテストを行います。
すべての実装クラスを追加したら、「プロジェクト (Project)」 > 「実行 (Run)」の順に選択し、アプリケーションを実行することができます。プロジェクトが起動すると、IM クライアントの中にアプリケーションがオンラインで表示されるはずです。
トラブルシューティング
私は最初ライブラリー・ファイルをプロジェクトのベース・ディレクトリーに配置しなかったため、「Exception in thread "main" java.lang.UnsatisfiedLinkError」というエラーを受信してしまいました。
Windows の場合には、ネイティブ・ライブラリー (DLL (dynamic-link library) など) を必ず作業ディレクトリーに置くことで、この問題を解決することができます。しかし私の Mac では、DYLD_LIBRARY_PATH という名前の環境変数をプロジェクトのワークスペースの場所に設定する必要がありました。
図 2. 構成に環境変数を追加する
キーに対して記述が適切に設定されていないと「com.aol.acc.AccException: IAccClientInfo_SetDescription」というエラーが発生します。
私は AOL のサイトから直接キーをコピーしたので、私の AIM_KEY 定数の値は「My Super Shopper (Key:my1XzlXXXXXXXXXX)」でした。この記事からダウンロードできるコードの中には単純にメッセージをエコー・バックする EchoMessageHandler を追加してあります。また EchoMessageHandler は受信メッセージとスクリーン・ネームをログに記録する機能もあるため、テストの際に便利です。
まとめ
AIM SDK を利用するとカスタムの Java クライアントを作成することができます。このクライアントを Java アプリケーションで利用すると、ユーザーからのメッセージの受け付けとユーザーへの応答の両方を IM を使って行うことができます。また、この記事で紹介したパターンを使用すると、保守や拡張が容易になるようにアプリケーションを拡張することができます。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample code | os-blackberry2-IBMRssReader_src.zip | 112KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Nathan A. Good はミネソタ州の Twin Cities 地域に住んでいます。彼は職業としてソフトウェア開発、ソフトウェア・アーキテクチャー、そしてシステム管理を行っています。ソフトウェアを作成していないときには PC やサーバーの組み立てを行ったり新技術に関して読んだり作業をして楽しみ、また彼の友人達をオープンソース・ソフトウェアに移行させようと説得することを楽しんでいます。彼は多くの本や記事を執筆または共同執筆しており、そうした中には『Professional Red Hat Enterprise Linux 3』、『Regular Expression Recipes: A Problem-Solution Approach』、『Foundations of PEAR: Rapid PHP Development』などがあります。 |
記事の評価
|