Занятие 2

Создание серверов CORBA с применением основного объектного адаптера BOA (Basic Object Adapter) было рассмотрено на первом занятии. Однако мы упомянули тогда, что его место вскоре (с появлением Inprise VisiBroker 4.0) займет переносимый объектный адаптер POA, введенный в стандарт CORBA спецификацией версии 2.3.

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

Действующие лица

Для понимания архитектуры POA следует сначала ознакомиться с терминологией, которая будет использоваться на протяжении всей статьи:

  • долгоживущий объект (persistent object) — объект CORBA, способный существовать за пределами процесса, в котором он был порожден;
  • временный объект (transient object) — объект CORBA, живущий только внутри процесса, в котором он был порожден;
  • се,рвант (servant) — физический программный код, реализующий абстрактный CORBA-объект;
  • менеджер сервантов (servant manager) — объект, отвечающий за управление ассоциациями между объектами и их сервантами, а также за проверку существования объектов;
  • менеджер POA (POA manager) — объект, который управляет состоянием POA, например, может ли последний обслуживать входящие запросы или нет;
  • активатор адаптеров (adapter activator) — объект, создающий POA в ответ на запросы, полученные в адрес POA, который в тот момент не существует;
  • идентификатор объекта (object ID) — идентификатор, который ассоциирует объекты CORBA и их серванты и служит для идентификации объекта в объектном адаптере POA, куда он помещен;
  • таблица активных объектов (active object map) — таблица, которая хранит соответствия между объектами CORBA и их сервантами с помощью идентификаторов объектов;
  • инкарнация (incarnation) — процесс установления ассоциации между объектами CORBA и их сервантами;
  • эфиризация (etherialization) — процесс расторжения связи между CORBA-объектами и их сервантами.

Некоторые из этих терминов вам уже знакомы по первому занятию.

Архитектура POA

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

Обычное архитектурное решение с несколькими экземплярами POA приведено на рис.: главный POA, имя которого «RootPOA», т. е. корневой объектный адаптер. Поскольку часто серверы создают несколько различных экземпляров объектного адаптера и располагают их в виде иерархического дерева, в его основание помещается «RootPOA». Такая схема очень похожа на файловую систему. Даже программный поиск подходящего адаптера выглядит как поиск файла в UNIX — корневой адаптер «RootPOA» в этом случае обозначается символом слэша «/». Отметим, что корневой адаптер всегда имеется в системе, и через него производятся все операции по созданию новых POA.

Рис.1

Из рис.1 вы также видите, что POA хранят ссылки на активные серванты. Под этим термином подразумевается конкретный существующий в памяти код, реализующий один или несколько объектов CORBA. Если POA использует политику (см. следующий раздел «Политики POA») RETAIN, то в специальной таблице активных объектов хранится ссылка на активный сервант и один или несколько объектных идентификаторов, соответствующих этому серванту. Если POA не содержит таблицы активных объектов или в последней ссылка на нужный сервант не была обнаружена, запрос передается специальному серванту по умолчанию. По сути своей, это самый обычный сервант, но который может обработать запрос к любому объекту, зарегистрированному в его экземпляре POA. То есть все запросы, приходящие к этому POA, автоматически попадают серванту по умолчанию. Правда, для этого нужно задать политику USE_DEFAULT_SERVANT.

Часто в своей работе сервер CORBA может воспользоваться услугами менеджера сервантов. Последний в этом случае получит запрос и определит, существует ли объект, которому предназначается данный запрос, и если да, то какой сервант за него отвечает. Чтобы такая схема работала, следует установить политику USE_SERVANT_MANAGER.

Политики POA

Создавая новый экземпляр POA, программист может настроить его иначе, чем другие POA. Этого добиваются, управляя политиками (policy). Они представляют собой объекты CORBA, унаследованные от CORBA::Policy. Создавая новый POA операции POA::create_POA, нужно передать ему ссылки на настроенные объекты политик. Если какие-то объекты политик не указаны, считается, что вы передаете POA политики по умолчанию.

На всякий случай запомните, что единожды созданный POA уже не может поменять свои установленные политики, — для этого нужно создать новый POA, а старый уничтожить. Для настройки поточной модели применяется объект поточной политики ThreadPolicy, у которого могут быть следующие значения:

  • ORB_CONTROL_MODEL — брокер объектных запросов заботится о распределении конкурирующих запросов по различным потокам; данная модель принята в VisiBroker;
  • SINGLE_THREAD_MODEL — все запросы от клиентов обслуживаются по очереди одним главным потоком.

Объект ThreadPolicy создается вызовом операции POA::create_thread_policy.

Политика продолжительности жизни объектов (LifespanPolicy) может отличаться следующим образом:

  • TRANSIENT — объекты существуют короткое время и «погибают» вместе с POA, где они были зарегистрированы;
  • PERSISTENT — объекты «долгоживущие» и могут пережить процесс, в котором они были созданы.

