Вечная жизнь. Воскрешение объекта

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

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

Но как можно понять какой объект необходимо воскресить? Где взять информацию для идентификации объектов на стороне сервера?

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

Первым делом необходимо настроить объектный адаптер соответствующим образом, а именно, задать для политики IdAssignmentPolicy значение USER_ID, а для политики ImplicitActivationPolicy значение NO_IMPLICIT_ACTIVATION (последнее необходимо в связи с тем, что используемое по умолчанию значение IMPLICIT_ACTIVATION несовместимо со значением USER_ID для политики IdAssignmentPolicy).

Код сервера примет следующий вид:

with Ada.Text_IO;

with CORBA.Object;
with CORBA.ORB;
with CORBA.Policy;
with PortableServer.IdAssignmentPolicy;
with PortableServer.ImplicitActivationPolicy;
with PortableServer.LifespanPolicy;
with PortableServer.POA.Helper;
with PortableServer.POAManager;

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

with Globals;
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
         Policies      : CORBA.Policy.PolicyList;
         Lifespan      : PortableServer.LifespanPolicy.Ref
           := PortableServer.POA.Create_Lifespan_Policy
               (PortableServer.PERSISTENT);
         Id_Assignment : PortableServer.IdAssignmentPolicy.Ref
           := PortableServer.POA.Create_Id_Assignment_Policy
               (PortableServer.USER_ID);
         Activation    : PortableServer.ImplicitActivationPolicy.Ref
           := PortableServer.POA.Create_Implicit_Activation_Policy
               (PortableServer.NO_IMPLICIT_ACTIVATION);

      begin
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Lifespan));
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Id_Assignment));
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Activation));

         Globals.User_POA :=
           PortableServer.POA.Local_Ref
            (PortableServer.POA.Create_POA
              (Root_POA,
               CORBA.To_CORBA_String ("UserPOA"),
               PortableServer.POA.Get_The_POAManager (Root_POA),
               Policies)); 
      end;

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

      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;

Теперь необходимо изменить код фабрики объектов в части создания объектных ссылок и регистрации сервантов объектов. Один вызов подпрограммы PortableServer.POA.Servant_To_Reference предётся заменить двумя вызовами: подпрограммы PortableServer.POA.Activate_Object_With_Id для активации объекта с задаваемым пользователем идентификатором; и подпрограммы PortableServer.POA.Create_Reference_With_Id для создания объектной ссылки.

Код фабрики будет выглядеть следующим образом:

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
      --  Создание объекта и объектной ссылки на этот объект.

      PortableServer.POA.Activate_Object_With_Id
       (Globals.User_POA,
        PortableServer.String_To_ObjectId
         (Integer'Image
           (Integer (Globals.User_Vectors.Length (Globals.Users)) + 1)),
        PortableServer.Servant (Users.User.Impl.New_User (name, surname)));

      Result :=
        Users.User.Helper.To_Ref
         (PortableServer.POA.Create_Reference_With_Id
           (Globals.User_POA,
            PortableServer.String_To_ObjectId
             (Integer'Image
               (Integer (Globals.User_Vectors.Length (Globals.Users)) + 1)),
            CORBA.To_CORBA_String (Users.User.Repository_Id)));

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

      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;

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

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

Менеджер сервантов это специальный локальный CORBA объект, закреплённый за объектным адаптером, к которому обращается объектный адаптер в случае невозможности найти сервант объекта. Напишем сначала соответствующий файл CORBA IDL:

import ::PortableServer;

local interface Activator : PortableServer::ServantActivator {};

сгенерируем все необходимые файлы:

idlac  activator.idl `polyorb-config --idls`

и создадим файлы спецификации и реализации пакета реализации менеджера сервантов. Файл спецификации будет выглядеть следующим образом:

with PortableServer.ServantActivator.Impl;

package Activator.Impl is

   type Object is new PortableServer.ServantActivator.Impl.Object with private;

   type Object_Ptr is access all Object'Class;

   function Incarnate
    (Self    : access Object;
     Oid     : in PortableServer.ObjectId;
     Adapter : in PortableServer.POA_Forward.Ref)
      return PortableServer.Servant;

private

   type Object is
     new PortableServer.ServantActivator.Impl.Object with null record;

   function Is_A
    (Self            : access Object;
     Logical_Type_Id : PolyORB.Std.String)
       return Boolean;

end Activator.Impl;

Файл реализации будет содержать заглушку реализации:

with Ada.Wide_Text_IO;

with CORBA;
with PortableServer.ServantActivator;
with PortableServer.ServantManager;

package body Activator.Impl is

   function Incarnate
    (Self    : access Object;
     Oid     : in PortableServer.ObjectId;
     Adapter : in PortableServer.POA_Forward.Ref)
      return PortableServer.Servant
   is
   begin
      Ada.Wide_Text_IO.Put_Line ("Incarnate");

      return null;
   end Incarnate;

   function Is_A
    (Self            : access Object;
     Logical_Type_Id : PolyORB.Std.String)
       return Boolean
   is
   begin
      return CORBA.Is_Equivalent
        (Logical_Type_Id,
         Activator.Repository_Id)
        or else CORBA.Is_Equivalent
          (Logical_Type_Id,
           "IDL:omg.org/CORBA/Object:1.0")
        or else CORBA.Is_Equivalent
           (Logical_Type_Id,
         PortableServer.ServantActivator.Repository_Id)
        or else CORBA.Is_Equivalent
           (Logical_Type_Id,
         PortableServer.ServantManager.Repository_Id);
   end Is_A;

