オブジェクト指向設計を行っていると、CORBAオブジェクトのオペレーション(メソッド)を呼び出す際に、そのオペレーションの戻り値や引数にオブジェクトを使用したくなります。ところが、CORBAでは、引数や戻り値に指定できるのは、CORBA基本型と、IDLで定義した型だけです。IDLでinterface定義した型を引数や戻り値に指定した場合は、CORBAオブジェクト参照情報が渡ります。しかし、オブジェクト参照ではなく、オブジェクト自体を「コピー」してそっくり渡したい場合があります。いってみれば、参照渡しに対する値渡し(Passing Object By Value)です。元々CORBAにはstruct(構造体)が定義されていますが、継承ができない、メソッドを持たない、といったマイナス点が目立ちます。そこでCORBA2.3から追加されたのがOBV(Object By Value)です。
なお、valuetypeで定義された型はCORBAオブジェクトではないので、リモートからそのメソッドを呼ぶことはできません。
単純な例ですが、ある予定(計画)を管理するCORBAオブジェクトSchedulerを考えます。Schedulerオブジェクトはただ1つだけ予定を持っており、オペレーションgetPlan()が呼ばれると予定を返すものとします。予定には、開始時間と終了時間があります。また、時間は(時、分、秒)で表すものとします。この予定はCORBAオブジェクトではなく、受け取ったマシン側にローカルに持ちたいとします。
オペレーションの戻り値は1つしか使用できません。今回の問題のように、複数のデータを返却するには次の方法があります。
void getPlan(out long startHour, out long startMinute, out long startSecond, out long endHour, out long endMinute, out long endSecond);
struct Plan { long startHour; long startMinute; long startSecond; long endHour; long endMinute; long endSecond; }; : Plan getPlan();
1.の方法は、outパラメータを使うためコーディングが面倒になること、引数の数が多い(しかも皆同じ型)ため複雑で間違いやすいメソッドになることが問題として挙げられます。
2.の方法は、1.に比べるとスマートですが、CORBA構造体はメソッドを持たないclassとしてマッピングされます。このclassは、フィールドが全てpublicであり、しかもfinal classとなっています。そのため、オブジェクト指向プログラミングとの相性があまりよくない問題が挙げられます。
ローカルでは、オブジェクトへメッセージを送る(メソッドを起動する)際は、その引数や戻り値にオブジェクトを使用することができます。これを、CORBAのリモートオペレーション呼び出しでも使用し、しかもそのオブジェクトをリモート参照ではなく、オブジェクトをそのまま相手にコピーして渡すには、そのオブジェクトをvaluetypeとしてIDLに定義します。
#ifndef _schedule_IDL_ #define _schedule_IDL_ module schedule { valuetype Time { private long hour; private long minute; private long second; long getHour(); long getMinute(); long getSecond(); }; valuetype Plan { private Time start; private Time end; }; interface Scheduler { Plan getPlan(); }; }; #endif |
時刻(時分秒)情報をvaluetype宣言。 Timeクラスとして定義する。 Timeクラスの属性を定義。 属性にはアクセス修飾子としてpublicか privateを指定できる。 Timeクラスのオペレーションを定義。 予定(開始時刻,終了時刻)情報をvaluetype宣言。 Planクラスとして定義する。 Planクラスの属性を定義。属性にはvaluetypeを 入れ子に指定してもよい。 また、オペレーションは定義しなくてもよい。 valuetypeはCORBAオブジェクトではないので、 CORBAオブジェクトのオペレーションの引数 または戻り値に指定される。 |
valuetype TimeがどのようにJavaにマッピングされるかを見てみます。コードはOpenORB 1.2.0を使った場合のものです。ただしコメント、インデント等は修正しています。
package schedule; public abstract class Time implements org.omg.CORBA.portable.StreamableValue { protected int hour; protected int minute; protected int second; public abstract int getHour(); public abstract int getMinute(); public abstract int getSecond(); static final String[] _ids_list = { "IDL:schedule/Time:1.0" }; public String [] _truncatable_ids() { return _ids_list; } public void _read(org.omg.CORBA.portable.InputStream is) { hour = is.read_long(); minute = is.read_long(); second = is.read_long(); } public void _write(org.omg.CORBA.portable.OutputStream os) { os.write_long(hour); os.write_long(minute); os.write_long(second); } public org.omg.CORBA.TypeCode _type() { return schedule.TimeHelper.type(); } } |
valuetypeの属性に指定したものはフィールドに、オペレーションはabstractメソッドにマッピングされています。また、シリアライズ用のメソッド_read、_writeといったものが定義されているのが分かります。OBVでは、オブジェクトの属性をオペレーション呼び出し時にシリアライズして転送します。受け取り手では、シリアライズされてきたデータからオブジェクトを生成する必要があります。このときシリアライズ対象となるのが、valuetypeで属性として定義されているものです。
valuetypeで定義された型は、Javaのabstract classにマッピングされます。したがって、これを継承した実装クラスを定義することになります。実装クラスでは、オペレーションをメソッドとして実装する他、private指定した属性に対するアクセッサメソッドも実装します。
package schedule; public class TimeImpl extends Time { public TimeImpl() { hour = 0; minute = 0; second = 0; } public TimeImpl(int h, int m, int s) { hour = h; minute = m; second = s; } public int getHour() { return hour; } public int getMinute() { return minute; } public int getSecond() { return second; } public String toString() { return "[TimeImpl]{hour=" + hour + ",minute=" + minute + ",second=" + second + "}"; } } |
valuetypeのTimeから生成された Timeクラスを継承する。 コンストラクタは必要に応じて適宜定義する。 IDLで定義したメソッドの実装を記述する。 toString()は定義しておくと便利です。 |
同様にvaluetypeで定義したPersonを実装します。
package schedule; public class PlanImpl extends Plan { public PlanImpl() { start = new TimeImpl(1, 1, 1); end = new TimeImpl(2, 2, 2); } public PlanImpl(Time aStart, Time anEnd) { start = aStart; end = anEnd; } public Time getStartTime() { return start; } public Time getEndTime() { return end; } public String toString() { return ("[PlanImpl]{start=" + start + ":end=" + end + "}"); } } |
valuetypeでは、オブジェクトの状態をシリアライズして転送することになります。オブジェクトの受け取り手では、シリアライズされたデータからオブジェクトを生成する必要があります。CORBAでは、この生成のコードを記述してあげる必要があります。簡単なデフォルトのファクトリを使用する場合は、
valueファクトリのクラス名に、<タイプ名>DefalutFactory と命名すれば、ORBへの登録は不要です。それ以外の名前を付けた場合には、ORBオブジェクトに登録しておく必要があります。
package schedule; import org.omg.CORBA.portable.ValueFactory; import org.omg.CORBA_2_3.portable.InputStream; import java.io.Serializable; public class TimeDefaultFactory implements ValueFactory { public Serializable read_value(InputStream in) { Time time = new TimeImpl(); in.read_value((Serializable)time); return time; } } |
上記コードでは、オブジェクトを最初にnewしておき、そこへシリアライズされてきたデータを当てこんでいます。read_valueメソッドは、ORBから呼ばれます。
package schedule; import org.omg.CORBA.portable.ValueFactory; import org.omg.CORBA_2_3.portable.InputStream; import java.io.Serializable; public class PlanDefaultFactory implements ValueFactory { public Serializable read_value(InputStream in) { Plan plan = new PlanImpl(); in.read_value((Serializable)plan); return plan; } } |
IDLのインタフェースの実装は、サーバントとして作成します。ここでは、簡単なPOA継承によって実装しています。
package schedule; public class SimpleScheduler extends SchedulerPOA { private Plan myPlan; public SimpleScheduler() { Time start = new TimeImpl(12, 34, 56); Time end = new TimeImpl(23,45,6); myPlan = new PlanImpl(start, end); } public Plan getPlan() { return myPlan; } } |
package schedule; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContext; import org.omg.CosNaming.NamingContextHelper; import org.omg.PortableServer.POA; import org.omg.PortableServer.POAHelper; import org.omg.PortableServer.POAManager; /** * Server.java * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ public class Server { public static void main(String[] args) { ORB orb = ORB.init(args, null); // 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); } // Servantの作成 SimpleScheduler scheduler = new SimpleScheduler(); org.omg.CORBA.Object schedulerRef = null; try { rootPoa.activate_object(scheduler); schedulerRef = rootPoa.servant_to_reference(scheduler); } catch (Exception e) { System.out.println("Schedulerオブジェクト作成でエラー"); e.printStackTrace(); System.exit(1); } // NamingServiceへの登録 // ルートネーミングコンテクストオブジェクトの取得 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); } NamingContext rootNameContext = NamingContextHelper.narrow(objNs); if (rootNameContext == null) { System.out.println("ルートネーミングコンテクストが空です"); System.exit(1); } // Schedulerオブジェクトのネームコンポーネント作成 NameComponent[] schedulerNameComponent = new NameComponent[1]; schedulerNameComponent[0] = new NameComponent("TheScheduler", "Object"); try { rootNameContext.rebind(schedulerNameComponent, schedulerRef); System.out.println("Registered scheduler object to name service"); } catch (Exception e) { System.out.println("Schedulerオブジェクトのネーミングサービスへの登録で エラーが発生しました"); 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); } // クライアントからの要求の待ち合わせ System.out.println("Waiting request from client"); try { orb.run(); } catch (Exception e) { e.printStackTrace(); } } } // Server |
package schedule; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContext; import org.omg.CosNaming.NamingContextHelper; /** * Client.java * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ public class Client { public static void main(String[] args) { Scheduler scheduler = null; try { ORB orb = ORB.init(args, null); System.out.println("ORB initialized."); org.omg.CORBA.Object objNs = orb.resolve_initial_references("NameService"); NamingContext rootNameContext = NamingContextHelper.narrow(objNs); System.out.println("NamingContext aquired."); NameComponent nc = new NameComponent("TheScheduler", "Object"); NameComponent[] path = {nc}; scheduler = SchedulerHelper.narrow(rootNameContext.resolve(path)); System.out.println("TheScheduler Object aquired."); } catch (Exception e) { e.printStackTrace(); System.exit(1); } try { Plan plan = scheduler.getPlan(); System.out.println("Server Plan = " + plan); Person person = scheduler.getPerson(); System.out.println("Server Person = " + person); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } // Client |