Вечная жизнь. Воскрешение объекта
Вадим Годунко <>, 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;
with Globals;
with Users.UserFactory.Impl;
with Users.UserFinder.Impl;
procedure Server is
begin
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;
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
и создадим файлы спецификации и реализации пакета реализации менеджера сервантов. Файл спецификации будет выглядеть следующим образом:
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;
with Activator.Impl;
with Globals;
with Users.UserFactory.Impl;
with Users.UserFinder.Impl;
procedure Server is
begin
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;
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
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;
Собрав сервер, запустив клиента и сервер можно убедиться, что все функции клиента теперь полностью работают, даже несмотря на возможные перебои в работе сервера.
|