Использование CORBA для объектного доступа к базе данных

Вадим Годунко <vgodunko@gmail.com>, 2007 г.

Начальная реализация сервера

В качестве примера будем разрабатывать интерфейс справочника пользователей. Каждый пользователь будет описываться одним объектом. Дополнительно потребуется объект‐фабрика для создания новых пользователей и объект‐поисковик для поиска в справочнике. Файл описания интерфейса users.idl будет выглядеть следующим образом:

module Users {

    interface User {
        attribute wstring name;
        attribute wstring surname;
    };
    typedef sequence<User> UserSequence;

    interface UserFactory {
        User create(in wstring name, in wstring surname);
    };

    interface UserFinder {
        UserSequence by_name (in wstring name);
    };

};

Далее, с помощью программы трансляции IDL в Ada — idlac — сгенерируем необходимые клиентские, серверные файлы и заготовки реализации объектов:

    idlac -c users.idl
    idlac -s users.idl
    idlac -i users.idl

Теперь всё готово для написания сервера. Объекты конечно не смогут выполнять свои функции, но сервер будет запускаться создавать необходимые объекты и ожидать запросов. В процессе совершенствования программы приведённый ниже код останется практически неизменным.

with Ada.Text_IO;

with CORBA.Object;
with CORBA.ORB;
with PortableServer.POA.Helper;
with PortableServer.POAManager;

with PolyORB.Setup.No_Tasking_Server;
--  Конфигурирование PolyORB для работы в режиме обычного многозадачного
--  сервера.

with Users.UserFactory.Impl;
with Users.UserFinder.Impl;

procedure Server is
begin
   --  Инициализация ORB.

   declare
      Argv : CORBA.ORB.Arg_List := CORBA.ORB.Command_Line_Arguments;

   begin
      CORBA.ORB.Init (CORBA.ORB.To_CORBA_String ("ORB"), Argv);
   end;

   declare
      Root_POA : PortableServer.POA.Local_Ref;

   begin
      --  Получение ссылки на корневой объектный адаптер.

      Root_POA :=
        PortableServer.POA.Helper.To_Local_Ref
         (CORBA.ORB.Resolve_Initial_References
           (CORBA.ORB.To_CORBA_String ("RootPOA")));

      --  Активация корневого объектного адаптера.

      PortableServer.POAManager.Activate
       (PortableServer.POA.Get_The_POAManager (Root_POA));

      --  Создание объекта-фабрики и объектной ссылки на этот объект.

      declare
         Ref : CORBA.Object.Ref;

      begin
         Ref :=
           PortableServer.POA.Servant_To_Reference
            (Root_POA,
             new Users.UserFactory.Impl.Object);

         --  Вывод на экран сформированной объектной ссылки.

         Ada.Text_IO.Put_Line
           ("'"
            & CORBA.To_Standard_String (CORBA.Object.Object_To_String (Ref))
            & "'");
      end;

      --  Создание объекта-поисковика и объектной ссылки на этот объект.

      declare
         Ref : CORBA.Object.Ref;

      begin
         Ref :=
           PortableServer.POA.Servant_To_Reference
            (Root_POA,
             new Users.UserFinder.Impl.Object);

         --  Вывод на экран сформированной объектной ссылки.

         Ada.Text_IO.Put_Line
           ("'"
            & CORBA.To_Standard_String (CORBA.Object.Object_To_String (Ref))
            & "'");
      end;
   end;

   --  Передача нити главной подпрограммы в ведение ORB.

   CORBA.ORB.Run;
end Server;

Собрать программу сервера можно командой:

    gnatmake server.adb `polyorb-config`

Для исключения иногда возникающих проблем с отсутствием настройки протокола группового вещания и экспериментальными функциями PolyORB содадим файл polyorb.conf следующего содержания:

##  Управление точками доступа.

[access_points]
srp=disable
soap=disable
iiop=enable
iiop.ssliop=disable
diop=disable
uipmc=disable