end Activator.Impl;

Единственная переопределённая диспетчеризируемая подпрограмма Incarnate как раз и будет вызываться объектным адаптером при отсутствии уже созданного объекта. По спецификации Incarnate должна вернуть указатель на экземпляр зарегистрированного в объектном адаптере серванта объекта или null, если по каким либо причинам создать объект не удалось. В параметре Oid будет передан объектный ключ объекта, который необходимо создать.

Теперь пора настроить объектный адаптер. Для настройки необходимо задать значение USE_SERVANT_MANAGER для политики RequestProcessingPolicy и зарегистрировать менеджер сервантов вызовом PortableServer.POA.Set_Servant_Manager:

with Ada.Text_IO;

with CORBA.Object;
with CORBA.ORB;
with CORBA.Policy;
with PortableServer.IdAssignmentPolicy;
with PortableServer.ImplicitActivationPolicy;
with PortableServer.LifespanPolicy;
with PortableServer.POA.Helper;
with PortableServer.POAManager;
with PortableServer.RequestProcessingPolicy;
with PortableServer.ServantManager;

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

with Activator.Impl;
with Globals;
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
         Policies      : CORBA.Policy.PolicyList;
         Lifespan      : PortableServer.LifespanPolicy.Ref
           := PortableServer.POA.Create_Lifespan_Policy
               (PortableServer.PERSISTENT);
         Id_Assignment : PortableServer.IdAssignmentPolicy.Ref
           := PortableServer.POA.Create_Id_Assignment_Policy
               (PortableServer.USER_ID);
         Activation    : PortableServer.ImplicitActivationPolicy.Ref
           := PortableServer.POA.Create_Implicit_Activation_Policy
               (PortableServer.NO_IMPLICIT_ACTIVATION);
         Processing    : PortableServer.RequestProcessingPolicy.Ref
           := PortableServer.POA.Create_Request_Processing_Policy
               (PortableServer.USE_SERVANT_MANAGER);

      begin
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Lifespan));
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Id_Assignment));
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Activation));
         CORBA.Policy.IDL_SEQUENCE_Policy.Append
          (Policies, CORBA.Policy.Ref (Processing));

         Globals.User_POA :=
           PortableServer.POA.Local_Ref
            (PortableServer.POA.Create_POA
              (Root_POA,
               CORBA.To_CORBA_String ("UserPOA"),
               PortableServer.POA.Get_The_POAManager (Root_POA),
               Policies)); 

         --  Создание активатора и его регистрация в объектном адаптере.

         declare
            Ref : Activator.Local_Ref;

         begin
            Activator.Set (Ref, new Activator.Impl.Object);  
            PortableServer.POA.Set_Servant_Manager (Globals.User_POA, Ref);
         end;
      end;

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

      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;

Теперь можно собрать сервер, запустить его и, прочитав ссылку на объект‐пользователя из файла, убедиться, что при попытке чтения значений атрибутов объекта на стороне сервера будет отображена строка «Incarnate», что означает, что объектный адаптер выполняет запрос на воскрешения не активного объекта.

Осталось только научить наш менеджер сервантов создавать сервант и активировать его в объектном адаптере. Для активиции осуществляется вызов уже использовавшейся ранее подпрограммы PortableServer.POA.Activate_Object_With_Id, однако в отличии от кода фабрики нет необходимости создавать объектную ссылку — она и так уже известна клиенту.

После модификации реализация менеджера сервантов будет выглядеть следующим образом:

with CORBA;
with PortableServer.POA;
with PortableServer.ServantActivator;
with PortableServer.ServantManager;

with Globals;
with Users.User.Impl;

