リモートに存在するオブジェクトへメッセージを送る側をクライアント、メッセージを受け付けて処理を実行する側をサーバと呼んできました。しかし、オブジェクト指向プログラミングでは、オブジェクトの間に一方的なクライアント/サーバ関係にあるだけでなく、相互にメッセージを送りあうことがよくあります。手続き的なプログラミングモデルでは、「コールバック」と呼ばれているものです。オブジェクト指向プログラミングでは、コールバックはごく当たり前に記述できるのですが、CORBAでは若干の記述が必要になります。
ここでは、簡単なIDLのClockインタフェースを持つCORBAオブジェクトをサーバとして提供するプログラムと、このClockオブジェクトに対してポーリングによる時刻取得を行うクライアント、コールバックによってClockオブジェクトからメッセージを受け取るクライアントの両方を作成していきながら、コールバック/プログラミングを見ていきます。
簡単なクロックは、5つのオペレーションを定義します。また、クロックに対してコールバックされるインタフェースとしてクロックリスナーを定義します。Javaのイベント機構をちょっともじったものですが、リスナーメソッドの引数をイベント型にはせずに直接基本型を渡すようにしています。
#ifndef _Clock_IDL_ #define _Clock_IDL_ #pragma prefix "idl.learn.torutk.java_conf.gr.jp" module clock_ex1 { interface ClockListener { void timeChanged(in long hour, in long minute, in long second); }; interface Clock { long getHour(); long getMinute(); long getSecond(); void addClockListener( in ClockListener listener ); void removeClockListener( in ClockListener listener ); }; }; #endif |
このファイルを、開発ディレクトリ(D:\develop\ex3)に置きます。
d:\develop\ex3\ |
|
Clock.idl |
IDLコンパイラを実行します。
D:\develop\ex3>idl2java Clock.idl OpenORB IDL Compiler / (c) 2000-2001 Exolab.org compile : Clock.idl D:\develop\ex3> |
IDLコンパイラ実行後の開発ディレクトリの内容は以下のとおりです。generatedというディレクトリの下に、パッケージ名に対応するディレクトリが作成され、Javaのソースファイルが生成されます。
d:\develop\ex3\ | ||
Clock.idl | ||
d:\develop\ex3\generated\jp\gr\java_conf\torutk\learn\idl\clock_ex1\ | ||
Clock.java | ||
ClockHelper.java | ||
ClockHolder.java | ||
ClockOperations.java | ||
ClockPOA.java | POA継承方式で使用 | |
ClockPOATie.java | POA委譲方式で使用 | |
_ClockStub.java | ||
ClockListener.java | ||
ClockListenerHelper.java | ||
ClockListenerHolder.java | ||
ClockListenerOperations.java | ||
ClockListenerPOA.java | POA継承方式で使用 | |
ClockListenerPOATie.java | POA委譲方式で使用 | |
_ClockListenerStub.java |
IDLに記述したClockインタフェースを実現するクラス(サーバント)としてEasyClock.javaを記述します。IDLを実装するには、次の2つの方法があります。
今回は、2.のPOA委譲方式を使ってみます。
POA委譲方式で実装する場合は、IDLで定義したオペレーションのJavaマッピングであるinterface ClockOperationsを実装します。
package jp.gr.java_conf.torutk.learn.idl.clock_ex1; import org.omg.CORBA.Any; import java.util.List; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; public class EasyClock implements ClockOperations { private int hour = 0; private int minute = 0; private int second = 0; private boolean isChanged = false; private List listeners = new ArrayList(); public EasyClock() { Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { public void run() { tick(); notifyListeners(); } }, 10000, 1000); } private final synchronized void tick() { second++; if (second >= 60) { second = 0; minute++; } if (minute >= 60) { minute = 0; hour++; } if (hour >= 24) { hour = 0; } setChanged(); } private final void setChanged() { isChanged = true; } private final void clearChanged() { isChanged = false; } private final boolean hasChanged() { return isChanged; } private final void notifyListeners() { if (!hasChanged()) { return; } Object[] listenersToNotify = null; synchronized(this) { listenersToNotify = listeners.toArray(); } for (int i=0; i<listenersToNotify.length; i++) { ClockListener listener = (ClockListener)listenersToNotify[i]; listener.clockChanged(getHour(), getMinute(), getSecond()); } } public final synchronized int getSecond() { return second; } public final synchronized int getMinute() { return minute; } public final synchronized int getHour() { return hour; } public void addClockListener(ClockListener listener) { listeners.add(listener); } public void removeClockListener(ClockListener listener) { listeners.remove(listener); } }// EasyClock |
エラー処理はほとんど行っていません。本当は、リスナーへ通知するときに既にリスナーが存在していない場合のエラー処理、removeClockListenerでリスナーが存在しないときの通知等が入っていません。
EasyClock.javaは、開発ディレクトリの下に直接パッケージ名に対応するディレクトリを作成して置くことにします。
d:\develop\ex3\ | ||
Clock.idl | ||
d:\develop\ex3\jp\gr\java_conf\torutk\learn\idl\clock_ex1\ | ||
EasyClock.java | IDLを実装するクラス | |
d:\develop\ex3\generated\jp\gr\java_conf\torutk\learn\idl\clock_ex1\ | ||
: : |
IDLコンパイラで生成されたファイル |
IDLに記述したClockListenerインタフェースを実現するクラス(サーバント)としてEasyClockListener.javaを記述します。今回は、POAの継承方式(Inheritance-Based Implementation)を使用します。
package jp.gr.java_conf.torutk.learn.idl.clock_ex1; public class EasyClockListener extends ClockListenerPOA { public void clockChanged( int hour, int minute, int second ) { System.out.println( "ServerClock=" + hour + ":" + minute + ":" + second ); } }// EasyClockListener |
EasyClockListener.javaは、開発ディレクトリの下に直接パッケージ名に対応するディレクトリを作成して置くことにします。
d:\develop\ex3\ | ||
Clock.idl | ||
d:\develop\ex3\jp\gr\java_conf\torutk\learn\idl\clock_ex1\ | ||
EasyClock.java | IDLインタフェース Clockを実装するクラス | |
EasyClockLisntener.java | IDLインタフェース ClockListenerを実装するクラス | |
d:\develop\ex3\generated\jp\gr\java_conf\torutk\learn\idl\clock_ex1\ | ||
: : |
IDLコンパイラで生成されたファイル |
Clockオブジェクトを起動するサーバプログラムです。今回のサーバント(EasyClock)はPOA委譲方式です。
package jp.gr.java_conf.torutk.learn.idl.clock_ex1; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContextExt; import org.omg.CosNaming.NamingContextExtHelper; import org.omg.PortableServer.POA; import org.omg.PortableServer.POAHelper; import org.omg.PortableServer.POAManager; /** * Server.java * * Created: Sun Oct 28 01:41:59 2001 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ public class Server { public static void main(String[] args) { // 1. ORBの起動 ORB orb = ORB.init(args, null); // 2. RootPOAの取得 org.omg.CORBA.Object objPoa = null; POA rootPoa = null; try { objPoa = orb.resolve_initial_references("RootPOA"); rootPoa = POAHelper.narrow(objPoa); } catch (org.omg.CORBA.ORBPackage.InvalidName e) { System.out.println("RootPOAの名前が正しくありません"); System.exit(1); } if (rootPoa == null) { System.out.println("POAのオブジェクト参照が取得できません"); System.exit(1); } // 3. サーバントの作成 // POATie(委譲による実装方式)の場合は、サーバントの他に、 // POATieオブジェクトを作成する。 EasyClock clock = new EasyClock(); ClockPOATie clockTie = new ClockPOATie(clock); // 4. サーバントをPOAに結び付け org.omg.CORBA.Object clockRef = null; try { rootPoa.activate_object(clockTie); clockRef = rootPoa.servant_to_reference(clockTie); } catch (Exception e) { System.out.println("Clockオブジェクト作成でエラー"); e.printStackTrace(); System.exit(1); } // 5. ネーミングサービスの取得 // ルートネーミングコンテクストオブジェクトの取得 org.omg.CORBA.Object objNs = null; try { objNs = orb.resolve_initial_references("NameService"); } catch (org.omg.CORBA.ORBPackage.InvalidName e) { System.out.println("ネーミングサービスの名前が正しくありません"); e.printStackTrace(); System.exit(1); } NamingContextExt rootNameContext = NamingContextExtHelper.narrow(objNs); if (rootNameContext == null) { System.out.println("ルートネーミングコンテクストが空です"); System.exit(1); } // 6. ネーミングサービスへサーバントのCORBAリファレンス登録 // リモートクロックオブジェクトのネームコンポーネント作成 NameComponent[] clockNameComponent = null; try { clockNameComponent = rootNameContext.to_name("TheClock.Object"); rootNameContext.rebind(clockNameComponent, clockRef); System.out.println("Registered clock object to name service"); } catch (Exception e) { System.out.println("クロックオブジェクトのネーミングサービスへの" + "登録でエラーが発生しました"); e.printStackTrace(); System.exit(1); } // 7. POAマネージャの活性化 POAManager poaManager = rootPoa.the_POAManager(); try { poaManager.activate(); System.out.println("activated POA Manager"); } catch (org.omg.PortableServer.POAManagerPackage.AdapterInactive e) { System.out.println("POAマネージャの活性化に失敗しました"); e.printStackTrace(); System.exit(1); } // 8. クライアントからの要求の待ち合わせ System.out.println("Waiting request from client"); try { orb.run(); } catch (Exception e) { e.printStackTrace(); } } } // Server |
参考までに、Clockオブジェクトを呼び出すクライアントのコードを載せます。
package jp.gr.java_conf.torutk.learn.idl.clock_ex1; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContextExt; import org.omg.CosNaming.NamingContextExtHelper; import org.omg.CosNaming.NamingContextPackage.NotFound; /** * Client.java * * Created: Sat Nov 10 02:09:34 2001 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ public class Client { public static void main(String[] args) { Clock clock = null; try { // 1. ORBの起動 ORB orb = ORB.init(args, null); // 2. ネーミングサービスの取得 org.omg.CORBA.Object objNs = orb.resolve_initial_references("NameService"); NamingContextExt ncRef = NamingContextExtHelper.narrow(objNs); // 3. ネーミングサービスからリモートオブジェクト // リファレンスを獲得 String name = "TheClock.Object"; clock = ClockHelper.narrow(ncRef.resolve_str(name)); } catch (NotFound e) { System.out.println("Remote Object 'TheClock' not found."); System.out.println("why = " + e.why.value()); System.out.println("rest_of_name length = " + e.rest_of_name.length); for (int i=0; i<e.rest_of_name.length; i++) { System.out.println("rest_of_name[" + i + "] = {" + e.rest_of_name[i].id + ", " + e.rest_of_name[i].kind + "}" ); } System.exit(1); } catch (Exception e) { e.printStackTrace(); System.exit(1); } try { // 4. リモートオブジェクトのメソッドを呼び出し int hour = clock.getHour(); int minute = clock.getMinute(); int second = clock.getSecond(); System.out.println("Server Clock = " + hour + ":" + minute + ":" + second); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } // Client |
コールバックを受け取るクライアントプログラムの記述です。リモートオブジェクトからメッセージ要求を受け付け、サーバントを呼び出すためには、POAが必要です。そこで、POAの取得、サーバントの作成、POAマネージャの活性化を行います。
package jp.gr.java_conf.torutk.learn.idl.clock_ex1; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContextExt; import org.omg.CosNaming.NamingContextExtHelper; import org.omg.CosNaming.NamingContextPackage.NotFound; import org.omg.PortableServer.POA; import org.omg.PortableServer.POAManager; import org.omg.PortableServer.POAHelper; /** * ListenerClient.java * * Created: Sat Nov 10 02:09:34 2001 * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ public class ListenerClient { public static void main(String[] args) { Clock clock = null; ORB orb = null; try { // ORBの起動 orb = ORB.init(args, null); // ネーミングサービスの取得 org.omg.CORBA.Object objNs = orb.resolve_initial_references("NameService"); NamingContextExt ncRef = NamingContextExtHelper.narrow(objNs); // リモートオブジェクトリファレンス獲得 String name = "TheClock.Object"; clock = ClockHelper.narrow(ncRef.resolve_str(name)); } catch (NotFound e) { System.out.println("Remote Object 'TheClock' not found."); System.out.println("why = " + e.why.value()); System.out.println("rest_of_name length = " + e.rest_of_name.length); for (int i=0; i<e.rest_of_name.length; i++) { System.out.println("rest_of_name[" + i + "] = {" + e.rest_of_name[i].id + ", " + e.rest_of_name[i].kind + "}" ); } System.exit(1); } catch (Exception e) { e.printStackTrace(); System.exit(1); } // RootPOAの取得 org.omg.CORBA.Object objPoa = null; POA rootPoa = null; try { objPoa = orb.resolve_initial_references("RootPOA"); rootPoa = POAHelper.narrow(objPoa); } catch (org.omg.CORBA.ORBPackage.InvalidName e) { System.out.println("RootPOAの名前が正しくありません"); System.exit(1); } if (rootPoa == null) { System.out.println("POAのオブジェクト参照が取得できません"); System.exit(1); } // コールバックオブジェクトのサーバント作成とPOAへの結びつけ org.omg.CORBA.Object listenerRef = null; try { EasyClockListener listener = new EasyClockListener(); rootPoa.activate_object(listener); listenerRef = rootPoa.servant_to_reference(listener); } catch (Exception e) { e.printStackTrace(); System.exit(1); } // POAマネージャの取得と活性化 POAManager poaManager = rootPoa.the_POAManager(); try { poaManager.activate(); System.out.println("activated POA Manager"); } catch (org.omg.PortableServer.POAManagerPackage.AdapterInactive e) { System.out.println("POAマネージャの活性化に失敗しました"); e.printStackTrace(); System.exit(1); } // コールバックオブジェクトのCORBAリファレンスをリモートオブジェクトへ // 渡す。 try { ClockListener ref = ClockListenerHelper.narrow(listenerRef); clock.addClockListener(ref); // コールバックのリクエストを待つイベントループ orb.run(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } // ListenerClient |
ネーミングサービスの起動については、使用しているCORBA製品によって異なります。OpenORBの提供するネーミングサービスについては、OpenORB TipsページのNaming Service項を参照。
サーバプログラムを実行するには、先にネーミングサービスを実行している必要があります。サーバプログラムを実行する際に、先に起動したネーミングサービスのオブジェクトリファレンス情報をコマンドラインオプションで指定します。
ネーミングサービスの指定 | -ORBInitRef NameService=<オブジェクトURL> |
D:\develop\ex3\classes>java jp.gr.java_conf.torutk.learn.idl.clock_ex1.Server -ORBInitRef NameService=corbaloc:iiop:1.2@nameserver:5555/NameService Registered clock object to name service activated POA Manager Waiting request from client |
ネーミングサービスにサーバを問合せて参照を取得後、普通にメソッドを呼び出す。
D:\develop\ex3\classes>java jp.gr.java_conf.torutk.learn.idl.clock_ex1.Cli ent -ORBInitRef NameService=corbaloc:iiop:1.2@nameserver:5555/NameService Server Clock = 0:1:26 |
D:\develop\ex3\classes>java jp.gr.java_conf.torutk.learn.idl.clock_ex1.Listener Client -ORBInitRef NameService=corbaloc:iiop:1.2@nameserver:5555/NameService ServerClock=0:2:58 ServerClock=0:2:59 ServerClock=0:3:0 ServerClock=0:3:1 |