## Управление поддерживаемыми протоколами.

[modules]
binding_data.srp=disable
binding_data.soap=disable
binding_data.iiop=enable
binding_data.iiop.ssliop=disable
binding_data.diop=disable
binding_data.uipmc=disable

Теперь можно запустить программу и получить что‐то подобное:

$ server 
'IOR:01aaaaaa1a00000049444c3a55736572732f55736572466163746f72793a312e3000aaaa0
1000000000000005c000000010102aa0a0000003132372e302e302e3100e6841b0000002f30303
03030303031315463663966653565386331623431643032aa01000000010000001c00000001aaa
aaa010001000000000000010100020000000101010002010100'
'IOR:01aaaaaa1900000049444c3a55736572732f5573657246696e6465723a312e3000aaaaaa0
1000000000000005c000000010102aa0a0000003132372e302e302e3100e6841b0000002f30303
03030303031325463663966653565386331623431643032aa01000000010000001c00000001aaa
aaa010001000000000000010100020000000101010002010100'

Две длинные строки непонятных символов есть текстовое представление объектных ссылок. В нашем случае первая является объектной ссылкой на объект‐фабрику, а вторая — на объект‐поисковик.

po_catref

Текстовое предстваление объектных ссылок совершенно не предназначено для использования человеком, хотя и несёт в себе много полезной информации. Преобразовать объектную ссылку в понятный человеку вид может программа po_catref. В нашем примере мы получми следующую информацию:

$ po_catref 'IOR:01aaaaaa1a00000049444c3a55736572732f55736572466163746f72793a3
12e3000aaaa01000000000000005c000000010102aa0a0000003132372e302e302e3100e6841b0
000002f3030303030303031315463663966653565386331623431643032aa01000000010000001
c00000001aaaaaa010001000000000000010100020000000101010002010100'
Parsing stringified reference: IOR:01aaaaaa1a00000049444c3a55736572732f5573657
2466163746f72793a312e3000aaaa01000000000000005c000000010102aa0a0000003132372e3
02e302e3100e6841b0000002f3030303030303031315463663966653565386331623431643032a
a01000000010000001c00000001aaaaaa010001000000000000010100020000000101010002010
100

Type Id: IDL:Users/UserFactory:1.0

Found:  1 profiles in IOR

Profile number: 1
   IIOP Version: 1.2
   Host Name: localhost
   Address: 127.0.0.1
   Family: FAMILY_INET
   Port:  34022
   Object_Id: 2f3030303030303031315463663966653565386331623431643032
   Tagged components:  1
      Component #1: 
         Tag:  1
         Type: TAG_Code_Sets
            SNCS-C: 0x00010001; ISO 8859-1:1987; Latin Alphabet No. 1
            SNCS-W: 0x00010100; ISO/IEC 10646-1:1993; UCS-2, Level 1
               SCCS-W: 0x00010101; ISO/IEC 10646-1:1993; UCS-2, Level 2
               SCCS-W: 0x00010102; ISO/IEC 10646-1:1993; UCS-2, Level 3
$ po_catref 'IOR:01aaaaaa1900000049444c3a55736572732f5573657246696e6465723a312
e3000aaaaaa01000000000000005c000000010102aa0a0000003132372e302e302e3100e6841b0
000002f3030303030303031325463663966653565386331623431643032aa01000000010000001
c00000001aaaaaa010001000000000000010100020000000101010002010100'
Parsing stringified reference: IOR:01aaaaaa1900000049444c3a55736572732f5573657
246696e6465723a312e3000aaaaaa01000000000000005c000000010102aa0a0000003132372e3
02e302e3100e6841b0000002f3030303030303031325463663966653565386331623431643032a
a01000000010000001c00000001aaaaaa010001000000000000010100020000000101010002010
100

Type Id: IDL:Users/UserFinder:1.0

Found:  1 profiles in IOR