package body Activator.Impl is

   function Incarnate
    (Self    : access Object;
     Oid     : in PortableServer.ObjectId;
     Adapter : in PortableServer.POA_Forward.Ref)
      return PortableServer.Servant
   is
      Result : Users.User.Impl.Object_Ptr;

   begin
      Result
        := Users.User.Impl.Load_User
            (Integer'Value (PortableServer.ObjectId_To_String (Oid)));

      PortableServer.POA.Activate_Object_With_Id
       (Globals.User_POA,
        Oid,
        PortableServer.Servant (Result));

      return PortableServer.Servant (Result);
   end Incarnate;

   function Is_A
    (Self            : access Object;
     Logical_Type_Id : PolyORB.Std.String)
       return Boolean
   is
   begin
      return CORBA.Is_Equivalent
        (Logical_Type_Id,
         Activator.Repository_Id)
        or else CORBA.Is_Equivalent
          (Logical_Type_Id,
           "IDL:omg.org/CORBA/Object:1.0")
        or else CORBA.Is_Equivalent
           (Logical_Type_Id,
         PortableServer.ServantActivator.Repository_Id)
        or else CORBA.Is_Equivalent
           (Logical_Type_Id,
         PortableServer.ServantManager.Repository_Id);
   end Is_A;

end Activator.Impl;

Поскольку теперь необходимо ещё реально сохранять и восстанавливать состояние объектов, были измененый также и файлы реализации фабрики реализации объекта. Поскольку способ хранения выходит за пределы обсуждаемой темы, ниже приводятся все изменённые файлы без особых комментариев.

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 (Id      : in Integer;
                      Name    : in CORBA.Wide_String;
                      Surname : in CORBA.Wide_String)
     return Object_Ptr;

   function Load_User (Id : in Integer) return Object_Ptr;

private

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

end Users.User.Impl;
with Ada.Strings.Wide_Unbounded.Wide_Text_IO;
with Ada.Wide_Text_IO;

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

package body Users.User.Impl is

   procedure Save (Self : in Object'Class);

   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;
      Save (Self.all);
   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;
      Save (Self.all);
   end Set_surname;

   function New_User (Id      : in Integer;
                      Name    : in CORBA.Wide_String;
                      Surname : in CORBA.Wide_String)
     return Object_Ptr
   is
      Result : Object_Ptr
        := new Users.User.Impl.Object'
                (PortableServer.Servant_Base with
                   Id => Id, Name => Name, Surname => Surname);
   begin
      Save (Result.all);

      return Result;
   end New_User;

   function Load_User (Id : in Integer) return Object_Ptr is
      File    : Ada.Wide_Text_IO.File_Type;
      Image   : constant String := Integer'Image (Id);
      Name    : Ada.Strings.Wide_Unbounded.Unbounded_Wide_String;
      Surname : Ada.Strings.Wide_Unbounded.Unbounded_Wide_String;

   begin
      Ada.Wide_Text_IO.Open
       (File, Ada.Wide_Text_IO.In_File, Image (Image'First + 1 .. Image'Last));
      Ada.Strings.Wide_Unbounded.Wide_Text_IO.Get_Line (File, Name);
      Ada.Strings.Wide_Unbounded.Wide_Text_IO.Get_Line (File, Surname);
      Ada.Wide_Text_IO.Close (File);

      return
        new Object'(PortableServer.Servant_Base with
                      Id      => Id,
                      Name    =>
                        CORBA.To_CORBA_Wide_String
                         (Ada.Strings.Wide_Unbounded.To_Wide_String (Name)),
                      Surname =>
                        CORBA.To_CORBA_Wide_String
                         (Ada.Strings.Wide_Unbounded.To_Wide_String
                           (Surname)));
   end Load_User;

   procedure Save (Self : in Object'Class) is
      File  : Ada.Wide_Text_IO.File_Type;
      Image : constant String := Integer'Image (Self.Id);

   begin
      Ada.Wide_Text_IO.Create
       (File,
        Ada.Wide_Text_IO.Out_File,
        Image (Image'First + 1 .. Image'Last));
      Ada.Wide_Text_IO.Put_Line
       (File, CORBA.To_Standard_Wide_String (Self.Name));
      Ada.Wide_Text_IO.Put_Line
       (File, CORBA.To_Standard_Wide_String (Self.Surname));
      Ada.Wide_Text_IO.Close (File);
   end Save;

end Users.User.Impl;
with Ada.Characters.Conversions;
with Ada.Integer_Wide_Text_IO;
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;
      Id       : Integer := 1;

   begin
      --  Загрузка идентификатора из файла. Если файл не существует - не
      --  страшно, начинаем нумерацию с 1.

      declare
         File : Ada.Wide_Text_IO.File_Type;

      begin
         Ada.Wide_Text_IO.Open (File, Ada.Wide_Text_IO.In_File, "id");
         Ada.Integer_Wide_Text_IO.Get (File, Id);
         Ada.Wide_Text_IO.Close (File);

      exception   
         when Ada.Wide_Text_IO.Name_Error =>
            null;
      end;

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

      PortableServer.POA.Activate_Object_With_Id
       (Globals.User_POA,
        PortableServer.String_To_ObjectId (Integer'Image (Id)),
        PortableServer.Servant
         (Users.User.Impl.New_User (Id, name, surname)));

      Result :=
        Users.User.Helper.To_Ref
         (PortableServer.POA.Create_Reference_With_Id
           (Globals.User_POA,
            PortableServer.String_To_ObjectId (Integer'Image (Id)),
            CORBA.To_CORBA_String (Users.User.Repository_Id)));

      --  Сохранение идентификатора в файл.

      declare
         File : Ada.Wide_Text_IO.File_Type;

      begin
         Id := Id + 1;

         Ada.Wide_Text_IO.Create (File, Ada.Wide_Text_IO.Out_File, "id");
         Ada.Integer_Wide_Text_IO.Put (File, Id);
         Ada.Wide_Text_IO.Close (File);
      end;

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

      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;

Собрав сервер, запустив клиента и сервер можно убедиться, что все функции клиента теперь полностью работают, даже несмотря на возможные перебои в работе сервера.