О временных и долгоживущих объектах достаточно сказано в первом занятии. Скажем лишь, что объект LifespanPolicy создается операцией POA::create_lifespan_policy.

Объект политики уникальности идентификаторов объектов (IdUniquenessPolicy) создается операцией POA::create_id_uniqueness_policy. С ее помощью определяют, может ли один сервант иметь сразу несколько идентификаторов объектов. Такая ситуация возникает, когда один сервант реализует сразу несколько объектов CORBA. Для IdUniquenessPolicy допустимы следующие значения:

  • UNIQUE_ID — сервант может иметь только один объектный идентификатор;
  • MULTIPLE_ID — сервант может иметь один или несколько идентификаторов.

Вызвав операцию POA::create_id_assignment_policy, программист может получить объект политики присвоения идентификаторов объектов (IdAssignmentPolicy), с помощью которого определяется, кто задает идентификаторы для объектов. Вот два возможных значения:

  • USER_ID — идентификатор для объекта задается программой;
  • SYSTEM_ID — адаптер объектов POA сам генерирует идентификатор и следит за его уникальностью.

Объект ServantRetentionPolicy определяет политику удержания сервантов в таблице активных объектов:

  • RETAIN — POA сохраняет активные серванты в таблице активных объектов;
  • NON_RETAIN — активные серванты в таблице активных объектов не удерживаются.

Для создания объекта вышеуказанной политики существует операция POA::create_servant_retention_policy.

Политика обработки запросов

Политика обработки запросов (объект RequestProcessingPolicy) определяет, как запросы обрабатываются адаптером объектов:

  • USE_ACTIVE_OBJECT_MAP — в таблице активных объектов ищется идентификатор объекта, и если он не найден, то возникает исключительная ситуация отсутствия объекта OBJECT_NOT_EXIST;
  • USE_DEFAULT_SERVANT — если идентификатор объектов в таблице не найден или действует политика NON_RETAIN, то запрос перенаправляется серванту по умолчанию; в этом случае при отсутствии такого серванта возникает исключение OBJ_ADAPTER;
  • USE_SERVANT_MANAGER — если в таблице активных объектов нет искомого идентификатора или установлена политика NON_RETAIN, то к поиску подходящего серванта подключается менеджер сервантов.

Операция POA::create_request_processing_policy создает нужный объект политики. Заметим, что некоторые политики используются парами. Так, вместе с USE_ACTIVE_OBJECT_MAP должна быть установлена политика RETAIN, а к USE_DEFAULT_SERVANT надо добавить MULTIPLE_ID.

Чтобы задать возможность скрытой активации сервантов, применяется политика ImplicitActivationPolicy, экземпляр объекта которой возвращается операцией POA::create_implicit_activation_policy. У данной политики имеется всего два состояния:

  • IMPLICIT_ACTIVATION — POA может активировать серванты неявным способом; кроме того, требует установки политик SYSTEM_ID и RETAIN;
  • NO_IMPLICIT_ACTIVATION — скрытая активация не поддерживается.

Для корневого адаптера «RootPOA» системой установлены следующие политики: ORB_CTRL_MODEL, TRANSIENT, UNIQUE_ID, SYSTEM_ID, RETAIN, USE_ACTIVE_OBJECT_MAP_ONLY, IMPLICIT_ACTIVATION. Если используется VisiBroker 4.0, то к этому списку добавляется еще и специфическая для VisiBroker политика BY_POA, суть которой состоит в том, что только экземпляры POA регистрируются Smart Agent без активных объектов данного POA.

Создание серверов на основе POA

Теперь ознакомимся с тем, что получается в результате работы компиляторов IDL2JAVA и IDL2CPP из комплектов VisiBroker for Java 4.0 и VisiBroker for C++ 4.0. Для этого создадим тестовое описание на языке IDL и поместим его в файл Test.idl:

interface Test
{
   string operation(in char x);
};

Сначала запустим IDL-компилятор для Java командой

idl2java Test.idl
и посмотрим те файлы, которые получились в результате ее выполнения.

Первый файл — заглушка (stub) для соединения клиента с объектом — хранится в файле _TestStub.java. Имя заглушек создается по схеме _<имя интерфейса>.java. Сам интерфейс объекта Test описан в файле Test.java.

Скелет объекта, от которого следует реализовать сервант, именуется по схеме <имя интерфейса>POA.java (для интерфейса Test это будет TestPOA.java). Если скелет создается с использованием tie-механизма, то вместо обычного единственного класса скелета вы получите целых два: tie и operation. Первый именуется по схеме <имя интерфейса>POATie.java, а второй — <имя интерфейса>Operation.java. Компиляция нашего IDL-описания приводит к появлению файлов TestPOATie.java и TestOperations.java.