Profile number: 1
   IIOP Version: 1.2
   Host Name: localhost
   Address: 127.0.0.1
   Family: FAMILY_INET
   Port:  34022
   Object_Id: 2f3030303030303031325463663966653565386331623431643032
   Tagged components:  1
      Component #1: 
         Tag:  1
         Type: TAG_Code_Sets
            SNCS-C: 0x00010001; ISO 8859-1:1987; Latin Alphabet No. 1
            SNCS-W: 0x00010100; ISO/IEC 10646-1:1993; UCS-2, Level 1
               SCCS-W: 0x00010101; ISO/IEC 10646-1:1993; UCS-2, Level 2
               SCCS-W: 0x00010102; ISO/IEC 10646-1:1993; UCS-2, Level 3

Как видно из примеров, объектная ссылка состоит из профайлов. В наших примерах всегда будет только один профайл, но это совершенно не обязательно.

Каждый профайл в свою очередь определяет используемый для взаимодействия с объектом протокол (у нас всегда будет IIOP), адресную информацию точки доступа TCP/IP, внутренний ключ объекта и дополнительную информацию в виде тэговых компонентов.

Наибольший интерес будет представлять адресная информация: точка доступа и ключ объекта. Точка доступа описывает характеристики используемого транспортного протокола, в нашем случае это TCP/IP. Ключ объекта представляет уникальный идентификатор объекта внутри программы сервера. Именно по ключу объектна ORB различает различные объекты внутри себя.

Можно заметить, что информация точки доступа в обоих объектных ссылках одинакова, а объектные ключи — различаются. Если поэксперементировать с перезапуском сервера, то можно заметить, что каждый раз и точка доступа и объектные ключи будут меняться.

Доработка сервера

Теперь можно реализовать основные функции сервера. Для этого достаточно заполнить сгенерированные idlac‐ом шаблоны реализации. Кроме этого дополнительно добавлен пакет Globals в котором хранится список объектных ссылок всех созданных пользователей.

Далее приводится полный текст всех изменённых файлов с необходимыми комментариями.

with Ada.Containers.Vectors;
with Users.User;

package Globals is

   package User_Vectors is
     new Ada.Containers.Vectors (Positive, Users.User.Ref, Users.User."=");

   Users : User_Vectors.Vector;

end Globals;
with CORBA;
with PortableServer;

package Users.User.Impl is

   type Object is new PortableServer.Servant_Base with private;

   type Object_Ptr is access all Object'Class;

   function Get_name (Self : access Object) return CORBA.Wide_String;

   procedure Set_name (Self : access Object; To : in CORBA.Wide_String);

   function Get_surname (Self : access Object) return CORBA.Wide_String;

   procedure Set_surname (Self : access Object; To : in CORBA.Wide_String);

   function New_User (Name    : in CORBA.Wide_String;
                      Surname : in CORBA.Wide_String)
     return Object_Ptr;

private

   type Object is new PortableServer.Servant_Base with record
      Name    : CORBA.Wide_String;
      Surname : CORBA.Wide_String;
   end record;

end Users.User.Impl;
with Users.User.Skel;
--  Осуществляем подключение файла скелетона - специального кода для
--  преобразования запростов в вызовы подпрограмм.

package body Users.User.Impl is

   function Get_name (Self : access Object) return CORBA.Wide_String is
   begin
      return Self.Name;
   end Get_name;

   procedure Set_name (Self : access Object; To : in CORBA.Wide_String) is
   begin
      Self.Name := To;
   end Set_name;

   function Get_surname (Self : access Object) return CORBA.Wide_String is
   begin
      return Self.Surname;
   end Get_surname;

   procedure Set_surname (Self : access Object; To : in CORBA.Wide_String) is
   begin
      Self.Surname := To;
   end Set_surname;

   function New_User (Name    : in CORBA.Wide_String;
                      Surname : in CORBA.Wide_String)
     return Object_Ptr
   is
   begin
      return
        new Users.User.Impl.Object'
             (PortableServer.Servant_Base with
                Name => name, Surname => surname);
   end New_User;