В случае с POA, как и с основным объектным адаптером BOA, для интерфейса Test будут созданы Helper- и Holder-классы в файлах TestHelper.java и TestHolder.java. Holder-класс служит «оберткой» создаваемого объекта для передачи его через ORB.

Что касается Helper-класса, то его назначение заключается в предоставлении программисту полезных методов-утилит, как, например, методов bind для связывания с объектом: ссылка на объект вызывается методом bind(). Это справедливо как для объектного адаптера BOA, так и для переносимого POA. Поэтому можно считать, что написание программы-клиента для объектов, зарегистрированных с помощью POA, ничем не отличается от аналогичных действий, с которыми мы познакомились на прошлом занятии. И тем не менее одно отличие в этих методах для разных объектных адаптеров есть: появился вариант, позволяющий найти объект, зарегистрированный в конкретном адаптере POA, для чего методу bind() в качестве одного из параметров передается полное имя POA.

Теперь обратимся к ситуации с Си++. Запустим следующую команду:
idl2cpp test.idl

В результате появятся файлы test_c.cc, test_c.hh, test_s.cc и test_s.hh. Файлы с суффиксом _s хранят описания классов серверной части приложения CORBA, а с суффиксом _c — клиентской. Расширения .cc и .hh легко меняются опциями -src_suffix и -hdr_suffix компилятора IDL2CPP.

Рассмотрим типичный случай использования POA. Поскольку два примера на разных языках заняли бы очень много места, приведем только вариант на языке Java (да простят меня любители Си++).

Обычно работа сервера на POA состоит из нескольких шагов:
  • получение ссылки на корневой адаптер POA;
  • определение политик для нового POA;
  • создание нового POA, порождаемого корневым POA;
  • создание серванта и его активация;
  • активация POA вызовом его менеджера.

Рассмотрим типичный сервер CORBA по частям. Запустившись, сервер считывает системные свойства и передает их методу инициализации брокера объектных запросов:

import org.omg.PortableServer.*;
public class POA_Server
{
...
  public static void main(String[] args)
  {
    new POA_Server ();
    try
    {
org.omg.CORBA.ORB orb = org.omg
.CORBA.ORB.init(args,
System.getProperties());

Затем сервер получает ссылку на корневой объектный адаптер «RootPOA». Это производится посредством вызова специального метода resolve_initial_references(), часто используемого для инициализации различных служб CORBA. Поскольку возвращаемая ссылка имеет тип Object, ее нужно привести к типу POA с помощью метода narrow(), который находится в Helper-классе объекта POA:

 POA poaRoot = POAHelper.narrow
(orb.resolve_initial_
references(«RootPOA»));

Теперь следует создать массив ссылок на объекты политик для нового POA и создать нужные экземпляры объектов. К примеру, мы хотим создать дочерний POA, который будет хранить долгоживущие объекты, поэтому вызываем метод create_lifespan_policy(). Обратите внимание на тот факт, что этот метод вызывается для корневого объектного адаптера:

String new_POA_name = ?TestInterface_POA?;
  org.omg.CORBA.Policy[] 
  TestInterfacePolicies = { 
  poaRoot.create_lifespan
  _policy(LifespanPolicyValue.
PERSISTENT) 
      };

Собственно создание POA заключается в вызове метода create_POA() корневого адаптера «RootPOA», которому передается имя создаваемого адаптера, ссылка на менеджер POA и массив объектов политик:

POA interface = poaRoot.create_POA
(new_POA_name,poaRoot.the_POAManager(),
TestInterfacePolicies);

Остается создать экземпляр серванта TestInterfaceImpl и передать ссылку на него методу активации серванта вместе с идентификатором объекта:

interface.activate_object_with_id(
new_POA_name.getBytes(), 
new TestInterfaceImpl());

Когда готов POA с активным сервантом, следует активировать сам POA, вызвав метод activate() менеджера объектных адаптеров POA:

poaRoot.the_POAManager().activate();

Дело в том, что новый POA игнорирует запросы, адресуемые объекту, т. е. как бы выключен. Активировав объектный адаптер, мы «открываем кран» для запросов, и они начинают «течь» через POA к соответствующим сервантам.

Заключительный штрих в инициализации сервера — переход в цикл ожидания запросов, что делается вызовом run() брокера объектных запросов:

 orb.run();
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
    }
  }
}

Это самый простой вариант сервера. Если создавать POA с сервантами по умолчанию или с менеджером сервантов, исходный текст будет отличаться. Да и вообще, вариантов построения серверов на POA — великое множество, все зависит от предполагаемой архитектуры создаваемых серверов. Поэтому, если вы хотите узнать о POA еще больше, то следует обратиться к главе 11 «The Portable Object Adapter» версии 2.3 спецификации CORBA.