end Users.User.Impl;
with CORBA;
with PortableServer;
with Users.User;

package Users.UserFactory.Impl is

   type Object is new PortableServer.Servant_Base with private;

   type Object_Ptr is access all Object'Class;

   function create
    (Self    : access Object;
     name    : in CORBA.Wide_String;
     surname : in CORBA.Wide_String)
       return Users.User.Ref;

private

   type Object is new PortableServer.Servant_Base with null record;

end Users.UserFactory.Impl;
with Ada.Characters.Conversions;
with Ada.Wide_Text_IO;
with CORBA.ORB;
with PortableServer.POA.Helper;
with Globals;
with Users.User.Impl;
with Users.User.Helper;

with Users.UserFactory.Skel;
--  Осуществляем подключение файла скелетона - специального кода для
--  преобразования запростов в вызовы подпрограмм.

package body Users.UserFactory.Impl is

   function create
    (Self    : access Object;
     name    : in CORBA.Wide_String;
     surname : in CORBA.Wide_String)
       return Users.User.Ref
   is
      Root_POA : PortableServer.POA.Local_Ref;
      Result   : Users.User.Ref;

   begin
      --  Получение ссылки на корневой объектный адаптер.

      Root_POA :=
        PortableServer.POA.Helper.To_Local_Ref
         (CORBA.ORB.Resolve_Initial_References
           (CORBA.ORB.To_CORBA_String ("RootPOA")));

      --  Создание объекта и объектной ссылки на этот объект.

      Result :=
        Users.User.Helper.To_Ref
         (PortableServer.POA.Servant_To_Reference
           (Root_POA,
            PortableServer.Servant
             (Users.User.Impl.New_User (name, surname))));

      --  Вывод на экран сформированной объектной ссылки.

      Ada.Wide_Text_IO.Put_Line
        ("User ("
           & CORBA.To_Wide_String (name)
           & ", "
           & CORBA.To_Wide_String (surname)
           & ") created. IOR '"
           & Ada.Characters.Conversions.To_Wide_String
              (CORBA.To_Standard_String
                (CORBA.Object.Object_To_String (Result)))
           & "'");

      --  Добавление в список пользователей.

      Globals.User_Vectors.Append (Globals.Users, Result);

      return Result;
   end create;

end Users.UserFactory.Impl;
with CORBA;
with PortableServer;

package Users.UserFinder.Impl is

   type Object is new PortableServer.Servant_Base with private;

   type Object_Ptr is access all Object'Class;

   function by_name
    (Self : access Object;
     name : in CORBA.Wide_String)
       return Users.UserSequence;

private

   type Object is new PortableServer.Servant_Base with null record;

end Users.UserFinder.Impl;
with Globals;
with Users.User;

with Users.UserFinder.Skel;
--  Осуществляем подключение файла скелетона - специального кода для
--  преобразования запростов в вызовы подпрограмм.

package body Users.UserFinder.Impl is

   function by_name
    (Self : access Object;
     name : in CORBA.Wide_String)
       return Users.UserSequence
   is
      use type CORBA.Wide_String;
      use type Globals.User_Vectors.Cursor;

      Result : Users.UserSequence;
      Cursor : Globals.User_Vectors.Cursor
        := Globals.User_Vectors.First (Globals.Users);

   begin
      while Cursor /= Globals.User_Vectors.No_Element loop
         if Globals.User_Vectors.Element (Cursor).Get_name = name then
            Users.Append
             (Result,
              Users.User.Convert_Forward.To_Forward
               (Globals.User_Vectors.Element (Cursor)));
         end if;

         Globals.User_Vectors.Next (Cursor);
      end loop;

      return Result;
   end by_name;

end Users.UserFinder.Impl;