Кросс-платформенные и многозвенные технологии

Технология CORBA

Разбить на страницы
Показывать лекцию целиком

Основы технологии CORBA

CORBA (Common Object Request Broker Architecture) - объектно-ориентированная технология создания распределенных приложений. Технология основана на использовании брокера объектных запросов (Object Request Broker, ORB) для прозрачной отправки и получения объектами запросов в распределенном окружении. Технология позволяет строить приложения из распределенных объектов, реализованных на различных языках программирования. Стандарт CORBA разработан Object Management Group (OMG).

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

В данном разделе приводится краткий обзор CORBA в том виде, как ее описание дается в спецификации OMG версии 3.0. Требования этого документа могут в различной степени удовлетворяться фактическими реализациями брокеров объектных запросов. На Рис. 2.1 изображен запрос, посылаемый клиентом реализации объекта. Клиент - это сущность, которая хочет выполнить операцию с объектом, а Реализация - это совокупность кода и данных, которые в действительности реализуют объект.

(рис 2.1) Клиент посылает запрос реализации объекта

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

На Рис. 2.2 изображена структура брокера объектных запросов.Его интерфейсы показаны на рисунке штрихованными прямоугольниками, стрелки обозначают, вызывается ли брокер или сам выполняет вызов.

(рис 2.2) Интерфейсы брокера объектных запросов

Чтобы сделать запрос, Клиент может использовать Динамический интерфейс вызова (Dynamic Invocation Interface),один и тот же, вне зависимости от интерфейса целевого объекта, или IDL заглушку (stub),специфичную для интерфейса целевого объекта. Клиент также может напрямую взаимодействовать с брокером для получения некоторых функций. Реализация объекта получает запрос как вызов либо через автоматически сгенерированный IDL скелетон,либо через динамический скелетон. Реализация объекта может вызывать объектный адаптер или ORB во время выполнения запроса или в другое время. Интерфейсы объектов могут быть описаны двумя способами. Во-первых, статически, на языке описания интерфейсов IDL.Этот язык позволяет описывать типы объектов через предоставляемые ими операции и их параметры. Во-вторых, интерфейсы могут быть добавлены в Репозиторий Интерфейсов.Это специальный сервис, представляющий компоненты интерфейсов как объекты и предоставляющий доступ к этим компонентам во время выполнения.

Для выполнения запроса клиент должен иметь доступ к объектной ссылке (IOR -Interoperable Object Reference),знать тип объекта и ту операцию, которую он хочет выполнить. Клиент инициирует запрос, вызывая подпрограммы заглушки, специфичные для конкретного объекта, или создавая запрос динамически (Рис. 2.3).

(рис 2.3) Клиент выполняет запрос (динамически или через заглушку)

Динамический интерфейс вызова и интерфейс заглушки имеет одинаковую семантику, так что получатель сообщения не может определить, как был послан запрос. ORB находит подходящий код реализации объекта, пересылает ему параметры и отдает управление через IDL скелетон или динамический скелетон (Рис. 2.4). Скелетоны специфичны для конкретного интерфейса и объектного адаптера. Во время выполнения запроса реализация может пользоваться некоторыми сервисами ORB через объектный адаптер. Когда запрос выполнен, управление и значения результата возвращаются клиенту.

(рис 2.4) Реализация объекта получает запрос

Реализация объекта может выбрать, какой объектный адаптер использовать, в зависимости от того, в каких сервисах она нуждается. На Рис. 2.5 показано, как информация об интерфейсе и реализации становится доступной клиентам и реализациям объектов. Интерфейсы описываются на IDL или с помощью репозитория интерфейсов. Их описания используются для генерации клиентских заглушек и скелетонов для реализации.

(рис 2.5) Репозитории интерфейсов и реализаций

Информация о реализации объекта предоставляется во время инсталляции и хранится в репозитории реализации, а затем используется в процессе доставки запроса.

Брокер объектных запросов (ORB)

Вместе с IDL -компилятором, репозиторями и различными объектными адаптерами, ORB предоставляет полный набор сервисов самым разным клиентам и объектным реализациям. Ядро ORB - это часть брокера, обеспечивающая базовое представление объектов и передачу запросов. Технология CORBA может поддерживать различные объектные механизмы за счет компонентов, надстроенных над ядром и предоставляющих интерфейсы, которые позволяют скрыть различия между разными ядрами.

Клиенты

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

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

Реализации объектов (Object implementation)

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

Объектные ссылки (IOR)

Объектная ссылка - это информация, необходимая для определения конкретного объекта внутри ORB.Как для клиента, так и для реализации объектная ссылка представляется так, как диктует связывание соответствующего языка программирования, таким образом, они изолированы от конкретного представления ссылки.

Объектная ссылка, переданная клиенту, действительна только на время жизни клиента. Различные брокеры должны предоставлять одно и то же представление объектной ссылки для данного языка программирования (это позволяет одной и той же программе получать доступ к объекту по ссылке независимо от используемого брокера). Кроме того, для удобства брокер может предоставлять другие способы доступа к объекту. Существует особенная объектная ссылка, не указывающая ни на один объект.

Язык описания интерфейсов (IDL)

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

IDL является средством, с помощью которого реализация объекта сообщает своим потенциальным клиентам, какие операции доступны и как они могут быть вызваны. Из IDL- описания CORBA -объект можно перевести на определенный язык программирования или в другую объектную систему.

Связывание языков программирования с IDL

Различные объектно-ориентированные и не объектно-ориентированные языки программирования могут получать доступ к CORBA -объектам по-разному. Для объектно-ориентированных языков, скорее всего, предпочтительно видеть CORBA -объекты как объекты языка программирования. И даже для не объектно-ориентированных языков скрытие фактического представления объектных ссылок и методов внутри брокера представляется удобным. Связывание того или иного языка программирования с IDL должно быть одинаковым для всех реализаций ORB.Связывание языка включает определение специфичных для языка типов данных и интерфейсов процедур для доступа к объекту через ORB.Оно включает структуру интерфейса клиентской заглушки (для объектноориентированных языков не обязательно), интерфейс динамического вызова, скелетон реализации, объектные адаптеры и интерфейс для обращения напрямую к брокеру. Связывание также определяет взаимодействие между вызовами объекта и потоками выполнения в клиенте и реализации. Самые распространенные связывания предоставляют синхронные вызовы, когда управление возвращается клиенту после завершения операции. Дополнительные связывания могут возвращать управления программе сразу после инициации вызова. В этом случае должны предоставляться дополнительные подпрограммы, зависящие от языка, осуществляющие синхронизацию потоков программы и вызова объекта.

Клиентские заглушки (client stubs)

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

Динамический интерфейс вызова (Dynamic invocation)

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

Скелетон реализации (Server skeleton)

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

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

Динамический интерфейс скелетона

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

Динамические скелетоны могут быть вызваны как клиентскими заглушками, так и динамическим интерфейсом вызова на стороне клиента, причем результат должен быть одинаковым.

Объектные адаптеры

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

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

Интерфейс ORB

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

Репозиторий интерфейсов

Репозиторий интерфейсов - это сервис, предоставляющий устойчивые объекты, отражающие IDL -информацию в форме, доступной во время выполнения. Информация из репозитория интерфейсов может быть использована брокером для осуществления запросов. Более того, используя информацию из репозитория, программа может найти объект, интерфейс которого был неизвестен во время компиляции программы, и, тем не менее, определить, какие операции могут выполняться объектом, и вызвать эти операции.

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

Репозиторий реализаций

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

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

Язык IDL

Появление в программировании того или иного языка обычно связано с возникновением и развитием некоей новой концепции. Так, одновременно с идеей объектно-ориентированной разработки (в ее современном варианте) был создан язык Smalltalk.Дальнейшее применение объектов и компонентов вкупе с внедрением последних в распределенные системы вызвало необходимость создания такого языка программирования, который бы позволил описать любой объект или компонент. И, что не менее важно, это описание должно быть одинаковым для любой платформы. Этим требованиям удовлетворяет язык описания интерфейсов IDL (Interface Definition Language).

Следует заметить, что IDL не только язык, но и инструмент, с помощью которого можно сохранять метаинформацию об объектах, т. е. данные о том, как устроен объект. Известно довольно много случаев, когда язык IDL используется для описания контрактов - технических параметров, позволяющих нескольким независимым группам работать одновременно над различными частями проекта. Хотя в основном исходные тексты на IDL служат своеобразным "сырьем", из которого специальные компиляторы IDL генерируют исходные тексты на одном из языков программирования высокого уровня. Такой процесс будем называть трансляцией. Типичный процесс создания распределенных объектных приложений состоит из описания объектов на IDL,их трансляции на какой-либо язык программирования, реализации объектов на данном языке и компиляции полученных исходных текстов в готовые для запуска модули.

Приведем список терминов, используемых далее при описании языка IDL:

  • модуль - блок с заданным именем, объединяющий логически связанные конструкции языка IDL ; в целом модуль можно воспринимать как пакет ( package ) в языке Java или пространство имен ( namespace ) в языке C++ ;
  • интерфейс - набор атрибутов и операций объекта, с помощью которого потребитель может обращаться к объекту;
  • операция - сущность, которую вызывают для выполнения действий, связанных с функциональным назначением объекта; операцию можно сравнить с методом класса
  • атрибут - сущность, описывающая какое-либо свойство объекта; атрибут эквивалентен паре операций, предназначенных для чтения и записи свойства класса
  • Синтаксис IDL

    Приступим к описанию элементов и конструкций языка.

    Комментарии

    Комментарии IDL - точная копия комментариев языка С++.

    Идентификаторы

    К идентификаторам IDL относятся имена интерфейсов, модулей, атрибутов, операций, констант и т. д. Идентификатор может содержать латинские буквы, цифры, а также знак подчеркивания Все идентификаторы в IDL -файлах должны начинаться с буквенного символа. Регистр букв в идентификаторах не различается (это сделано для того, чтобы не возникало лишних проблем при трансляции IDL описания на язык программирования, нечувствительный к регистру). Символ подчеркивания '_' не может быть первым в имени идентификатора (однако очень часто при трансляции IDL компилятор сам добавляет перед получаемыми идентификаторами символы подчеркивания, чтобы избежать конфликтов имен).

    Ключевые слова

    К регистру букв в ключевых словах, в отличие от идентификаторов, компиляторы IDL чувствительны. Ниже приведена таблица ключевых слов IDL.

    Ключевые слова IDL
    any double interface readonly unsigned
    attribute enum long sequence union
    boolean exception module short void
    case FALSE Object string wchar
    char fixed octet struct wstring
    const float oneway switch
    context In out TRUE
    default inout raises typedef

    Литералы

    Литералы языка IDL подразделяются на булевы, "узкие" и "широкие" символьные, целочисленные, с плавающей точкой, с фиксированной точкой и строковые. Все эти виды литералов, кроме чисел с фиксированной точкой, аналогичны литералам C/C++,поэтому описывать их подробно нет необходимости.

    Литералы IDL
    Вид литерала Примеры значений
    Булев TRUE, FALSE
    Символьный 'p', '\t', '010', 'x1A'
    Целочисленный 99, 013, OxFFFF, 0XFFFF
    С плавающей точкой 0.15, 1234E+13, 0.987e-150
    С фиксированной точкой d0.15, D36.28
    Строковый "hello, world"

    Когда одного байта для хранения символа не хватает, применяются так называемые "широкие" символы, содержащие более 8 бит. Таблицы этих символов могут быть разными на разных платформах. Поэтому, задавая литералы с "широкими" символами, следует не выходить за рамки таблицы ISO Latin-1 (8859-1).

    Для финансовых вычислений в IDL предусмотрены литералы с фиксированной точкой, состоящие из целой и дробной частей, разделенных десятичной точкой и отмеченных буквой d или D. Такого рода литералы будут в дальнейшем применяться вместе с типом fixed.Однако, несмотря на то, что эти элементы языка описаны в спецификации CORBA 2.2,найти их реализацию в компиляторах IDL не удалось.

    Строковые литералы должны состоять из символов, допустимых в качестве символьных литералов за исключением символа '\0'.И хотя компиляторы корректно обрабатывают эти символы, тем не менее, ясно, что при трансляции IDL на C и C++ наличие нулевого символа в строке может послужить источником ошибки. Строковые литералы, как и символьные, могут быть основаны на простых и "широких" символах.

    Препроцессинг

    Для организации условной компиляции и задания некоторых опций компиляторы IDL используют препроцессинг, основанный на директивах, описанных в стандарте ANSI C++.Однако, в препроцессинге IDL присутствуют специальные разновидности директивы #pragma.

    Область видимости имен

    Любое имя IDL видно в том блоке, где оно описано. В качестве подобного блока могут выступать описания модулей, интерфейсов, составных типов. Для явного указания блока, в котором содержится используемое имя, применяется пара символов :: (оператор доступа к области видимости из языка C++ ).

    Простые типы

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

    Булев тип boolean отвечает за хранение логических значений TRUE и FALSE.Символьные типы могут описывать обычные 8-битовые символьные данные (тип char) или "широкие" символьные данные (тип wchar),размер которых более 8 бит. В процессе передачи символьных данных по сети они могут быть переконвертированы так, что изменится их представление, но значение символа будет сохранено. Такая ситуация может возникнуть при передаче данных между компьютерами с отличающимися кодировками. Целочисленные типы - это short, long, long long,а также их беззнаковые разновидности с префиксом unsigned.Обратите внимание, что тип int в IDL отсутствует.

    Диапазоны допустимых хранимых значений для целочисленных типов
    Short от -2 15 до 2 15 - 1
    Long от 2 31 до 2 31 - 1
    long long от -2 63 до 2 63 - 1
    unsigned short от 0 до 2 16 - 1
    unsigned long от 0 до 2 32 - 1
    unsigned long long от 0 до 2 64 - 1

    К типам чисел с плавающей точкой относятся float, double и long double.Тип float соответствует IEEE-числу с плавающей точкой одинарной точности. Тип double - IEEE-числу с плавающей точкой двойной точности. И последний тип, long double,используется для описания IEEE-числа с плавающей точкой двойной расширенной точности. Более полные данные о числах с плавающей точкой IEEE можно узнать из документа "IEEE Standard for Binary Floating-Point Arithmetic", ANSI/IEEE Standard 754-1985. В IDL есть два специальных простых типа: octet и any. Первый служит для передачи по коммуникационным системам 8-битовых чисел так, чтобы они в процессе пересылки не подверглись изменениям. Тип octet - хорошая альтернатива типу char в тех случаях, когда передается маленькое число.

    В объектах типа any могут храниться значения любого типа, позволенного IDL, any можно сравнить с универсальным типом void* в языке C или с классом java.lang.Object в языке Java.

    Константы

    Описание констант начинается с ключевого слова const,за которым указываются: тип константы, ее имя, символ присвоения = и константное выражение, состоящее из литералов и математических операторов. Константа может иметь любой из простых типов.

    Математические операторы IDL соответствуют аналогичным операторам C/C++.

    Конструируемые типы

    Для начала отметим, что ключевое слово typedef используется в IDL так же, как и в C/C++:для присвоения синонима имени типа.

    К конструируемым типам IDL относятся перечислимые типы, дискриминируемые объединения и структуры.

    Перечислимые типы

    Перечислимые типы знакомы большинству программистов. Общая схема их описания:

    enum < Имя энумератора >  {< Список элементов >};

    В качестве списка элементов выступают идентификаторы, разделенные запятыми:

    enum Semaphore  {red,  yellow,  green};

    Дискриминируемые объединения

    В IDL объединения обладают дискриминатором - элементом, определяющим, какой член объединения использовать в том или ином случае. Таким образом, объект, полученный в результате компиляции дискриминируемого объединения, способен в разное время хранить значения разных типов. Главное, чтобы во время описания объединения с помощью IDL были перечислены все возможные варианты хранимых типов. Типичное описание дискриминируемого объединения выглядит следующим образом:

    union < Имя объединения > switch
    (< Тип дискриминатора >)
    {
    < Список элементов выбора >
    };

    Здесь <Тип дискриминатора> может быть любым интегральным типом (символьным, целочисленным, булевым или перечислимым).

    Со списком элементов выбора дело обстоит несколько сложнее. Каждый элемент состоит из ключевого слова case и следующего за ним константного выражения, после которого ставится двоеточие и производится собственно описание хранимого типа. Константное выражение должно возвращать тип, совпадающий с типом дискриминатора. При записи в объединение некоторого значения объединение принимает тип, совпадающий с типом сохраняемого значения. Заодно запоминается дискриминатор. В дальнейшем, если будет произведена попытка считать значение под типом, отличающимся от того, под которым это значение было сохранено, произойдет генерация исключения org.omg.CORBA.BAD_OPERATION. Однако для программиста все-таки предусмотрено некоторое облегчение: объединение обладает значением по умолчанию, которое на языке IDL описывается ключевым словом default.Объединение может содержать только один такой элемент.

    Рассмотрим короткий пример описания дискриминируемого объединения:

    union MyType switch   (short)
    {
    case 13:   short alpha;
    case 0x0C << 3:   long beta;
    default:   arr alphabet;
    };

    В этом случае тип MyType имеет дискриминатор типа short.Числа, стоящие после case,представляют собой значения дискриминатора. Именно от них зависит, какой член объединения будет использован. Если значение дискриминанта равняется 13,то под именем alpha будет храниться значение short.При дискриминанте 0X0C << 3 (конечное значение этого константного выражения - 96) хранимое значение будет иметь тип long и носить имя beta.И наконец, по умолчанию значение, хранящееся в объединении, будет иметь тип arr и к нему можно обращаться по имени alphabet.

    Структуры

    Структуры служат для определения сложных типов, призванных хранить наборы разнородных данных. Типичная структура описывается следующим образом (аналогично структурам C/C++ ):

    struct < Имя структуры >
    {
    < Список членов >
    };

    Список членов - это разделенный точкой с запятой набор элементов, являющихся комбинацией из имени типа и идентификатора. Имя типа может быть любым типом IDL (допускается использование массивов с обязательным указанием числа элементов).

    Шаблонные типы

    К шаблонным типам можно отнести строки ("узкие" и "широкие"), последовательности и числа с фиксированной точкой.

    Строки.

    Строки в IDL - это 8-битовые "узкие" string и "широкие" wstring.Первые могут содержать любые символы типа char за исключением '\0'.Второй тип строки состоит из символов, подпадающих под базовый тип wchar,и заканчивается "широким" нулевым символом. Длина строки может быть как ограниченной, так и неограниченной. Ограниченные (bounded) строки можно сравнить с символьным массивом заданной длины.

    Типичное описание подобного типа: typedef wstring <28> boundedString ; В угловых скобках задается размер строки в символах. Неограниченные же (unbounded) строки содержат в себе символов столько, сколько потребуется:

    typedef string unboundedString ;

    Описание строчных типов должно предваряться ключевым словом typedef.

    Последовательности.

    Последовательности во многом схожи со строками. Различие между ними состоит в том, что последовательности помимо символьных данных могут хранить данные и других типов. Точно так же, как и строки, последовательности могут быть ограниченными по размеру либо неограниченными. Правда, в описании последовательности всегда присутствуют угловые скобки, в которых пишется тип хранимых данных (для неограниченных последовательностей) или тип хранимых данных с размером последовательности, разделенные запятой. Первый тип последовательности может выглядеть так:

    typedef sequence unboundedSequence ;

    А вот пример описания типа ограниченной последовательности:

    typedef sequence boundedSequence ;

    Последовательности могут быть рекурсивно вложены друг в друга:

    typedef sequence< sequence > recursedSequence ;

    Числа с фиксированной точкой.

    Тип данных fixed представляет собой десятичное число с фиксированной точкой, имеющее до 31 значащей цифры. К сожалению, на данный момент, тип fixed только описан в спецификации CORBA,но не реализован.

    Прочие типы

    К оставшимся типам относятся массивы и native -типы. Массивы описываются ключевым словом typedef,за которым следуют тип, идентификатор и размерность массива. Допускаются многомерные массивы:

    typedef double someArray[100][100] ;

    Тип native служит для введения новых типов данных, которые реализованы на языке программирования, отличном от IDL. Следующая строка исходного текста на IDL: native NonIDLType; говорит компилятору, что где-то имеется тип NonIDLType,реализованный непонятным для него образом, но, тем не менее, к нему нужно сделать определенный интерфейс.

    Исключения

    Описание типов-исключений практически полностью совпадает с описанием структур. Минимальное различие состоит в замене ключевого слова struct на exception:

    exception < Имя исключения >
    {
    < Список членов >
    };

    Интерфейсы

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

    Следует различать описания (или опережающие описания) интерфейсов и их определения (или полные описания). Допускаются множественные описания, однако множественные определения являются ошибкой.

    Описания требуются в том случае, если необходимо обратиться к интерфейсу еще до его определения. Чтобы получить подобное описание, достаточно написать ключевое слово interface и его имя:

    interface < Имя интерфейса >;

    При определении после имени добавляются двоеточие и имена интерфейсов-предков:

    interface <Имя интерфейса> [ :<Имя интерфейса-предка 1 > ... [ ,< Имя интерфейса-предка n >]...]
    {
    <Описания типов, констант, исключений, атрибутов и операций >
    };

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

    Операции

    Операции - это единственное средства манипулирования внутренним состоянием объекта.

    Описываются операции по схеме, показанной ниже:

    {  oneway void  |   < Возвращаемый тип >  }
    < Имя операции > ({in | out | inout } <Параметр>  ...
    [, { in | out | inout } <Параметр>]... )
    [ raises ( < Имя исключения > ... [, <Имя исключения> ] ... )];

    Если определяется операция, возвращающая некоторое значение, то слева от ее имени необходимо написать тип возвращаемого значения. Исключением являются операции, определенные как oneway,они должны возвращать тип void,т. е. не возвращать никакого значения. Ключевое слово oneway говорит, что при вызове операции программа не ждет, пока эта операция завершится, а продолжает выполнение. Параметры операции могут быть входными (in), выходными (out) или комбинированными (inout).Если операция может возбудить исключения, то они должны быть перечислены в скобках через запятую после ключевого слова raises.

    Атрибуты

    Атрибуты можно воспринимать как аналог переменных (полей) класса, однако, это не совсем корректно, так как интерфейс не может иметь состояния. Поэтому более правильно считать атрибут сокращением для пары операций считывания и модификации определенного свойства класса.

    В IDL атрибуты описываются следующим образом:

    [readonly] attribute < Тип атрибута > < Имя атрибута >;

    Необязательный модификатор readonly говорит, что значение описываемого атрибута не может быть изменено, только считано.

    Модуль

    Модуль - самая "старшая" единица языка IDL. Он служит для группировки типов, интерфейсов и т. д., логически связанных друг с другом. У модуля есть имя и тело:

    module < Имя модуля >
    {
    < Тело модуля >
    };

    Можно считать, что модули являются прямым отображением пакетов языка Java и пространств имен в C++.

    Связывание с IDL

    Спецификация CORBA регламентирует, во что должен превратиться каждый элемент языка IDL в процессе его трансляции в исходные тексты на языке программирования высокого уровня. Версия 2.2 спецификации CORBA расписывает подобные соответствия для C, C++, Smalltalk, Cobol, Ada и Java.Для примера рассмотрим подробнее трансляцию в Java.

    Комментарии

    Комментарии IDL никак не отражаются на сгенерированном Java-коде.

    Имена

    Результаты работы различных компиляторов могут различаться. В основном это касается добавления символов подчеркивания перед сгенерированными именами. Поскольку трансляция зависит от того, что за элемент языка IDL подвергается обработке, различается и количество файлов, получаемых в процессе генерации. Для пользовательских типов, например, будут созданы специальные файлы, имена которых заканчиваются суффиксами Helper и Holder.По спецификации, компилятор резервирует за собой следующие имена:

    <тип> Helper, где <тип> - имя пользовательского типа;

    <тип> Holder, где <тип> - имя пользовательского типа;

    <базовыйТипJava>Holder, где <базовыйТипJava> - один из примитивных типов языка Java;

    <интерфейс>Package, где <интерфейс> - имя интерфейса IDL.

    Вспомогательные классы

    Если программист описывает собственные типы, в результате их трансляции появляются два вспомогательных класса, имена которых состоят из имени типа с добавлением суффиксов Helper и Holder. Они необходимы для корректной работы с объектами. Helper содержит набор статических методов, выполняющих одни и те же рутинные действия. Класс с суффиксом Holder работает "оболочкой" для пользовательского типа, когда его нужно передать в качестве параметров операции объекта. Его генерация происходит не во всех случаях.

    Класс Helper всегда имеет статические методы для чтения и записи данных в поток read () и write (), упаковки данных в тип Any и распаковки (методы insert () и extract() ), а также методы определения типа type() и его идентификатора в репозитарии id().

    Класс Holder должен не только уметь записывать данные в поток методом _write(), читать их оттуда методом _ read () и возвращать код типа методом _ typecode (). В нем должна быть предусмотрена открытая переменная value, хранящая значение, и два конструктора: один - по умолчанию, т.е. без параметров, и второй - с параметром, инициализирующим переменную value.

    Модули

    Во время трансляции описания модулей превращаются в пакеты с теми же самыми именами, что и сами модули. Соответственно все описания типов внутри модулей после трансляции в классы и интерфейсы Java приобретают область видимости внутри сгенерированных пакетов. Если описания типов находятся за пределами модулей, то они транслируются в глобальный пакет Java,т. е. не включаются ни в один пакет. Для примера опишем следующий модуль:

    module UserModule
    {
    typedef string UserType;
    };

    После его трансляции в выходном каталоге появится подкаталог с именем модуля, и в нем будут сохранены файлы, появившиеся в результате генерации исходных текстов для типа UserType. А в самих этих текстах появится строка принадлежности к пакету UserModule:

    package UserModule;

    Интерфейсы

    В первую очередь создаются описания общедоступных интерфейсов Java,наследуемые от базового CORBA -интерфейса org.omg.CORBA.Object. Возьмем следующее описание на

    IDL:

    interface UserInterface
    {
    };

    После компиляции создается следующий интерфейс на языке Java:

    public interface UserInterface
    
    extends org.omg.CORBA.Object
    {
    
    }

    Внутри сгенерированного интерфейса описываются операции, которые компилятор обнаружит в IDL -файле. Для каждого из атрибутов интерфейса в них создаются описания методов чтения и записи. Если атрибут объявлен как readonly,для него генерируется лишь метод чтения. Исходный текст на IDL:

    attribute float UserAttribute;

    будет транслирован в следующие описания методов:

    float UserAttribute();

    void UserAttribute(float arg);

    Если IDL -интерфейс наследуется от другого интерфейса, то в его описании на Java также будет присутствовать наследование.

    Любой параметр операции с модификатором in транслируется в аргумент метода, имеющий соответствующий тип на языке Java.То же самое и с возвращаемым операцией значением. Параметры inout и out не могут транслироваться непосредственно в параметры методов на Java. Поэтому приходится пользоваться Holder классами. Программа-клиент подставляет в качестве параметра экземпляр подобного класса, в котором, как в контейнере, находится передаваемое значение. После передачи параметра по значению хранимые данные изменяются на серверной стороне и возвращаются клиенту, который "вскрывает контейнер" и извлекает новое значение аргумента. Например, показанная операция имеет параметр, объявленный как inout:

    void userOperation(inout double param);

    Компилятор IDL сделает из этого следующий метод на языке Java:

    void userOperation(org.omg.CORBA.DoubleHolder param);

    Такой подход, конечно, создает дополнительные сложности программиста: придется создать экземпляр Holder класса вручную.

    Для интерфейса также создаются класс Helper и класс Holder. В первом из них дополнительно к методам, описанным в разделе "Вспомогательные классы", генерируется метод narrow(), с помощью которого делается приведение к оригинальному типу интерфейса. Дело в том, что программе при запросе ссылки на объект возвращается ссылка типа org.omg.CORBA.Object, которую необходимо привести к запрошенному типу перед использованием, что и делает narrow(). При невозможности произвести эту операцию происходит исключение CORBA::BAD PARAM.

    Некоторые компиляторы (в том числе, idl2java из Visibroker) генерируют еще один метод. Он называется bind() и служит для получения ссылки на запрашиваемый объект. Этот метод не является частью спецификации CORBA.

    Часто у программистов возникают сложности с пониманием того, как транслируются вложенные в описания интерфейсов конструкции. Кажется, что достаточно сгенерировать новый пакет с его именем и поместить в него внутреннее содержимое интерфейса. Однако, по спецификации CORBA,во избежание конфликтов имен нельзя создавать пакеты, имена которых совпадают с уже имеющимися именами. Поэтому решено задавать имена пакетов, добавляя к имени интерфейсов суффикс Package.

    Опишем интерфейс, внутри которого объявляется пользовательский тип:

    interface UserInterface
    {
    typedef any UserType;
    };

    В результате трансляции получится пакет UserInterfacePackage, в который и будут помещены все сгенерированные для пользовательского типа файлы, и все они будут начинаться с директивы:

    package UserInterfacePackage;

    Простые типы

    Трансляция простых типов IDL приводит к появлению соответствующих идентификаторов, но уже имеющих Java -типы. В табл. 2.4 показано соответствие между ними: если данный тип может привести к возникновению исключительной ситуации, то она отмечена в графе "Исключения".

    Программист должен быть осторожен, когда работает с целочисленными типами IDL,объявленными как unsigned. Как известно, в Java нет беззнаковых типов, и это может стать причиной ошибок. Следовательно, требуется позаботиться о соблюдении знаковости транслированного числа.

    Holder классы для простых типов IDL определены в библиотеках, отвечающих за поддержку CORBA в пакете org.omg.CORBA. Имена этих классов начинаются с имени IDL -типа, написанного с заглавной буквы, и заканчиваются суффиксом Holder.

    Соответствие простых типов IDL типам Jav
    Тип IDL Тип Java Исключения
    boolean Boolean
    char Char CORBA::DATA_CONVERSION
    wchar Char
    octet Byte
    string java.lang.String CORBA::MARSHAL, CORBA::DATA_CONVERSION
    wstring java.lang.String CORBA::MARSHAL
    short Short
    unsigned short Short
    long Int
    unsigned long int
    long long long
    Unsigned long long long
    Float float
    Double double
    long double double (?)
    Fixed java.math.BigDecimal CORBA::DATA_CONVERSION
    Any org.omg.CORBA.Any CORBA::BAD_OPERATION

    Константы

    Константы внутри интерфейса.

    Константы, декларируемые внутри интерфейса, транслируются в поля, описанные как public final static, т. е. константы Java.Например, строки:

    interface UserInterface
    {
    const string constIntoInterface = "Hello!";
    };

    будут превращены компилятором java2idl в следующий исходный текст на языке Java:

    public interface UserInterface extends com.inprise.vbroker.CORBA.Object  {
    
    final public static java.lang.String
    constIntoInterface =   (java.lang.String)   "Hello!";
    }

    Константы вне интерфейса

    Константы, не включенные ни в один интерфейс, превращаются в интерфейс с тем же самым именем, что и константа. Внутри этого интерфейса помещается public static final поле с именем value. Например, следующий исходный текст:

    module UserModule  
    {
    const string constIntoInterface = "Hello!";
    };

    будет транслирован следующим образом:

    package UserModule;
    public interface constIntoInterface  
    {
    final public static java.lang.String value = (java.lang.String)"Hello!";
    }

    Конструируемые типы

    Перечислимые типы

    В результате трансляции перечисления получается класс с модификаторами public final и именем, соответствующим имени, описанному в IDL-файле.Для каждого элемента выбора внутри класса создаются два статических члена. Первый является уникальной целочисленной константой, а второй - ссылкой на экземпляр перечисления, инициализированный константным значением для данного выбора. Заодно генерируется закрытый конструктор, инициализируемый целочисленным значением. Еще один метод, value (), возвращает целое число, которым инициализировано перечисление, а в дополнение к нему имеется метод получения элемента перечисления по заданному числу from_int (). При недопустимом значении параметра этого метода возникает исключение CORBA::BAD_ PARAM. Например, исходный текст:

    enum UserEnum {  labelOne,   labelTwo,   labelThree  };

    будет транслирован следующим образом:

    public final class UserEnum 
    {
    public static final int  labelOne = 0, labelTwo = 1, labelThree = 2; 
    public static final UserEnum labelOne = new UserEnum(labelOne); 
    public static final UserEnum labelTwo = new UserEnum(labelTwo); 
    public static final UserEnum labelThree = new UserEnum(labelThree); 
    public int value() { return value; }
    public static final UserEnum from int(int i) throws org.omg.CORBA.BAD_PARAM  
    	{ switch (i)   
    		{
    		case _labelOne:
    		return labelOne;
    		case _labelTwo:
    		return labelTwo;
    		case _labelThree:
    		return labelThree;
    		default:
    		throw new org.omg.CORBA.BAD_PARAM();
    		}
    	}
    private UserEnum(int _value) 
    	{
    	this.value = value;
    	}
    private int _value;
    }

    Дискриминируемые объединения

    Объединение, описанное на языке IDL, транслируется в Java -класс тем же самым именем и с модификаторами public final. Внутри можно также найти:

  • конструктор по умолчанию (без параметров);
  • метод чтения дискриминатора discriminator() ;
  • метод чтения для каждого варианта с именем, заимствованным из декларируемого варианта;
  • методы модификации значения для каждого декларируемого варианта;
  • методы модификации значения для каждого декларируемого варианта, который объявляется для нескольких меток case ;
  • метод default (), если в нем есть необходимость.
  • В качестве примера рассмотрим следующее дискриминируемое объединение:

    enum UserEnum {Single,   Double, Any};
    union UserUnion switch   (UserEnum)   {
    case Single:
    case Double:  wchar anySymbol; 
    default:   any other;
    };

    и полученный в результате трансляции Java-код:

    final public class UserUnion  
    {
    private java.lang.Object _object; 
    private UserModule.UserEnum _disc;
    private UserModule.UserEnum _defdisc = UserModule.UserEnum.Any; 
    public UserUnion()   { }
    public UserModule.UserEnum discriminator()
    	{ return _disc;
    	} 
    public char anySymbol() {... } public org.omg.CORBA.Any other() {... } 
    public void anySymbol(char value) {... }
    public void anySymbol(UserModule.UserEnum disc, char value) {... } 
    public void other(org.omg.CORBA.Any value) {... }
    }

    Все методы чтения генерируют исключительную ситуацию CORBA:: BAD_OPERATION, если читаемое значение не установлено. Поэтому желательно сначала вызывать метод descriminator (), чтобы ознакомиться с текущим типом хранимого значения. Если не указать в объединении метку default, компилятор сверит все имеющиеся метки со всеми возможными значениями дискриминанта. Если таких значений больше, чем ветвей case, будет сгенерирован еще один метод default () (или _ default () в случае конфликта имен), в котором хранимое значение будет установлено так, чтобы оно было за пределами дискриминанта. Если в предыдущем примере удалить строку с меткой default, то сгенерируется следующий метод:

    public void _default() 
    { disc = defdisc; _object = null;
    }

    Структуры

    Тип struct во время компиляции транслируется в класс Java с модификаторами final и public. Имя полученного класса совпадает с именем структуры. В классе объявляются переменные-члены для каждого объявленного в IDL поля структуры. Тип переменных-членов уже относится к языку Java и выясняется в процессе трансляции полей структуры. Так же внутри полученного класса декларируются два конструктора: один - по умолчанию, т. е. без параметров, и другой - конструктор инициализации с параметрами для инициализации переменных-членов. Некоторые компиляторы создают также метод toString (), возвращающий строку, в текстовой форме отражающую содержимое полей класса.

    Например, объявлена следующая структура:

    struct UserStructure
    {any descriptor; Object reference;
    };

    После трансляции полученный класс UserStructure имеет следующий вид:

    public final class UserStructure  
    {
    public org.omg.CORBA.Any descriptor; 
    public org.omg.CORBA.Object reference; 
    public UserStructure()   {   }
    public UserStructure(org.omg.CORBA.Any __descriptor, org.omg.CORBA.Object reference)   
    	{
    	descriptor = __descriptor;
    	reference = __reference;
    	}
    }

    Последовательности и массивы

    Последовательности не создают в процессе трансляции исходного текста какого-либо исходного текста, но значительно усложняют класс Helper и класс Holder. Класс Holder теперь содержит массив с именем value для хранения элементов последовательности, а в методах класса Holder идет проверка диапазона передаваемого массива на предмет выхода за границы. Например, при трансляции:

    typedef sequence UserSequence;

    вызывает генерацию следующего массива в Holder классе:

    public byte[] value;

    и несколько мест, где происходит проверка границ массива:

    abstract public class UserSequenceHelper  
    {
    ...
    public static byte[]   read(org.omg.CORBA.portable.InputStream_input)   
    	{
    	if(_length3 > 128)   
    		{
    		throw new org.omg.CORBA.BAD_PARAM( "Sequence exceeded bound");
    		}
    	}
    public static void write(org.omg.CORBA.portable.OutputStream output, byte[] value)   
    	{
    	if(value.length > 128) 
    		{
    		throw new org.omg.CORBA.BAD_PARAM( "Sequence exceeded bound");
    		}
    	}
    }

    Трансляция массивов во многом похожа на трансляцию ограниченных последовательностей. Разница лишь в проверке границ. Если ограниченные последовательности, как показано выше, предполагают проверку на выход за границы размера, то массив проверяется на четкое соответствие размеру:

    public static void write(org.omg.CORBA.portable.OutputStream _output, byte[]   value)   
    {
    if(value.length != 128)   
    	{
    	throw new org.omg.CORBA.BAD_PARAM("Invalid array length");
    	}
    output.write octet array(value, 0, value.length);
    }

    Исключения

    Поскольку исключения имеют схожее со структурой строение, любое исключение, описанное пользователем на IDL,транслируется в класс с модификатором final public, ведущим свою родословную от org.omg.CORBA.UserException. Системные исключения CORBA,наоборот, наследуются от исключения java.lang.RuntimeException, которое, как правило, не перехватывается. Сгенерированный класс содержит по переменной для каждого описанного в IDL поля и два конструктора: по умолчанию (без параметров) и инициализации. Следующий пример:

    exception UserException  { string why;
    octet errorCode;
    };

    показывает, как происходит трансляция исключения:

    public final class UserException extends org.omg.CORBA.UserException  
    { 
    public String why; 
    public byte errorCode; 
    public UserException() { super(); }
    public UserException(String __why, byte __errorCode) 
    	{ 
    	super(); 
    	why = __why;
    	errorCode = __errorCode;
    	}
    }

    В CORBA имеется ряд предопределенных системных исключений, каждое из которых косвенно наследует класс java.lang.RuntimeException через другой класс org.omg.CORBA.SystemException.

    Псевдонимы типов (typedef)

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

    Создание CORBA-систем

    Для того чтобы создать CORBA -систему, сначала необходимо установить и настроить соответствующий инструментарий. В этом разделе будет вкратце описаны основные действия на каждом этапе разработки, а затем в разделе с примерами эти действия будут рассмотрены на конкретном примере.

    Инструменты и их конфигурирование

    Среди наиболее популярных и доступных инструментов для создания CORBA -систем брокер для Java от Sun Microsystems,входящий в стандартную поставку Java, VisiBroker от Inprise/Borland, WebLogic.

    Порядок действия при создании CORBA-системы

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

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

  • объектно-ориентированный анализ и моделирование;
  • описание и трансляция объектов;
  • создание сервера;
  • создание клиента;
  • отладка объектов.
  • Объектно-ориентированный анализ и моделирование

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

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

    Описание и трансляция интерфейсов

    Готовая модель системы содержит классы, которые должны быть описаны с помощью языка IDL.Далее это описание можно транслировать с помощью IDL -компилятора в базовые исходные тексты на конкретном языке программирования (заглушки и скелетоны) или добавить IDL -описания в репозиторий интерфейсов.

    Примеры

    Пример "Служба мгновенных сообщений"

    Функциональность

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

    (рис 2.6) Окно регистрации и аутентификации

    В окне регистрации/аутентификации (Рис. 2.6) пользователь вводит свое имя и пароль, а потом нажимает одну из кнопок Регистрация (Register) или Аутентификация (Login).При регистрации сервер запоминает имя и пароль пользователя (если такое имя еще не зарегистрировано) и выполняет вход в систему с этими параметрами. При аутентификации сервер проверяет наличие комбинации имя/пароль среди ранее зарегистрированных пользователей и в случае совпадения выполняет вход в систему.

    (рис 2.7) Окно сообщений

    У Ника открыто окно обмена сообщениями с Лео (Рис. 2.7). Сообщения Лео появляются на экране сразу после отправки. Кэйт зарегистрирована на сервере, но не подключена к системе в данный момент. Питер написал Нику сообщение, о чем свидетельствует синий цвет кнопки. Ник прочитает это сообщение после того, как переключится в режим общения с Питером, нажав на кнопку с его именем.

    Инструменты

    Разработка будет вестись на языках Java и C++ с помощью средств Java 2 SE 1.4.2 и Microsoft Visual Studio 8 (2005). В качестве брокера объектных запросов (ORB) для обоих языков будем использовать Borland VisiBroker 7.На настройке брокера остановимся подробнее.

    На момент написания этого текста седьмая версия VisiBroker - единственная официально бесплатно распространяемая для ознакомительного использования ( trial, 60 дней). Стабильную версию Inprise обещает выпустить в следующем году. Описываемые здесь способы настройки могут не потребоваться при использовании окончательной версии. Временную (на 60 дней) лицензию можно получить на официальном сайте. После окончания ее действия штатная замена лицензии не помогает, требуется удалить старую лицензию как файл, и после этого установить новую.

    В качестве C+ + компилятора VisiBroker 7,в отличие от предыдущих версий, поддерживает только компилятор из Visual Studio 8 (компилятор от Borland не поддерживается). Java поставляется в комплекте, причем две версии - 1.4.2 и 1.5. Несоответствие версий стало одной из причин неспособности данного брокера работать с Java без модификации. Для компиляции и выполнения написанных на Java CORBA-объектов используются входящие в VisiBroker программы vbj и vbjc вместо java и javac соответственно. При этом для компиляции используется версия JDK 1.5,а для выполнения - 1.4.2, что приводит к неработоспособности. Можно было бы добавить опцию компиляции с целью 1.4.2, но проще выбрать одну версию, в нашем случае 1.4.2. Для этого требуется заменить в файле <VBDir>\bin\toolsj dk.config строку

    "javahome $var(installRoot)/jdk/jdk1.5.0"на

    "javahome $var(installRoot)/jdk/jdk1.4.2".

    Кроме vbj, vbjc из программ от VisiBroker нам потребуются SmartAgent и Naming Service.Первый должен быть запущен для выполнения всех примеров (кроме первого). Второй проще запускать указанным в соответствующих примерах образом. Несмотря на то, что согласно документации запуск Naming Service с соответствующими параметрами является альтернативой использованию SmartAgent,для работы текущей версии сервиса имен требуется и запуск SmartAgent,и явное указание параметров.

    Интерфейс

    Реализацию службы мгновенных сообщений с использованием CORBA мы начнем с описания интерфейсов CORBA -объектов на языке IDL. Создадим IDL -файл с описанием модуля Message, в котором содержатся два интерфейса - MessageReceiver и MessagingService. Методы этих интерфейсов будут доступны для вызова клиентам CORBA -объектов. Первый интерфейс реализуется на стороне клиента службы мгновенных сообщений, а второй - на стороне сервера.

    module Message  
    {
    interface MessageReceiver  
    {
    void newMessage(in unsigned long from,   in string text); 
    void userStatusNotification(in unsigned long uid,   in string name,   in boolean online);
    };
    interface MessagingService  
    {
    unsigned long registerUser(string userName,   in string password); 
    unsigned long login(in string userName, in string password, in MessageReceiver receiver); 
    boolean sendMessage(in unsigned long from, in string password, in unsigned long to, in string msg);
    boolean logout(in unsigned long uid, in string password);
    };

    Интерфейс MessageReceiver предназначен для уведомления клиента о событиях в системе. Метод newMessage уведомляет клиента о новом предназначенном ему сообщении, а метод userStatusNotification - об изменении статуса других пользователей службы.

    Интерфейс сервера MessagingService предоставляет методы для регистрации пользователя ( registerUser ), аутентификации ( login ), отправки сообщения конкретному пользователю ( sendMessage ) и выхода из системы ( logout ). В целях гарантии аутентичности каждый метод сервера требует передачи пароля в качестве одного из параметров.

    Далее IDL -файл необходимо скомпилировать, чтобы получить классы заглушек, скелетонов и другие вспомогательные классы на целевом языке программирования. Для компиляции IDL -описания в Java будем использовать команду:

    idl2java [params] Message.idl

    Здесь в параметрах указывается тип используемого объектного адаптера. От него зависит, какие вспомогательные классы будут созданы. Тип объектного адаптера по умолчанию - POA.Для компиляции в C+ + используется команда:

    idl2cpp [params] Message.idl

    IDL -файл необязательно компилировать отдельно. Имена создаваемых автоматически файлов вполне предсказуемы, поэтому их можно использовать в коде реализации и до компиляции. На практике удобно создать единый makefile, с помощью которого будет компилироваться сначала IDL -описание, а затем классы реализации на целевом языке.

    Прямое задание IOR

    В нашем первом примере и сервер и клиент службы мгновенных сообщений будут реализованы на языке Java.Для вызова методов CORBA -объекта необходимо сначала получить ссылку на этот объект. В данном примере ссылка на сервер задается явно в виде Interoperable Object Reference (IOR).

    Начнем с реализации сервера службы мгновенных сообщений. Он выполняет следующие действия:

  • Инициализация ORB.

    ORB orb = ORB.init(args,null);

  • Создание серванта (объекта реализации).

    Message.MessagingService messagingService = new MessagingServiceImpl();

  • Получение IOR серванта и сохранение его в файл.

    String ior = orb.object_to_string(messagingService);

    ...

    FileWriter fw = new FileWriter("MS.ior"); fw.write(ior);

  • Ожидание подключения клиентов.
  • Ниже приведен полный листинг кода сервера.

    import org.omg.CORBA.*; 
    import java.io.*;
    public class Server  
    {
    public static void main(String[]   args)   
    { 
    ORB orb = ORB.init(args,null); 
    Message.MessagingService messagingService =new MessagingServiceImpl(); 
    String ior = orb.object to string(messagingService); 
    System.out.println(messagingService + " is ready.\n" + ior);
    try 	
    	{
    	FileWriter fw = new FileWriter("MS.ior");
    	fw.write(ior);
    	fw.close();
    	System.out.println("IOR written to file"); 
    	}   
    catch(IOException e)   
    	{ 
    	System.out.println("Failed to write IOR to file. Exception: "); 
    	e.printStackTrace();
    	}
    try 	
    	{
    	Thread.currentThread().join();
    	}  
    catch(InterruptedException ex)   
    	{}
    }

    Далее опишем сервант, реализующий интерфейс MessagingService. Как видно из листинга, приведенного ниже, его необходимо унаследовать от класса-скелетона Message.MessagingServiceImplBase, сгенерированного автоматически компилятором IDL.

    import org.omg.CORBA.*;
    import org.omg.Messaging.*;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.HashMap;
    
    public class MessagingServiceImpl extends Message.MessagingServiceImplBase  
    { 
    private HashMap nameToId; 
    private ArrayList users; 
    public MessagingServiceImpl()   
    	{
    	System.out.println("Constructing MessagingServiceImpl");
    	nameToId = new HashMap();
    	users = new ArrayList();
    	}
    public int registerUser (String userName, String password)   
    	{ 
    	if (nameToId.containsKey(userName))   
    	{
    	System.out.println("User " + userName + " already registered"); 
    	return 0; 
    	}  
    	else  
    	{
    	int uid = nameToId.size();
    	nameToId.put(userName,  new Integer(uid)); 
    	users.add(new User(userName,  password)); 
    	notifyAllUsers(++uid,   false); 
    	System.out.println("User " + userName + " registered successfully,  user ID is  " + (new Integer(uid)).toString());
    	return uid;
    	}
    	}
    ...
    private void notifyAllUsers(int uid,  boolean online)
    	{
    	String userName = ((User)users.get(uid - 1)).name;
    	for (int i = 0;  i < users.size(); ++i)	
    		{
    		if (i   != uid - 1)   
    			{
    			User buddy = (User)users.get(i);
    			if (buddy.online)   
    				{
    				buddy.receiver.userStatusNotification( uid,  userName,   online);
    				}
    			}
    		}
    	}
    class User {... } 
    class Msg    {... }
    }

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

  • Инициализация ORB.

    ORB orb = ORB.init(args, null);

  • Получение IOR сервера из файла.

    String ior = br.readLine();

  • Преобразование IOR сервера в CORBA -объект и приведение его к соответствующему типу.

    org.omg.CORBA.Object obj = orb.string_to_object(ior);

    Message.MessagingService service =

    Message.MessagingServiceHelper.narrow(obj);

  • Создание окон пользовательского интерфейса и серванта.
  • Полный листинг класса клиента приведен ниже. Листинги классов пользовательского интерфейса можно найти в файлах с примерами.

    import org.omg.CORBA.*;
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.util.ArrayList;
    import java.util.Vector;
    import java.io.*;
    
    public class Client  
    {
    public static void main(String[]   args)   
    {
    try 
    	{
    	ORB orb = ORB.init(args, null); 
    	FileReader fr = new FileReader("MS.ior"); 
    	BufferedReader br = new BufferedReader(fr); 
    	String ior = br.readLine();
    	org.omg.CORBA.Object obj = orb.string_to_object(ior); 
    	System.out.println(ior);
    	Message.MessagingService service = Message.MessagingServiceHelper.narrow(obj);
    	MessagingFrame mf = new MessagingFrame(service);
    	Message.MessageReceiver receiver = new MessageReceiverImpl(mf);
    	LoginFrame loginFrame = new LoginFrame(service,   receiver,  mf);
    	loginFrame.show(); 
    	}   
    catch   (Throwable t)   
    	{	t.printStackTrace();
    	}
    }

    Наконец, создадим сервант, реализующий интерфейс MessageReceiver. По аналогии с сервантом сервера, он наследуется от скелетона Message._MessageReceiverImplBase.

    import javax.swing.*; 
    import org.omg.CORBA.*; 
    import java.awt.*; 
    import java.awt.event.*; 
    import javax.swing.*; 
    import java.util.ArrayList; 
    import java.util.Vector;
    
    public class MessageReceiverImpl extends Message.MessageReceiverImplBase 
    { 
    public MessageReceiverImpl(MessagingFrame mf)   
    	{ 
    	mesFrm = mf;
    	}
    
    public void newMessage(int from,   String text)   
    	{
     	mesFrm.newMessage(from,   text); 
    	System.out.println("New message from " + 
    	(new Integer(from)).toString()   + ":   " + text);
    	}
    public void userStatusNotification(int uid,   String name,  boolean online)   
    	{ 
    	mesFrm.buddyStateChanged(uid,  name,   online); 
    	System.out.println(name + " is now " + (online ? "online" : "offline"));
    	}
    private MessagingFrame mesFrm;
    }

    Использование BOA

    Одним из существенных недостатков реализации службы мгновенных сообщений в первом примере была необходимость передачи IOR сервера клиенту (например, путем копирования файла). Более удобный способ получения ссылки на объект - использование механизма bind. Этот механизм не является стандартным,то есть не предусмотрен стандартом CORBA, но предоставляется VisiBroker.

    В качестве объектного адаптера в данном примере используется Basic Object Adapter (BOA).Он менее переносим (однако, более прост в использовании), чем более новый Portable Object Adapter (POA).

    Отметим, что, поскольку здесь (как и в первом примере) используется BOA,при компиляции IDL-файла в качестве параметра требуется указать -boa.

    В приведенном ниже листинге класса сервера изменения по сравнению с предыдущим примером выделены жирным шрифтом. Их смысл заключается в том, что теперь сервер инициализирует BOA и сообщает ему о готовности CORBA -объекта и его серванта. Теперь при создании серванта в качестве параметра ему передается имя CORBA -объекта. Это имя регистрируется конструктором родителя серванта ( Message.MessagingServiceImplBase ) для последующего использования механизмом bind.

    public class Server  
    {
    public static void main(String[]   args)   
    	{
    	org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    	com.inprise.vbroker.CORBA.BOA boa = ((com.inprise.vbroker.CORBA.ORB)orb).BOA_init();
    	Message.MessagingService messagingService =new MessagingServiceImpl("MessagingService");
    	boa.obj_is_ready(messagingService);
    	System.out.println(messagingService + " is ready."); 
    	boa.impl_is_ready();
    	}
    }

    Реализация серванта остается прежней. Единственное изменение: добавляется вызов родительского конструктора, которому передается имя CORBA -объекта.

    public MessagingServiceImpl(String name)   
    { 
    super(name);
    System.out.println("Constructing MessagingServiceImpl"); 
    nameToId = new HashMap(); 
    users = new ArrayList();
    }

    В реализации клиента добавилась инициализация BOA. Кроме того, ссылка на сервер теперь не получается напрямую из IOR, а находится по имени MessagingService, зарегистрированному сервером. Поиск выполняется при помощи метода bind класса MessagingServiceHelper, который был автоматически сгенерирован из IDL -описания.

    public class Client  
    {
    public static void main(String[]   args)   
    	{
    	try 
    		{
    		org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    		com.inprise.vbroker.CORBA.BOA boa =
    		((com.inprise.vbroker.CORBA.ORB)orb).BOA_init();
    		Message.MessagingService service =
    		Message.MessagingServiceHelper.bind(orb,   "MessagingService");
    		MessagingFrame mf = new MessagingFrame(service); 
    		Message.MessageReceiver receiver = new MessageReceiverImpl(mf); 
    		LoginFrame loginFrame = new LoginFrame(service,   receiver,  mf); 
    		loginFrame.show(); 
    		}  
    	catch   (Throwable t)   
    		{ 
    		t.printStackTrace();
    		}
    	}
    }

    Использование POA

    В данном примере используется другой вид объектного адаптера - Portable Object Adapter (POA).В связи с этим в реализации сервера произошли следующие изменения:

  • За инициализацией ORB следует получение ссылки на корневой объектный адаптер rootPOA. Корневой адаптер всегда существует, ссылку на него можно получить с помощью метода ORB resolve_initial_references.
  • Далее создается массив политик, который после передается в качестве одного из параметров конструктору нового объектного адаптера myPOA. Адаптер myPOA создается внутри корневого объектного адаптера ( POA можно вкладывать друг в друга).
  • После создания серванта имя будущего CORBA -объекта представляется в виде набора байт. Далее объектному адаптеру дается команда активировать CORBA- объект с заданным именем и ссылкой на сервант.
  • В корневом объектном адаптере активируется менеджер объектных адаптеров.
  • Для ожидания подключений в данном примере используется метод ORB run(). В предыдущих примерах использовался альтернативный способ - присоединение сервера к некоторому потоку.
  • import org.omg.PortableServer.*;
    public class Server  
    {
    public static void main(String[]   args)   
    	{
    	try 
    		{
    		org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    		POA rootPOA = POAHelper.narrow(orb.resolve initial references("RootPOA"));
    		org.omg.CORBA.Policy[] policies = {rootPOA.create lifespan policy( LifespanPolicyValue.PERSISTENT)};
    		POA myPOA = rootPOA.create_POA("messaging poa", rootPOA.the_POAManager(),  policies  ); 
    		MessagingServiceImpl messagingServant =new MessagingServiceImpl();
    		byte[]  messagingId = "MessagingService".getBytes(); 
    		myPOA.activate object with id(messagingId,  messagingServant); 
    		rootPOA.the_POAManager().activate();
    		System.out.println(myPOA.servant to reference(messagingServant)   + " is ready."); 
    		orb.run(); 
    		}  
    	catch   (Exception e)   
    		{
    		e.printStackTrace();
    		}
    	}
    }

    Класс серванта теперь наследуется от другого скелетона - Message.MessagingServicePOA. Здесь нет необходимости указывать имя CORBA- объекта в качестве параметра конструктора родителя.

    public class MessagingServiceImpl extends Message.MessagingServicePOA 
    {
    private HashMap nameToId; 
    private ArrayList users; 
    public MessagingServiceImpl()   
    	{
    	System.out.println("Constructing MessagingServiceImpl");
    	nameToId = new HashMap();
    	users = new ArrayList();
    	}
    }

    Изменения в клиенте аналогичны изменениям в сервере:

  • После получения ссылки на корневой объектный адаптер создаются политики и адаптер callbackPOA, в котором будет размещен CORBA -объект получатель сообщений ( MessageReceiver ).
  • Далее с помощью механизма bind получаем ссылку на сервер.
  • Создание пользовательского интерфейса.
  • Активация получателя сообщений и менеджера объектных адаптеров.
  • Получение ссылки на MessageReceiver для последующей передачи серверу.
  • public static void main(String[]   args)   
    {
    try 
    	{
    	org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null); 
    	POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); 
    	org.omg.CORBA.Policy[] policies = {rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT)};
    	POA callbackPOA = rootPOA.create_POA("MessageReceive",  rootPOA.the_POAManager(), policies); 
    	byte[] messagingId = "MessagingService".getBytes(); 
    	Message.MessagingService service =
    		Message.MessagingServiceHelper.bind(orb,   "/messaging_poa", messagingId);
    	MessagingFrame mf = new MessagingFrame(service);
    	MessageReceiverImpl mr = new MessageReceiverImpl(mf);
    	callbackPOA.activate_object(mr);
    	callbackPOA.the_POAManager().activate();
    	Message.MessageReceiver receiver = 
    		Message.MessageReceiverHelper.narrow(callbackPOA.servant_to_reference(mr));
    	LoginFrame loginFrame = new LoginFrame(service,   receiver,  mf);
    	loginFrame.show();
    	}  
    catch   (Throwable t)   
    	{
    	t.printStackTrace();
    	}
    }

    Сервер получателя сообщений теперь наследуется от другого скелетона:

    static class MessageReceiverImpl extends Message.MessageReceiverPOA...

    Использование сервиса имен

    В данном примере рассмотрен еще один способ получения ссылки на CORBA -объект -использование сервиса имен. Этот способ более удобен, чем прямое задание IOR, и, в отличие от механизма bind, является стандартным.Основные изменения в реализации сервера состоят в следующем:

  • Подключение пакета CosNaming
  • Получение ссылки на сервис имен
  • Создание пути размещения объекта в виде последовательности компонентов имени (в данном случае имя состоит из одного компонента, однако в более сложных случаях удобно использовать иерархическую структуру)
  • Установление соответствия между полным именем объекта и ссылкой на него. Метод rebind контекста именования отличается от его метода bind тем, что при конфликте имен он будет разрешен в пользу нового сопоставления.
  • import org.omg.PortableServer.*;
    import org.omg.CosNaming.*;
    import org.omg.CORBA.*;
    public class Server  
    {
    public static void main(String[]   args)   
    {
    try 
    	{
    	ORB orb = ORB.init(args,null);
    	POA rootPOA = (POA)orb.resolve_initial_references("RootPOA");
    	rootPOA.the_POAManager().activate();
    	MessagingServiceImpl messagingServant = new MessagingServiceImpl(orb);
    	org.omg.CORBA.Object rootObj =orb.resolve_initial_references("NameService"); 
    	NamingContext root = NamingContextHelper.narrow(rootObj); 
    	NameComponent[] path ={new NameComponent("MessagingService",   "")}; 
    	org.omg.CORBA.Object ref =rootPOA.servant_to_reference(messagingServant); 
    	root.rebind(path,  ref); 
    	System.out.println("Ready"); 
    	orb.run(); 
    	}   
    catch   (Exception e)   
    	{
    	e.printStackTrace();
    	}
    }

    Класс серванта по сравнению с предыдущим примером остается неизменным. Изменения в клиенте аналогичны изменениям в сервере. Метод resolve контекста именования позволяет получить ссылку на CORBA -объект по его полному имени.

    public static void main(String[]   args)   
    { 
    try 
    	{
    	ORB orb = ORB.init(args,null); 
    	org.omg.CORBA.Object rootObj  = orb.resolve initial references("NameService"); 
    	NamingContext root = NamingContextHelper.narrow(rootObj);
    	NameComponent[]   path = {new NameComponent("MessagingService", "")};
    	org.omg.CORBA.Object msgObj  = root.resolve(path); 
    	Message.MessagingService service =Message.MessagingServiceHelper.narrow(msgObj); 
    	MessagingFrame mf = new MessagingFrame(service);
    	MessageReceiverlmpl mr = new MessageReceiverlmpl(mf);
    	POA rootPOA =   (POA)orb.resolve_initial_references("RootPOA");
    	rootPOA.the_POAManager().activate();
    	Message.MessageReceiver receiver =
    		Message.MessageReceiverHelper.narrow(
    	rootPOA.servant_to_reference(mr));
    	LoginFrame loginFrame
    	new LoginFrame(service,  mf,   orb.object to string(receiver));
    	loginFrame.show();
    	}
    catch   (Throwable t)   
    	{
    	t.printStackTrace();
    	}
    }

    При использовании сервиса имен необходимо использовать дополнительные параметры командам запуска сервера и клиента. Кроме того, необходимо запустить сам сервис имен. Это делается следующим образом:

    start nameserv -J-Dvbroker.se.iiop_tp.scm.iiop_tp.listener.port=<port> NameService
    start vbj Server -ORBInitRef NameService=iioploc://<host>:<port>/NameService
    start vbj Client -ORBInitRef NameService=iioploc://<host>:<port>/NameService

    Здесь host - идентификатор узла, на котором запущен сервис имен. Порт может быть любым свободным, но должен согласовываться при запуске сервиса имен, клиента и сервера.

    Сервер на C++

    Данный пример демонстрирует взаимодействие компонентов, реализованных на различных языках программирования. А именно, сервер службы мгновенных сообщений реализован на С++.

    Для корректного взаимодействия прямая передача ссылки на CORBA -объект получателя сообщений заменена передачей IOR. Это приводит к изменению в IDL -описании (измененное описание используется в этом и предыдущем примерах):

    interface MessagingService  
    {
    unsigned long login(in string userName, in string password, in string receiverlOR);
    };

    Приведем реализацию сервера на С++. Функциональность, по сравнению с реализацией на Java,не изменилась.

    #include "CosNaming c.hh" #include "Messlmpl.h"
    USE_STD_NS
    int main(int argc,   char* const* argv)
    {
    try 
    	{
    	// Инициализируем брокер объектных запросов 
    	CORBA::ORB_var orb = CORBA::ORB_init(argc,   argv); 
    	// Получаем ссылку на корневой объектный адаптер
    	PortableServer::POA_var rootPOA = 
    		PortableServer::POA::_narrow( orb->resolve_initial_references("RootPOA"));
    
    	// Определяем политику для дочернего объектного адаптера 
    	CORBA::PolicyList policies; 
    	policies.length(l); 
    	policies[(CORBA::ULong)0]   =
    		rootPOA->create_lifespan_policy(PortableServer::PERSISTENT); 
    		
    	// Получаем ссылку на мэнеджер объектных адаптеров 
    	PortableServer::POAManager_var poa_manager = rootPOA->the_POAManager();
    	
    	// Создаем дочерний адаптер
    	PortableServer::POA_var myPOA = rootPOA->create_POA("messaging_poa",  poa_manager,policies);
    	
    	// Создаем сервер   (реализацию объекта) 
    	MessagingServiceImpl messagingServant(orb);
    	
    	// Получение идентификатора серванта 
    	PortableServer::ObjectId_var messagingId =PortableServer::string_to_ObjectId("MessagingService"); 
    
    	// Активация идентификатора серванта в дочернет объектном адаптере 
    	myPOA->activate object with id(messagingId,   messagingServant);
    	
    	// Активация мэнеджера объектных адаптеров 
    	poa_manager->activate(); 
    	CORBA::Object_var reference =
    		myPOA->servant to reference(messagingServant); 
    	
    	// Получаем ссылку на сервис имен 
    	CosNaming::NamingContext_var rootContext = 
    		CosNaming::NamingContext::_narrow(orb->resolve_initial_references("NameService")); 
    
    	// Регистрация в сервисе имен 
    	CosNaming::Name name; 
    	name.length(l);
    	name[0].id = (const char *) "MessagingService"; 
    	name[0].kind = (const char *) ""; 
    	rootContext->rebind(name, reference); 
    	cout << reference << " is ready" << endl; 
    	// Ожидание соединений orb->run(); 
    	}   
    catch(const CORBA::Exception e)   
    	{ cerr << e << endl;
    	return l;
    	}
    return 0;
    }

    Ниже приведен частичный листинг серванта на С++ (полную версию можно найти в электронном варианте):

    #include "message s.hh" 
    #include <math.h> 
    #include <string> 
    #include <vector> 
    #include <map>
    
    USE_STD_NS
    
    class Msg  {...};     // Сообщение
    class User  {...};   // Информация о клиенте
    class MessagingServiceImpl : public virtual POA_Message::MessagingService,
    public virtual PortableServer::RefCountServantBase
    {
    public:	MessagingServiceImpl(CORBA::ORB_var _orb)   : orb(_orb)   {}
    CORBA::ULong registerUser(const char* _userName, const char* _password)
    	{
    	string userName(_userName); 
    	string password(_password);
    	if (nameTold.find(userName) != nameToId.end())   
    		{ 
    		std::cout << "User " << userName <<" already registered" << std::endl; 
    		return (CORBA::ULong)  0; 
    		}  
    	else  
    		{
    		int uid = nameToId.size(); 
    		nameToId[userName]  = uid; 
    		User u(userName,  password); 
    		users.push_back(u); 
    		notifyAllUsers(++uid, false); 
    		std::cout << "User " << userName <<" registered successfully,  user ID is  " <<
    		uid << std::endl;
    		return (CORBA::ULong) uid;
    		}
    	private:	
    		CORBA::ORB_var orb;
    		void notifyAllUsers(int uid,  bool online)
    			{
    			string userName = users[uid - l].name; 
    			for   (int i = 0;  i < users.size();  ++i)   
    				{
    				if (i != uid - l) 
    				{
    				User buddy = users[i]; 
    				if (buddy.online)   
    					{
    					buddy.receiver->userStatusNotification( uid, userName.c_str(), online);
    					}
    				}
    				}
    			}
    	map<string, int> nameToId; 
    	vector<User> users;
    };

    Запуск сервера осуществляется следующей командой:

    start Server -ORBInitRef NameService=iioploc://<host>:<port>/NameService

    Пример "Банковская система"

    Общее описание

    Разработаем простейшую банковскую систему, поддерживающую следующий набор функций:

  • хранение информации о счетах клиентов;
  • выдачу сведений по запросу авторизованных клиентов;
  • модификация счетов (снятие/зачисление денег, перевод на другой счет) авторизованными клиентами;
  • открытие и закрытие счетов;
  • выдачу отчетов о транзакциях за заданный клиентом период времени.
  • Система должна обеспечивать актуальность данных, отображаемых пользователю, а так же обеспечивать безопасность работы: перевод денег на счет может осуществляться любым клиентом, а снятие денег, перевод со счета и закрытие счета - только его владельцем. В качестве ORB используется Borland $$\text{\textregistered}$$ Visibroker.

    Создание таблиц в базе данных

    (рис 2.8) Мифологическая модель базы данных

    Информация о счетах клиентов и транзакциях будет храниться в базе данных, инфологическая модель которой приведена на Рис. 2.8. Эта база данных состоит из двух основных таблиц:

  • таблицы accounts, хранящей информацию о текущем состоянии счетов клиентов. В таблице хранятся имя клиента ( name ), пароль для доступа к счету ( password ), текущий остаток на счете ( balance ) и признак закрытия счета ( closed );
  • таблицы transactions, хранящей информацию обо всех транзакциях. В таблице хранятся дата и время проведения транзакции ( transaction_date ), сумма ( amount ) и комментарий к транзакции ( transactioncomment ). Номера счетов источника ( source_account_id ) и получателя ( dest_account_id ) транзакции связаны с таблицей accounts по внешнему ключу.
  • Для того, чтобы создать таблицы в базе данных Oracle 9i,необходимо выполнить следующий SQL-код.Помимо создания таблиц в базе данных, данный SQL-код создает процедуру, сообщающую серверу системы о появлении новых записей в таблице transactions и триггер, инициирующий выполнение этой процедуры. Так же для каждой таблицы определен триггер, автоматически генерирующий поле id для каждой добавленной строки таблицы.

    delete from transactions;
    drop table transactions;
    drop sequence transactions id;
    
    delete from accounts;
    drop table accounts;
    drop sequence accounts id;
    
    create table accounts   (id number,
    name varchar2(12 8), password varchar2(12 8), balance number default 0, closed char(1),
    constraint accounts pk primary key   (id));
    
    create table transactions (id number, transaction date number, source account id number, 
    	dest account id number, amount number, transaction comment varchar2(12 8), 
    	constraint transactions pk primary key (id), 
    	constraint source account fk foreign key (source account id) references accounts(id),
    	constraint dest account fk foreign key (dest account id)   references accounts(id));
    
    create sequence transactions id start with 1 increment by 1; 
    create sequence accounts id start with 1 increment by 1;
    
    create or replace and compile java source named "Trigger" as
    
    import java.net.*;
    import java.io.*;
    import java.math.BigInteger;
    
    public class Trigger  
    {
    private static java.net.Socket s = null; 
    private static OutputStream outputStream = null;
    public static synchronized void trig(long id,   String host,   int port)   
    	{
    	try 
    		{
    		byte[]   b = new byte[8];
    		for (int i = 0;  i < 8;  ++i)
    			{
    			b[i] = ( byte )(id % 256); 
    			id >>= 8;
    			}
    		if (connect(host, port)) 
    			{ 
    			outputStream.write(b); 
    			outputStream.flush();
    			}
    		}   
    	catch   (Exception e)   
    		{
    		System.out.println("Failed"); e.printStackTrace();
    		}
    	}
    private static boolean connect(String host,   int port)   throws Exception  
    	{
    	if ( s == null )
    		s = new Socket( host,  port  );
    	outputStream = s.getOutputStream();
    	return true;
    	public static void disconnect()   
    		{
    		try 
    			{
    			s.close(); 
    			s = null; 
    			}   
    		catch   (Exception e)   
    			{}
    		}
    	}
    
    /
    show errors java source "Trigger"
    create or replace procedure account changed(accountId number, host varchar2, port number)   as
    language java name   'Trigger.trig(long,   java.lang.String,   int)';
    
    /
    create or replace procedure disconnect as
    
    language java name   'Trigger.disconnect()';
    /
    
    create or replace trigger transactions trigger after insert on transactions
    referencing new as new for each row 
    begin
    account changed( rnew.source account id, 'smal', 12345); 
    account changed( rnew.dest account id,  'smal', 12345); 
    end;
    
    /
    
    show errors

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

    Интерфейсы системы

    Клиентским приложениям система доступна посредством интерфейсов Bank и Account и структуры Transaction. Кроме того, посредством интерфейса AccountEvents сервер сообщает клиентам об изменениях на счете. Для сообщения об ошибках добавлены исключения BankException и AccountException, содержащие информацию об ошибках в работе системы.

    Интерфейс Bank

    Это первый интерфейс, получаемый клиентами, после подключения к серверу системы. Интерфейс описан в IDL-файле следующим образом:

    interface Bank 
    {
    AccountId CreateAccount(  in wstring usrName,     in wstring usrPasswd ) raises(BankException);
    Account	OpenAccount ( in AccountId id,	in wstring usrPasswd )
    raises(BankException);
    void	CloseAccount (in AccountId id,	in wstring usrPasswd )
    raises(BankException);
    };

    Посредством этого интерфейса клиент может залогиниться в систему ( OpenAccount ), открыть новый ( CreateAccount ) или закрыть существующий ( CloseAccount ) счет.

    Класс интерфейса Bank

    Класс интерфейса Bank существует в единственном экземпляре все время жизни сервера. Следующий код добавляет объект этого класса в POA,делая его доступным внешним приложениям:

    CORBA::Object var obj  = orb->resolve initial references("RootPOA"); 
    PortableServer::POA var rootPOA = PortableServer::POA::  narrow(obj);
    
    CORBA::PolicyList policies;
    policies.length(1);
    
    policies[(CORBA::ULong)0]   = rootPOA->create lifespan policy(PortableServer::PERSISTENT );
    
    // get the POA Manager
    PortableServer::POAManager var poa manager = rootPOA->the POAManager();
    PortableServer::POA var
    bankPOA = rootPOA->create POA(   "banking poa",  poa manager,  policies  );
    const int MAXBUF = 1024; char ior[MAXBUF];
    
    // Convert from string to object 
    std::ifstream in(argv[1]); 
    if   (   !in )
    throw std::runtime error(  std::string("Can't open file \"")   + argv[1] + "\""  );
    in.getline(ior,  MAXBUF);
    
    // Create the servant
    ParamsPtr params = ReadParams();
    BankImpl bankServant(orb,   ior,   *params);
    
    // Decide on the ID for the servant
    PortableServer::ObjectId var bankId = PortableServer::string_to_ObjectId("Bank");
    
    // Activate the servant with the ID on bankPOA
    bankPOA->activate object with id(bankId,   bankServant);
    
    // Activate the POA Manager 
    poa manager->activate();
    CORBA::Object var reference = bankPOA->servant to reference(  bankServant);
    
    // Wait for incoming requests
    orb->run();

    Класс BankImpl реализует интерфейс Bank, а так же занимается информированием клиентов об изменении их счетов. Для этого в члене channels хранится отображение номеров счетов на открытые в настоящий момент соединения с сервером. Фактически, основную работу выполняет класс database::Bank, который непосредственно модифицирует базу данных. Фактически, объект класса BankImpl хранит указатель на объект класса database::Bank и делегирует ему запросы клиентского приложения на работу со счетом.

    ::CORBA::LongLong BankImpl::CreateAccount(  const wchar t*    usrName,   const wchar t*    usrPasswd )
    {
    database::AccountId id;
    CheckResult( bank ->CreateAccount(    usrName,    usrPasswd,   id )   ); 
    return id;
    }
    
    banking::Account ptr BankImpl::OpenAccount(   ::CORBA::LongLong    id,   const wchar t*    usrPasswd )
    {
    database::AccountPtr db acc;
    CheckResult( bank ->OpenAccount(id, usrPasswd, db acc ));
    AccountsMap::iterator it = accounts.find(id );
    if (it == accounts.end())
    	{
    	VISMutex var lock(channelsMutex);
    	std::string const channelIOR  (  CreateChannel( id )); 
    	AccountImpl * servant = new AccountImpl( db acc,   channelIOR ); 
    	CORBA::Object var ref (accountPOA ->servant to reference(servant));
    	servant-> remove ref();
    	banking::Account var account = banking::Account:: narrow(ref); 
    	it = accounts.insert(  std::make pair(    id,   account  )   ).first;
    	}
    return banking::Account::  duplicate(it->second);
    }
    
    void BankImpl::CloseAccount(::CORBA::LongLong    id,   const wchar t*    usrPasswd)
    {
    CheckResult( bank ->CloseAccount( id, usrPasswd ));
    }

    Упомянутая в коде функция CheckResult формирует исключения в случае, если запрос к базе данных завершился неудачей. Кроме того, класс BankImpl сообщает клиентам об операциях, затрагивающих их счет, формируя сообщения в канал сообщений.

    void BankImpl::OnDBChanged( database::AccountId id )
    {
    VISMutex var lock(  channelsMutex    );
    EventChannelsMap::const iterator it = channels.find(id );
    if (it != channels .end()   )
    it->second->add message(  id );
    }

    Интерфейс Account

    Интерфейс Account позволяет получать информацию о текущем состоянии счета, переводить средства на другой счет и запрашивать у сервера список транзакций. В IDL- файле этот интерфейс описан следующим образом:

    interface Account
    {
    readonly attribute wstring	HolderName;
    readonly attribute Money	Balance;
    readonly attribute AccountId	Id;
    readonly attribute string	ChannelIOR;
    TransactionList      Transactions();
    Transaction	ProcessTransaction(  in AccountId dest, in Money amount,   in wstring comment  )
    
    raises(AccountException)
    };

    Класс интерфейса Account

    Интерфейс Account реализуется объектами класса AccountImpl. Как и в случае класса BankImpl, объекты данного класса не взаимодействуют с базой данных напрямую, а делегируют вызовы объекту промежуточного класса. Кроме того, в момент первичного создания счета на него помещается стартовая сумма в 1000 денежных единиц. Полный код класса BankImpl приведен ниже.

    class AccountImpl
    :  public virtual POA banking::Account
    ,  public virtual PortableServer::RefCountServantBase
    {
    public:
    AccountImpl( database::AccountPtr account, std::string const channelIOR )
    :   account	(account)
    ,   channelIOR_ (channelIOR )
    	{
    	account_->PutMoney(  1000,   L"Initial balance", 0); 
    	std::cout << "Account created" << std::endl;
    	}
    ~AccountImpl() 
    	{
    	std::cout << "Account destructed" << std::endl;
    	}
    
    
    virtual wchar_t* 			HolderName() 	{ return CORBA::wstring_dup( account_->HolderName() ); }
    virtual ::CORBA::Float 		Balance() 	{ return account_->Balance(); }
    virtual ::CORBA::LongLong 	Id() 		{ return account_->Id(); }
    virtual char * 			ChannelIOR() 	{ return ORBA::string_dup(channelIOR_.c_str() ; }
    
    virtual banking::TransactionList* Transactions() 
    {
    database::TransactionsListPtr tl = account_->Transactions();
    banking::TransactionList_var res = new banking::TransactionList; 
    res->length(  tl->size() );
    CORBA::ULong i = 0;
    
    for ( database::TransactionsList::const_iterator it = tl->begin();  it != tl->end();  ++it )
    	{
    	res[i]=CreateTransaction( *it );
    	++i;
    	return res._retn();
    	}
    
    virtual banking::Transaction * ProcessTransaction( ::CORBA::LongLong _dest, ::CORBA::Float amount, const wchar t* comment)
    	{
    	database::Transaction trans;
    	CheckResult(  account_->ProcessTransaction( _dest,  _amount,  _comment, trans));
    	return new banking::Transaction(  CreateTransaction(  trans ));
    	}
    
    private:
    database::AccountPtr const account_;
    std::string	const channelIOR_;
    };

    Структура Transaction

    Структура Transaction содержит информацию об отдельно проведенной транзакции, затрагивающей текущий счет. Фактически, структура содержит одну строку из таблицы transactions. В IDL -файле эта структура описана следующим образом:

    struct Transaction
    {
    TransactionId id;
    Money amount;
    Time date; 
    AccountId source;
    AccountId destination;
    wstring comment; 
    };

    Классы работы с базой данных

    Все классы работы с базой данных вынесены в отдельное пространство имен database. Эти классы реализуют набор интерфейсов, практически полностью повторяющий внешние

    интерфейсы системы, что позволяет достаточно прозрачным для серверного приложения

    образом менять способ хранения данных. Рассмотрим код работы с базой данных на примере метода database ::AccountImpl:: ProcessTransaction. Код работы с SQL- сервером выделен жирным.

    ACCOUNT RESULT ProcessTransaction( AccountId dest, Money amount, String comment, Transaction * trans  )
    if (!IsAccountValid(  conn ,dest))
    	return AR_INVALID_ACCOUNT_ID;
    if  ( Balance() - amount < 0 )
    	return AR_NO_ENOUGH_MONEY;
    
    TransactionId id = ( conn_->Execute() << "select transactions_id.nextval from dual")->Fields->
    	GetItem("nextval")->Value;conn_->BeginTransaction();conn_->Execute() <<"update accounts set 
    	balance=(select balance+(" << amount<< ")   
    	from accounts where id=" << dest << ")  
    	where id=" << dest;conn_->Execute() << "update accounts set balance=(select balance-(" << amount<< ") 
    	from accounts where id=" << id_ << ")  
    where id=" << id_;conn_->Execute()  << "insert into transactions(id,source_account_id, 
    	dest_account_id, "<< "amount,  transaction_comment,  transaction_date)  
    	values ("<< id << ", " << id_ << ", 
    	" << dest << ", " << amount << ",'" << comment << "', 
    	"<< time( NULL )  << ")";conn_->CommitTransaction();
    if (trans)
    	*trans = ReadTransaction( id );
    return AR_OK;
    }

    Остальные классы пространства имен database взаимодействуют с базой данных аналогичным образом.

    Получение сообщений от базы данных о новых транзакциях

    В описании кода создания базы данных было уделено внимание передаче серверу сообщения о появлении новых записей в таблице transactions. Теперь рассмотрим код, принимающий эти сообщения. Ниже полностью приведен текст функции, работающей в отдельном потоке и получающей сообщения от сервера базы данных.

    DWORD 	stdcall ServerProc(  LPVOID data  )
    {
    database::IOnDBChanged * consumer = reinterpret_cast<database::IOnDBChanged *>(data);
    SOCKET sock = socket( AF_INET,   SOCK_STREAM,   IPPROTO_TCP  ); 
    hostent * localHost = gethostbyname(   ""  );
    char* localIP = inet_ntoa(  *(struct in addr *)*localHost->h_addr_list  );
    sockaddr_in saServer; 
    saServer.sin_family = AF_INET;
    saServer.sin addr.s_addr = inet addr(  localIP  ); 
    saServer.sin port = htons(  12345  );
    int res = bind(  sock,   (  sockaddr *  )   saServer,   sizeof(  saServer )   );
    assert( res != SOCKET_ERROR );
    res = listen(  sock,   SOMAXCONN );
    assert( res != SOCKET_ERROR );
    fd_set readfds; 
    FD_ZERO(  readfds  ); 
    FD_SET(  sock,   readfds  );
    
    while   (   !endListenSocket  select(  0,   readfds,  NULL,  NULL,  NULL )   != SOCKET_ERROR )
    	{
    	SOCKET incoming = accept(  sock,   0,   0  );
    	static const int SIZE = sizeof(database::AccountId); 
    	char buf[SIZE];
    	while (( res = recv( incoming, buf, SIZE, 0)) > 0) 
    		{
    		database::AccountId id = *reinterpret_cast< database::AccountId * >(buf );
    		consumer->OnDBChanged( id );
    		}
    	closesocket( incoming );
    	FD_ZERO( readfds  ); 
    	FD_SET( sock, readfds  );
    	}
    return 0;
    }

    Первые строки этой функции получают -адрес сервера банковской системы и создают слушающий сокет. Затем при поступлении соединения читаются 8 байт, формирующих номер счета участника транзакции, и, наконец, вызывается метод сервера банковской системы, который уже шлет сообщение об изменении счета клиентскому приложению.

    Клиентская часть

    Рабочее место оператора представляет собой standalone java -приложение. На рабочем месте оператора должна быть установлена Java и VisiBroker для поддержки событий с сервера. При запуске приложения, оно подключается к CORBA-серверу для получения банковского интерфейса (Рис. 2.9).

    (рис 2.9) Окно входа в систему
    byte[] bankId = "Bank".getBytes();
    orb_ = org.omg.CORBA.ORB.init(args,  null);
    remoteBanking = banking.BankHelper.bind(orb , "/banking poa", bankId);
    try 
    	{
    	org.omg.CORBA.Object obj  = orb.resolve_initial_references("RootPOA"); 
    	rootPOA = POAHelper.narrow(obj); 
    	}   
    catch   (InvalidName invalidName)   
    	{ invalidName.printStackTrace();
    	}

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

    (рис 2.10) Окно регистрации нового пользователя
    try 
    {
    acc = clientApplet_.getRemoteBanking().OpenAccount(account,  new String(PINPasswordField.getPassword()));
    }   
    catch   (BankException el)   
    {
    JOptionPane.showMessageDialog(clientApplet , el.message, "Error", JOptionPane.ERROR_MESSAGE);
    return;
    }

    В случае проблем с авторизацией на сервере произойдет Exception, который будет обработан на клиенте и будет показано соответствующее сообщение, описывающее причину отказа.

    При регистрации нового клиента (Рис. 2.10) производится соответствующий вызов банковского интерфейса.

    long accId = clientApplet.getRemoteBanking().CreateAccount( accountTextField.getText(), new String(PINPasswordField.getPassword()));
    
    JOptionPane.showMessageDialog(clientApplet, "Write down and remember your unique account number : 
    	" + accId, "Succeded", JOptionPane.INFORMATION_MESSAGE);

    При успешной авторизации клиента отображается список транзакций совершенных данным клиентом (Рис. 2.11).

    (рис 2.11) Основное окно клиентской программы

    Для обработки событий сервера и обновления списка транзакций используется PushViewModel

    pv = new PushView(clientApplet.getOrb(), account.ChannelIOR())   
    {
    public void push(org.omg.CORBA.Any data)   throws Disconnected 
    	{
    	UpdateAccountTable();
    	}
    public void disconnect push consumer()   
    	{
    	System.out.println("View.disconnect push consumer");
    	}
    };

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

    Обработка IDL-файла

    IDL-файл в чистом виде не пригоден для компиляции ни компиляторами языка С++,ни компиляторами Java.Для создания файлов, обрабатываемых этими компиляторами, необходимы дополнительные инструменты, которые "переведут" IDL -описания интерфейсов на язык, понятный компилятору. Для C++ соответствующий инструмент называется midl, а для Java - idl2java.Запуск midl производится средой разработки Microsoft $$\text{\textregistered}$$ Visual Studio™ 2005,а для генерации Java -файлов создан пакетный файл, который вызывает idl2java с корректно установленными путями.

    Java Stored Procedures

    В коде создания базы данных был упомянут интересный фрагмент, начинающийся со строки create or replace and compile java source named "Trigger" as.Данный фрагмент иллюстрирует очень мощную особенность СУБД Oracle,называемую хранимыми процедурами на языке Java (Java Stored Procedures).Эти процедуры исполняются виртуальной машиной Java,встроенной в сервер базы данных. Хранимые процедуры на Java и классы, их содержащие, должны отвечать следующим требованиям:

  • не должно быть конструкторов;
  • переменные и методы должны быть объявленными как static;
  • необходимо использовать текущее соединение с базой данных (не требуется имя пользователя и пароль);
  • необходимо объявить output -переменные как массивы.
  • Затем эти хранимые процедуры могут вызываться из триггеров, а так же их выполнение может быть инициировано приложениями, подключенными к базе данных.

    Тестовый клиент

    В процессе отладки системы был создан тестовый клиент на языке C++, который создает два счета и переводит в несколько приемов средства с одного из этих счетов на уведомления от сервера об изменении счета. Полный исходный код тестового клиента может быть найден в каталоге с исходными текстами системы. Вывод тестового клиента в процессе работы приведен на Рис. 2.12.

    (рис 2.12) Окно тестового клиента

    Состояния базы данных до и после выполнения тестового клиента приведены на Рис. 2.13 и Рис. 2.14 соответственно

    (рис 2.13) База данных до запуска тестового клиента(рис 2.14) База данных после запуска тестового клиента

    Пример "Книжный магазин "BookStore""

    Общее описание

    Разработаем систему заказов для книжного магазина, позволяющую:

  • обновлять и корректировать информацию о книгах, хранящуюся в базе данных (добавление новых книг, изменение цен и т.д.);
  • выполнять заказы клиентов и информировать их о произошедших изменениях;
  • обновлять информацию о книгах при ее изменении в базе данных.
  • Создание таблиц в базе данных

    Следующий код создает данную таблицу:

    create table BookStore
    (
    id	int primary key,
    name	varchar(255),
    author	varchar(255),
    publisher	varchar(255),
    price	float
    );

    Интерфейс системы

    Для связи СОRBA -объектов необходимо задать IDL -интерфейс, позволяющий выполнять запросы клиента и обеспечивать доступ к книгам и их модификацию.

    struct Book  
    {
    long	id;
    string	name;
    string	author;
    string	publisher;
    float price;
    }
    typedef sequence <Book> BookList;
    interface User  
    {
    BookList  getBookList ();
    string	getAdmin	();   // ior
    };
    
    interface Admin  
    {
    long	updateBook	(in Book book );
    };

    Структура Book позволяет описывать и хранить информацию о книге. Интерфейс User имеет два метода:

  • BookList getBookList (), данный метод возвращает список имеющихся на сервере книг, их названия, цену и прочую информацию;
  • string getAdmin (), данный метод возвращает строковое представление ior объекта для получения доступа к интерфейсу Admin.
  • Интерфейс Admin имеет единственный метод updateBook позволяющий добавлять новые книги и редактировать информацию о них.

    Обработка IDL-файла

    После описания IDL -интерфейс, необходимо сгенерировать вспомогательные классы для целевого языка, позволяющие реализовать методы интерфейса и предоставить их во внешнее пользование.

    Для клиента, написанного на Java,используется ORB из стандартной поставки JDK.

    idlj.exe -fall Server.idl

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

    Для сервера, написанного на C++,используется ORB из библиотеки ACE/TAO,соответственно, для генерации вспомогательных классов используется специальное приложение:

    tao_idl.exe -GI Server.idl

    Опция -GI позволяет сгенерировать дополнительные ServerI.h/.cpp файлы, в которых описан каркас реализации серванта, и остается лишь реализовать его методы. Полный список сгенерированных файлов при запуске указанной команды:

  • ServerC. h/.cpp - файлы, предоставляющие доступ к IDL -интерфейсам (аналогичные файлы используются в клиенте, реализованном на Java);
  • ServerS. h/. cpp - файлы, описывающие абстрактные классы необходимые для реализации сервантов;
  • ServerI.h/.cpp - каркас реализации серванта.
  • Получение доступа к CORBA-интерфейсу

    Клиенты, посылающие запросы к серверу реализованы на языке Java.В качестве ORB взят стандартный ORB, входящий в Java SDK.

    Для получения доступа к интерфейсу сервера, клиенту необходимо выполнить следующие действия.

  • Инициализация ORB и активизация POA -менеджера
    // create and initialize the ORB 
    ORB orb = ORB.init(args,  null);
    POA rootpoa =   (POA)orb.resolve_initial_references("RootPOA"); 
    rootpoa.the_POAManager().activate();
  • Получение доступа к объекту сервиса имен
    // get the root naming context
    org.omg.CORBA.Object objRef = orb.resolve initial references("NameService");
    // Use NamingContextExt instead of NamingContext.  This is 
    // part of the Interoperable naming Service.
    NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
  • Получение доступа к интерфейсу пользователя с помощью сервиса имен
    String name	= "BookStore.User";
    corbaUser     = UserHelper.narrow(ncRef.resolve_str(name));
  • Получение доступа к интерфейсу администратора с помощью значения ior
    String adminlor = corbaUser.getAdmin();
    org.omg.CORBA.Object adminObj  = orb.string_to_object(adminlor); 
    corbaAdmin = AdminHelper.narrow(adminObj);
  • Обратная связь. Сервис событий

    При изменении данных книги, сервер должен оповестить клиентов. Для этого можно использовать сервис событий (сервис EventService,входящий с состав пакета ACE/TAO).Для запуска сервиса событий необходимо выполнить следующие действия.

  • Получение доступа к объекту сервиса событий
    CORBA::Object var eventServiceObj = namingContext->resolve str("CosEventService"); 
    EventChannel_var eventChannel = EventChannel::_narrow(eventServiceObj.in());
  • Активизация сервиса и получение объекта поставщика событий
    SupplierAdmin_var supplierAdmin = eventChannel->for_suppliers(); 
    // Get a ProxyPushConsumer from the SupplierAdmin. 
    ProxyPushConsumer_var consumer =supplierAdmin->obtain_push_consumer(); 
    
    // Connect to the ProxyPushConsumer as a PushSupplier 
    //   (passing a nil PushSupplier object reference to it because 
    // we don't care to be notified about disconnects).
    consumer->connect_push_supplier(  CosEventComm::PushSupplier::_nil());
  • Для генерации события необходимо создать объект события и передать его поставщику событий:
    const CORBA::String var notificationMsg = CORBA::string_dup("BookStore.Notify"); 
    	  CORBA::Any any; any <<= notificationMsg; consumer->push(any);
  • Обработка событий

    На стороне клиента необходимо зарегистрировать обработчик событий. Для этого достаточно реализовать вспомогательный класс (в данном проекте это класс EventNotify ), унаследованный от базового класса - обработчика событий CosEventComm.PushConsumerPOA и реализующий методы, необходимые для обработки поступающих сообщений. Для регистрации обработчика событий необходимо:

  • Получить доступ к объекту сервиса событий
    // Find the EchoEventChannel.
    org.omg.CORBA.Object eventServiceObj = ncRef.resolve str("CosEventService"); 
    EventChannel echoEC =EventChannelHelper.narrow(eventServiceObj);
  • Создать обработчик событий
    // Instantiate an EchoEventConsumer_i servant. 
    notifier = new BookStore.Server.EventNotify(orb,   this); 
    // Register it with the RootPOA.
    byte  []  oid = rootpoa.activate object(notifier); 
    org.omg.CORBA.Object consumer_obj  =rootpoa.id_to_reference(oid); 
    CosEventComm.PushConsumer consumer =
    CosEventComm.PushConsumerHelper.narrow(consumer_obj);
  • Зарегистрировать обработчик событий
    // Get a ConsumerAdmin object from the EventChannel. 
    CosEventChannelAdmin.ConsumerAdmin consumerAdmin =echoEC.for_consumers(); 
    
    // Get a ProxyPushSupplier from the ConsumerAdmin. 
    CosEventChannelAdmin.ProxyPushSupplier supplier = consumerAdmin.obtain_push_supplier(); 
    
    // Connect to the ProxyPushSupplier,  passing our PushConsumer 
    // object reference to it.
    supplier.connect_push_consumer(consumer);
  • Связь с базой данных

    Для работы с базой данных сервер BookStore использует ODBC (Open Database Connectivity) драйвер. Покажем работу сервера с базой данных на примере запроса получения списка книг.

  • Инициализация драйвера и окружения ODBC
    SQLHENV henv;
    SQLAllocHandle(SQL_HANDLE_ENV,   NULL,   henv); 
    // tell to use 3rd ODBC version
    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3,
    SQL_IS_INTEGER); 
    SQLHDBC hdbc;
    SQLAllocHandle(SQL_HANDLE_DBC, henv, hdbc);
  • Соединение с базой данных
    SQLDriverConnect(hdbc, NULL, (SQLCHAR*)dsn, SQL_NTS,
    outStr, MAX_NUM, outStrLen, SQL_DRIVER_COMPLETE);

    При соединении указывается DSN (Data Source Name) - имя источника данных, установленных на компьютере, либо специфичные для конкретного драйвера параметры соединения.

  • Отправка запроса
    SQLAllocHandle(SQL_HANDLE_STMT, hdbc, hstmt);
    static SQLCHAR querySql[] = "select id,  name, author, publisher,  price from BookStore";
    SQLExecDirect(hstmt, querySql, SQL_NTS);
  • Обработка результатов запроса
    SQLBindCol(hstmt, i + 1, SQL_C_SLONG, (SQLPOINTER)uid, sizeof(uid)); 
    SQLBindCol(hstmt, i + 1, SQL_C_CHAR, (SQLPOINTER)name,  MAXLEN);
    SQLBindCol(hstmt, i + 1, SQL_C_CHAR,(SQLPOINTER)author, MAXLEN); 
    SQLBindCol(hstmt, i + 1, SQL_C_CHAR, (SQLPOINTER)publisher, MAXLEN); 
    SQLBindCol(hstmt, i + 1, SQL_C_DOUBLE, (SQLPOINTER)price, sizeof(price)); 
    for   (;;)   
    	{
    	if   (SQLFetch(hstmt)   == SQL_NO_DATA)   
    		{ break;
    		}
    	// process book info
    	}
  • Завершение обработки запроса
    SQLCloseCursor(hstmt);
  • Обратная связь: Oracle AQ

  • Создадим пользователя с правами на управление AQ
    create user BookAdmin identified by "12345" default tablespace USERS temporary tablespace TEMP;
    grant CONNECT, RESOURCE, CREATE SESSION, aq_administrator_role TO bookadmin; 
    grant EXECUTE on SYS.DBMS_AQ to BookAdmin;
    exec DBMS_AQADM.GRANT_SYSTEM_PRIVILEGE(privilege =>	'ENQUEUE_ANY', grantee =>'BookAdmin', admin option => FALSE);
    
    exec DBMS_AQADM.GRANT_SYSTEM_PRIVILEGE(privilege =>	'DEQUEUE_ANY', grantee =>'BookAdmin', admin option => FALSE);
  • Зайдем в Oracle под этим пользователем
    connect BookAdmin/12345;
  • Создадим очередь событий
    create type EventType as object(type int,   text varchar2(2000));
    exec DBMS_AQADM.CREATE_QUEUE_TABLE(queue_table =>  'EventTable', queue payload type =>  'BookAdmin.EventType');
    exec DBMS_AQADM.CREATE_QUEUE(queue_name =>  'EventQueue',   queue_table => 'EventTable');
    exec DBMS_AQADM.START_QUEUE(queue_name =>  'EventQueue');
  • Создадим триггер, помещающий событие в очередь при добавлении в таблицу
    create or replace trigger inserttrigger after insert on BookStore declare
    queue_options	DBMS_AQ.ENQUEUE_OPTIONS_T;
    message_properties   DBMS_AQ.MESSAGE_PROPERTIES_T;
    message id	RAW(16);
    my_message	BookAdmin.EventType;
    begin
    my_message	:= BookAdmin.EventType(1, 'insert');
    
    DBMS_AQ.ENQUEUE(
    queue_name =>  'BookAdmin.EventQueue', enqueue_options => queue_options, 
    	message_properties => message_properties, payload => my_message, msgid => message_id);
    end;
  • Сгенерируем Java имплементацию для типа BookAdmin.EventType
    set ORA_HOME=C:\oracle\ora92\
    set CLASSPATH=%ORA_HOME%jdbc\lib\classes12.zip;%ORA_HOME%sqlj\lib\translator.zip ;%ORA_HOME%sqlj\lib\runtime.zip
    
    jpub -user=BookAdmin/12345 -sql=EventType -usertypes=oracle -methods=false
  • Напишем код на Java,извлекающий события из очереди AQ
    String URL = "jdbc:oracle:thin:@server:1521:DB";
    String USER = "BookAdmin"; 
    String PASSWORD = "12345"; 
    String QUEUE_NAME = "EventQueue"; 
    
    // Connect to DB
    Class.forName("oracle.jdbc.driver.OracleDriver"); 
    Connection connection =DriverManager.getConnection(URL, USER, PASSWORD);
    connection.setAutoCommit(false); 
    
    // Connect to AQ
    Class.forName("oracle.AQ.AQOracleDriver");
    AQSession session = AQDriverManager.createAQSession(connection); 
    AQQueue queue = session.getQueue(USER,  QUEUE_NAME); 
    
    // Dequeue AQ message
    AQMessage message = queue.dequeue(new AQDequeueOption(), EVENTTYPE.getORADataFactory());
    
    EVENTTYPE m =(EVENTTYPE) message.getObjectPayload().getPayloadData();
  • Работа приложения

    Для доступа к базе данных книжного магазина необходимо запустить клиентское приложение r_admin.bat (Рис. 2.15).

    В центре окна отображается список книг. Получение списка книг осуществляется посредством использования метода getBookList объекта User, предоставляемого сервером.

    (рис 2.15) Клиентское приложение в действии

    После ввода информации добавляемой книги, а так же при изменении уже существующих параметров книги (Рис. 2.16), вызывается метод updateBook объекта Admin, предоставляемого сервером.

    (рис 2.16) Редактирование информации о книге

    На стороне сервера для предоставления соответствующих IDL -интерфейсов необходимо запустить серверное приложение r_server.bat.После запуска необходимо дождаться сообщения о завершении инициализации данных: запуск ORB,регистрация сервантов в сервисе имен, связь с базой данных (Рис. 2.17).

    (рис 2.17) Консоль запущенного сервера

    Приложение: словарь терминов CORBA

    BOA (Basic Object Adapter) - стандарт объектного адаптера до CORBA 2.2 (недостаточно полно специфицированный).

    CORBA (Common Object Request Broker Architecture) - технология создания распределенных приложений; стандарт, разработанный Object Management Group (OMG); независимая от языка реализации модель взаимодействия распределенных объектов. Позволяет создавать запросы между объектами на разных языках программирования.

    CORBA-объект - виртуальное понятие: нечто, посылающее запросы к другим CORBA- объектам - серверным объектам и получающее запросы от других CORBA -объектов - клиентов.

    IDL (Interface Definition Language) - язык описания интерфейсов в формате, который не зависит от языка программирования.

    IIOP (Internet Inter-ORB Protocol) - протокол передачи объектных запросов по TCP/IP.

    IOR (Interoperable Object Reference) - ссылка на объект, уникальная в пределах сервера (как правило, содержит идентификатор объекта как составную часть).

    Java IDL - не вполне корректное название реализации CORBA для Java (содержит не только компилятор IDL ).

    Java-IDL компилятор - компилятор описаний IDL в классы-заглушки и вспомогательные классы Java.

    ORB (Object Request Broker) - программа-транслятор межобъектного взаимодействия; работая на клиенте и на сервере, передает объектные запросы между ними.

    POA (Portable Object Adapter) - стандарт объектного адаптера начиная с CORBA 2.2 (достаточно полно специфицирован, является платформенно-независимым).

    Smart Agent - административная утилита, осуществляет поиск объектов в домене и балансировку нагрузки.

    Активация CORBA -объекта - запуск существующего CORBA -объекта для обработки клиентских запросов (в зависимости от политик объектного адаптера предполагает создание сервантов,занесение в карту активных объектов,и т.д.).

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

    Временный (transient) CORBA-объект - объект, который уничтожается с завершением активировавшего его потока.

    Деактивация CORBA -объекта - останов CORBA -объекта (разрыв связки между объектом и сервантом, в общем случае без разрушения объекта).

    Демон активации объектов (Object Activation Daemon, OAD) - демон, отслеживающий входящие запросы и активизирующий нужные объекты-серверы.

    Идентификатор объекта (Object ID) - уникальное имя объекта внутри его объектного адаптера.

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

    Карта активных объектов (Active Object Map) - таблица объектного адаптера,в которой он ведет реестр активных CORBA -объектов и связанных с ними сервантов (первые представлены в карте своими идентификаторами).

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

    Объектный адаптер - элемент технологии CORBA,отображающий понятие программно

    реализованных сервантов на концепцию CORBA -объектов; в его обязанности входит: создание CORBA -объектов и их объектных ссылок; демультиплексирование запросов на каждый серверный CORBA -объект; перенаправление запросы к соответствующему серванту, который обеспечивает реализацию серверного CORBA -объекта; активация и деактивация CORBA -объектов (соответственно, инкарнация и эфемеризация соответствующие серванты).

    Связывание языка программирования - правила трансляции IDL -описаний в код на данном языке; эти правила определены OMG.

    Сервант - физическая реализация CORBA -объекта; серверная программа, написанная на каком-либо из языков программирования и выполняющая CORBA -объект.

    Сервис именования (Naming Service) - CORBA -объект, который позволяет обнаружить другие объекты по имени. Может быть устойчивым (запоминать ссылки и имена после остановки) и временным (не запоминать).

    Скелетон - заготовка для серванта, генерируемая IDL -компилятором.

    Устойчивый (persistent) CORBA-объект - объект, который может существовать дольше, чем активировавший его поток.

    Эфемеризация серванта - разрушение связки CORBA -объект - сервант

    Страницы:

    Основы технологии CORBA

    CORBA (Common Object Request Broker Architecture) - объектно-ориентированная технология создания распределенных приложений. Технология основана на использовании брокера объектных запросов (Object Request Broker, ORB) для прозрачной отправки и получения объектами запросов в распределенном окружении. Технология позволяет строить приложения из распределенных объектов, реализованных на различных языках программирования. Стандарт CORBA разработан Object Management Group (OMG).

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

    В данном разделе приводится краткий обзор CORBA в том виде, как ее описание дается в спецификации OMG версии 3.0. Требования этого документа могут в различной степени удовлетворяться фактическими реализациями брокеров объектных запросов. На Рис. 2.1 изображен запрос, посылаемый клиентом реализации объекта. Клиент - это сущность, которая хочет выполнить операцию с объектом, а Реализация - это совокупность кода и данных, которые в действительности реализуют объект.

    (рис 2.1) Клиент посылает запрос реализации объекта

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

    На Рис. 2.2 изображена структура брокера объектных запросов.Его интерфейсы показаны на рисунке штрихованными прямоугольниками, стрелки обозначают, вызывается ли брокер или сам выполняет вызов.

    (рис 2.2) Интерфейсы брокера объектных запросов

    Чтобы сделать запрос, Клиент может использовать Динамический интерфейс вызова (Dynamic Invocation Interface),один и тот же, вне зависимости от интерфейса целевого объекта, или IDL заглушку (stub),специфичную для интерфейса целевого объекта. Клиент также может напрямую взаимодействовать с брокером для получения некоторых функций. Реализация объекта получает запрос как вызов либо через автоматически сгенерированный IDL скелетон,либо через динамический скелетон. Реализация объекта может вызывать объектный адаптер или ORB во время выполнения запроса или в другое время. Интерфейсы объектов могут быть описаны двумя способами. Во-первых, статически, на языке описания интерфейсов IDL.Этот язык позволяет описывать типы объектов через предоставляемые ими операции и их параметры. Во-вторых, интерфейсы могут быть добавлены в Репозиторий Интерфейсов.Это специальный сервис, представляющий компоненты интерфейсов как объекты и предоставляющий доступ к этим компонентам во время выполнения.

    Для выполнения запроса клиент должен иметь доступ к объектной ссылке (IOR -Interoperable Object Reference),знать тип объекта и ту операцию, которую он хочет выполнить. Клиент инициирует запрос, вызывая подпрограммы заглушки, специфичные для конкретного объекта, или создавая запрос динамически (Рис. 2.3).

    (рис 2.3) Клиент выполняет запрос (динамически или через заглушку)

    Динамический интерфейс вызова и интерфейс заглушки имеет одинаковую семантику, так что получатель сообщения не может определить, как был послан запрос. ORB находит подходящий код реализации объекта, пересылает ему параметры и отдает управление через IDL скелетон или динамический скелетон (Рис. 2.4). Скелетоны специфичны для конкретного интерфейса и объектного адаптера. Во время выполнения запроса реализация может пользоваться некоторыми сервисами ORB через объектный адаптер. Когда запрос выполнен, управление и значения результата возвращаются клиенту.

    (рис 2.4) Реализация объекта получает запрос

    Реализация объекта может выбрать, какой объектный адаптер использовать, в зависимости от того, в каких сервисах она нуждается. На Рис. 2.5 показано, как информация об интерфейсе и реализации становится доступной клиентам и реализациям объектов. Интерфейсы описываются на IDL или с помощью репозитория интерфейсов. Их описания используются для генерации клиентских заглушек и скелетонов для реализации.

    (рис 2.5) Репозитории интерфейсов и реализаций

    Информация о реализации объекта предоставляется во время инсталляции и хранится в репозитории реализации, а затем используется в процессе доставки запроса.

    Брокер объектных запросов (ORB)

    Вместе с IDL -компилятором, репозиторями и различными объектными адаптерами, ORB предоставляет полный набор сервисов самым разным клиентам и объектным реализациям. Ядро ORB - это часть брокера, обеспечивающая базовое представление объектов и передачу запросов. Технология CORBA может поддерживать различные объектные механизмы за счет компонентов, надстроенных над ядром и предоставляющих интерфейсы, которые позволяют скрыть различия между разными ядрами.

    Клиенты

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

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

    Реализации объектов (Object implementation)

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

    Объектные ссылки (IOR)

    Объектная ссылка - это информация, необходимая для определения конкретного объекта внутри ORB.Как для клиента, так и для реализации объектная ссылка представляется так, как диктует связывание соответствующего языка программирования, таким образом, они изолированы от конкретного представления ссылки.

    Объектная ссылка, переданная клиенту, действительна только на время жизни клиента. Различные брокеры должны предоставлять одно и то же представление объектной ссылки для данного языка программирования (это позволяет одной и той же программе получать доступ к объекту по ссылке независимо от используемого брокера). Кроме того, для удобства брокер может предоставлять другие способы доступа к объекту. Существует особенная объектная ссылка, не указывающая ни на один объект.

    Язык описания интерфейсов (IDL)

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

    IDL является средством, с помощью которого реализация объекта сообщает своим потенциальным клиентам, какие операции доступны и как они могут быть вызваны. Из IDL- описания CORBA -объект можно перевести на определенный язык программирования или в другую объектную систему.

    Связывание языков программирования с IDL

    Различные объектно-ориентированные и не объектно-ориентированные языки программирования могут получать доступ к CORBA -объектам по-разному. Для объектно-ориентированных языков, скорее всего, предпочтительно видеть CORBA -объекты как объекты языка программирования. И даже для не объектно-ориентированных языков скрытие фактического представления объектных ссылок и методов внутри брокера представляется удобным. Связывание того или иного языка программирования с IDL должно быть одинаковым для всех реализаций ORB.Связывание языка включает определение специфичных для языка типов данных и интерфейсов процедур для доступа к объекту через ORB.Оно включает структуру интерфейса клиентской заглушки (для объектноориентированных языков не обязательно), интерфейс динамического вызова, скелетон реализации, объектные адаптеры и интерфейс для обращения напрямую к брокеру. Связывание также определяет взаимодействие между вызовами объекта и потоками выполнения в клиенте и реализации. Самые распространенные связывания предоставляют синхронные вызовы, когда управление возвращается клиенту после завершения операции. Дополнительные связывания могут возвращать управления программе сразу после инициации вызова. В этом случае должны предоставляться дополнительные подпрограммы, зависящие от языка, осуществляющие синхронизацию потоков программы и вызова объекта.

    Клиентские заглушки (client stubs)

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

    Динамический интерфейс вызова (Dynamic invocation)

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

    Скелетон реализации (Server skeleton)

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

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

    Динамический интерфейс скелетона

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

    Динамические скелетоны могут быть вызваны как клиентскими заглушками, так и динамическим интерфейсом вызова на стороне клиента, причем результат должен быть одинаковым.

    Объектные адаптеры

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

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

    Интерфейс ORB

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

    Репозиторий интерфейсов

    Репозиторий интерфейсов - это сервис, предоставляющий устойчивые объекты, отражающие IDL -информацию в форме, доступной во время выполнения. Информация из репозитория интерфейсов может быть использована брокером для осуществления запросов. Более того, используя информацию из репозитория, программа может найти объект, интерфейс которого был неизвестен во время компиляции программы, и, тем не менее, определить, какие операции могут выполняться объектом, и вызвать эти операции.

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

    Репозиторий реализаций

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

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

    Язык IDL

    Появление в программировании того или иного языка обычно связано с возникновением и развитием некоей новой концепции. Так, одновременно с идеей объектно-ориентированной разработки (в ее современном варианте) был создан язык Smalltalk.Дальнейшее применение объектов и компонентов вкупе с внедрением последних в распределенные системы вызвало необходимость создания такого языка программирования, который бы позволил описать любой объект или компонент. И, что не менее важно, это описание должно быть одинаковым для любой платформы. Этим требованиям удовлетворяет язык описания интерфейсов IDL (Interface Definition Language).

    Следует заметить, что IDL не только язык, но и инструмент, с помощью которого можно сохранять метаинформацию об объектах, т. е. данные о том, как устроен объект. Известно довольно много случаев, когда язык IDL используется для описания контрактов - технических параметров, позволяющих нескольким независимым группам работать одновременно над различными частями проекта. Хотя в основном исходные тексты на IDL служат своеобразным "сырьем", из которого специальные компиляторы IDL генерируют исходные тексты на одном из языков программирования высокого уровня. Такой процесс будем называть трансляцией. Типичный процесс создания распределенных объектных приложений состоит из описания объектов на IDL,их трансляции на какой-либо язык программирования, реализации объектов на данном языке и компиляции полученных исходных текстов в готовые для запуска модули.

    Приведем список терминов, используемых далее при описании языка IDL:

  • модуль - блок с заданным именем, объединяющий логически связанные конструкции языка IDL ; в целом модуль можно воспринимать как пакет ( package ) в языке Java или пространство имен ( namespace ) в языке C++ ;
  • интерфейс - набор атрибутов и операций объекта, с помощью которого потребитель может обращаться к объекту;
  • операция - сущность, которую вызывают для выполнения действий, связанных с функциональным назначением объекта; операцию можно сравнить с методом класса
  • атрибут - сущность, описывающая какое-либо свойство объекта; атрибут эквивалентен паре операций, предназначенных для чтения и записи свойства класса
  • Синтаксис IDL

    Приступим к описанию элементов и конструкций языка.

    Комментарии

    Комментарии IDL - точная копия комментариев языка С++.

    Идентификаторы

    К идентификаторам IDL относятся имена интерфейсов, модулей, атрибутов, операций, констант и т. д. Идентификатор может содержать латинские буквы, цифры, а также знак подчеркивания Все идентификаторы в IDL -файлах должны начинаться с буквенного символа. Регистр букв в идентификаторах не различается (это сделано для того, чтобы не возникало лишних проблем при трансляции IDL описания на язык программирования, нечувствительный к регистру). Символ подчеркивания '_' не может быть первым в имени идентификатора (однако очень часто при трансляции IDL компилятор сам добавляет перед получаемыми идентификаторами символы подчеркивания, чтобы избежать конфликтов имен).

    Ключевые слова

    К регистру букв в ключевых словах, в отличие от идентификаторов, компиляторы IDL чувствительны. Ниже приведена таблица ключевых слов IDL.

    Ключевые слова IDL
    any double interface readonly unsigned
    attribute enum long sequence union
    boolean exception module short void
    case FALSE Object string wchar
    char fixed octet struct wstring
    const float oneway switch
    context In out TRUE
    default inout raises typedef

    Литералы

    Литералы языка IDL подразделяются на булевы, "узкие" и "широкие" символьные, целочисленные, с плавающей точкой, с фиксированной точкой и строковые. Все эти виды литералов, кроме чисел с фиксированной точкой, аналогичны литералам C/C++,поэтому описывать их подробно нет необходимости.

    Литералы IDL
    Вид литерала Примеры значений
    Булев TRUE, FALSE
    Символьный 'p', '\t', '010', 'x1A'
    Целочисленный 99, 013, OxFFFF, 0XFFFF
    С плавающей точкой 0.15, 1234E+13, 0.987e-150
    С фиксированной точкой d0.15, D36.28
    Строковый "hello, world"

    Когда одного байта для хранения символа не хватает, применяются так называемые "широкие" символы, содержащие более 8 бит. Таблицы этих символов могут быть разными на разных платформах. Поэтому, задавая литералы с "широкими" символами, следует не выходить за рамки таблицы ISO Latin-1 (8859-1).

    Для финансовых вычислений в IDL предусмотрены литералы с фиксированной точкой, состоящие из целой и дробной частей, разделенных десятичной точкой и отмеченных буквой d или D. Такого рода литералы будут в дальнейшем применяться вместе с типом fixed.Однако, несмотря на то, что эти элементы языка описаны в спецификации CORBA 2.2,найти их реализацию в компиляторах IDL не удалось.

    Строковые литералы должны состоять из символов, допустимых в качестве символьных литералов за исключением символа '\0'.И хотя компиляторы корректно обрабатывают эти символы, тем не менее, ясно, что при трансляции IDL на C и C++ наличие нулевого символа в строке может послужить источником ошибки. Строковые литералы, как и символьные, могут быть основаны на простых и "широких" символах.

    Препроцессинг

    Для организации условной компиляции и задания некоторых опций компиляторы IDL используют препроцессинг, основанный на директивах, описанных в стандарте ANSI C++.Однако, в препроцессинге IDL присутствуют специальные разновидности директивы #pragma.

    Область видимости имен

    Любое имя IDL видно в том блоке, где оно описано. В качестве подобного блока могут выступать описания модулей, интерфейсов, составных типов. Для явного указания блока, в котором содержится используемое имя, применяется пара символов :: (оператор доступа к области видимости из языка C++ ).

    Простые типы

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

    Булев тип boolean отвечает за хранение логических значений TRUE и FALSE.Символьные типы могут описывать обычные 8-битовые символьные данные (тип char) или "широкие" символьные данные (тип wchar),размер которых более 8 бит. В процессе передачи символьных данных по сети они могут быть переконвертированы так, что изменится их представление, но значение символа будет сохранено. Такая ситуация может возникнуть при передаче данных между компьютерами с отличающимися кодировками. Целочисленные типы - это short, long, long long,а также их беззнаковые разновидности с префиксом unsigned.Обратите внимание, что тип int в IDL отсутствует.

    Диапазоны допустимых хранимых значений для целочисленных типов
    Short от -2 15 до 2 15 - 1
    Long от 2 31 до 2 31 - 1
    long long от -2 63 до 2 63 - 1
    unsigned short от 0 до 2 16 - 1
    unsigned long от 0 до 2 32 - 1
    unsigned long long от 0 до 2 64 - 1

    К типам чисел с плавающей точкой относятся float, double и long double.Тип float соответствует IEEE-числу с плавающей точкой одинарной точности. Тип double - IEEE-числу с плавающей точкой двойной точности. И последний тип, long double,используется для описания IEEE-числа с плавающей точкой двойной расширенной точности. Более полные данные о числах с плавающей точкой IEEE можно узнать из документа "IEEE Standard for Binary Floating-Point Arithmetic", ANSI/IEEE Standard 754-1985. В IDL есть два специальных простых типа: octet и any. Первый служит для передачи по коммуникационным системам 8-битовых чисел так, чтобы они в процессе пересылки не подверглись изменениям. Тип octet - хорошая альтернатива типу char в тех случаях, когда передается маленькое число.

    В объектах типа any могут храниться значения любого типа, позволенного IDL, any можно сравнить с универсальным типом void* в языке C или с классом java.lang.Object в языке Java.

    Константы

    Описание констант начинается с ключевого слова const,за которым указываются: тип константы, ее имя, символ присвоения = и константное выражение, состоящее из литералов и математических операторов. Константа может иметь любой из простых типов.

    Математические операторы IDL соответствуют аналогичным операторам C/C++.

    Конструируемые типы

    Для начала отметим, что ключевое слово typedef используется в IDL так же, как и в C/C++:для присвоения синонима имени типа.

    К конструируемым типам IDL относятся перечислимые типы, дискриминируемые объединения и структуры.

    Перечислимые типы

    Перечислимые типы знакомы большинству программистов. Общая схема их описания:

    enum < Имя энумератора >  {< Список элементов >};

    В качестве списка элементов выступают идентификаторы, разделенные запятыми:

    enum Semaphore  {red,  yellow,  green};

    Дискриминируемые объединения

    В IDL объединения обладают дискриминатором - элементом, определяющим, какой член объединения использовать в том или ином случае. Таким образом, объект, полученный в результате компиляции дискриминируемого объединения, способен в разное время хранить значения разных типов. Главное, чтобы во время описания объединения с помощью IDL были перечислены все возможные варианты хранимых типов. Типичное описание дискриминируемого объединения выглядит следующим образом:

    union < Имя объединения > switch
    (< Тип дискриминатора >)
    {
    < Список элементов выбора >
    };

    Здесь <Тип дискриминатора> может быть любым интегральным типом (символьным, целочисленным, булевым или перечислимым).

    Со списком элементов выбора дело обстоит несколько сложнее. Каждый элемент состоит из ключевого слова case и следующего за ним константного выражения, после которого ставится двоеточие и производится собственно описание хранимого типа. Константное выражение должно возвращать тип, совпадающий с типом дискриминатора. При записи в объединение некоторого значения объединение принимает тип, совпадающий с типом сохраняемого значения. Заодно запоминается дискриминатор. В дальнейшем, если будет произведена попытка считать значение под типом, отличающимся от того, под которым это значение было сохранено, произойдет генерация исключения org.omg.CORBA.BAD_OPERATION. Однако для программиста все-таки предусмотрено некоторое облегчение: объединение обладает значением по умолчанию, которое на языке IDL описывается ключевым словом default.Объединение может содержать только один такой элемент.

    Рассмотрим короткий пример описания дискриминируемого объединения:

    union MyType switch   (short)
    {
    case 13:   short alpha;
    case 0x0C << 3:   long beta;
    default:   arr alphabet;
    };

    В этом случае тип MyType имеет дискриминатор типа short.Числа, стоящие после case,представляют собой значения дискриминатора. Именно от них зависит, какой член объединения будет использован. Если значение дискриминанта равняется 13,то под именем alpha будет храниться значение short.При дискриминанте 0X0C << 3 (конечное значение этого константного выражения - 96) хранимое значение будет иметь тип long и носить имя beta.И наконец, по умолчанию значение, хранящееся в объединении, будет иметь тип arr и к нему можно обращаться по имени alphabet.

    Структуры

    Структуры служат для определения сложных типов, призванных хранить наборы разнородных данных. Типичная структура описывается следующим образом (аналогично структурам C/C++ ):

    struct < Имя структуры >
    {
    < Список членов >
    };

    Список членов - это разделенный точкой с запятой набор элементов, являющихся комбинацией из имени типа и идентификатора. Имя типа может быть любым типом IDL (допускается использование массивов с обязательным указанием числа элементов).

    Шаблонные типы

    К шаблонным типам можно отнести строки ("узкие" и "широкие"), последовательности и числа с фиксированной точкой.

    Строки.

    Строки в IDL - это 8-битовые "узкие" string и "широкие" wstring.Первые могут содержать любые символы типа char за исключением '\0'.Второй тип строки состоит из символов, подпадающих под базовый тип wchar,и заканчивается "широким" нулевым символом. Длина строки может быть как ограниченной, так и неограниченной. Ограниченные (bounded) строки можно сравнить с символьным массивом заданной длины.

    Типичное описание подобного типа: typedef wstring <28> boundedString ; В угловых скобках задается размер строки в символах. Неограниченные же (unbounded) строки содержат в себе символов столько, сколько потребуется:

    typedef string unboundedString ;

    Описание строчных типов должно предваряться ключевым словом typedef.

    Последовательности.

    Последовательности во многом схожи со строками. Различие между ними состоит в том, что последовательности помимо символьных данных могут хранить данные и других типов. Точно так же, как и строки, последовательности могут быть ограниченными по размеру либо неограниченными. Правда, в описании последовательности всегда присутствуют угловые скобки, в которых пишется тип хранимых данных (для неограниченных последовательностей) или тип хранимых данных с размером последовательности, разделенные запятой. Первый тип последовательности может выглядеть так:

    typedef sequence unboundedSequence ;

    А вот пример описания типа ограниченной последовательности:

    typedef sequence boundedSequence ;

    Последовательности могут быть рекурсивно вложены друг в друга:

    typedef sequence< sequence > recursedSequence ;

    Числа с фиксированной точкой.

    Тип данных fixed представляет собой десятичное число с фиксированной точкой, имеющее до 31 значащей цифры. К сожалению, на данный момент, тип fixed только описан в спецификации CORBA,но не реализован.

    Прочие типы

    К оставшимся типам относятся массивы и native -типы. Массивы описываются ключевым словом typedef,за которым следуют тип, идентификатор и размерность массива. Допускаются многомерные массивы:

    typedef double someArray[100][100] ;

    Тип native служит для введения новых типов данных, которые реализованы на языке программирования, отличном от IDL. Следующая строка исходного текста на IDL: native NonIDLType; говорит компилятору, что где-то имеется тип NonIDLType,реализованный непонятным для него образом, но, тем не менее, к нему нужно сделать определенный интерфейс.

    Исключения

    Описание типов-исключений практически полностью совпадает с описанием структур. Минимальное различие состоит в замене ключевого слова struct на exception:

    exception < Имя исключения >
    {
    < Список членов >
    };

    Интерфейсы

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

    Следует различать описания (или опережающие описания) интерфейсов и их определения (или полные описания). Допускаются множественные описания, однако множественные определения являются ошибкой.

    Описания требуются в том случае, если необходимо обратиться к интерфейсу еще до его определения. Чтобы получить подобное описание, достаточно написать ключевое слово interface и его имя:

    interface < Имя интерфейса >;

    При определении после имени добавляются двоеточие и имена интерфейсов-предков:

    interface <Имя интерфейса> [ :<Имя интерфейса-предка 1 > ... [ ,< Имя интерфейса-предка n >]...]
    {
    <Описания типов, констант, исключений, атрибутов и операций >
    };

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

    Операции

    Операции - это единственное средства манипулирования внутренним состоянием объекта.

    Описываются операции по схеме, показанной ниже:

    {  oneway void  |   < Возвращаемый тип >  }
    < Имя операции > ({in | out | inout } <Параметр>  ...
    [, { in | out | inout } <Параметр>]... )
    [ raises ( < Имя исключения > ... [, <Имя исключения> ] ... )];

    Если определяется операция, возвращающая некоторое значение, то слева от ее имени необходимо написать тип возвращаемого значения. Исключением являются операции, определенные как oneway,они должны возвращать тип void,т. е. не возвращать никакого значения. Ключевое слово oneway говорит, что при вызове операции программа не ждет, пока эта операция завершится, а продолжает выполнение. Параметры операции могут быть входными (in), выходными (out) или комбинированными (inout).Если операция может возбудить исключения, то они должны быть перечислены в скобках через запятую после ключевого слова raises.

    Атрибуты

    Атрибуты можно воспринимать как аналог переменных (полей) класса, однако, это не совсем корректно, так как интерфейс не может иметь состояния. Поэтому более правильно считать атрибут сокращением для пары операций считывания и модификации определенного свойства класса.

    В IDL атрибуты описываются следующим образом:

    [readonly] attribute < Тип атрибута > < Имя атрибута >;

    Необязательный модификатор readonly говорит, что значение описываемого атрибута не может быть изменено, только считано.

    Модуль

    Модуль - самая "старшая" единица языка IDL. Он служит для группировки типов, интерфейсов и т. д., логически связанных друг с другом. У модуля есть имя и тело:

    module < Имя модуля >
    {
    < Тело модуля >
    };

    Можно считать, что модули являются прямым отображением пакетов языка Java и пространств имен в C++.

    Связывание с IDL

    Спецификация CORBA регламентирует, во что должен превратиться каждый элемент языка IDL в процессе его трансляции в исходные тексты на языке программирования высокого уровня. Версия 2.2 спецификации CORBA расписывает подобные соответствия для C, C++, Smalltalk, Cobol, Ada и Java.Для примера рассмотрим подробнее трансляцию в Java.

    Комментарии

    Комментарии IDL никак не отражаются на сгенерированном Java-коде.

    Имена

    Результаты работы различных компиляторов могут различаться. В основном это касается добавления символов подчеркивания перед сгенерированными именами. Поскольку трансляция зависит от того, что за элемент языка IDL подвергается обработке, различается и количество файлов, получаемых в процессе генерации. Для пользовательских типов, например, будут созданы специальные файлы, имена которых заканчиваются суффиксами Helper и Holder.По спецификации, компилятор резервирует за собой следующие имена:

    <тип> Helper, где <тип> - имя пользовательского типа;

    <тип> Holder, где <тип> - имя пользовательского типа;

    <базовыйТипJava>Holder, где <базовыйТипJava> - один из примитивных типов языка Java;

    <интерфейс>Package, где <интерфейс> - имя интерфейса IDL.

    Вспомогательные классы

    Если программист описывает собственные типы, в результате их трансляции появляются два вспомогательных класса, имена которых состоят из имени типа с добавлением суффиксов Helper и Holder. Они необходимы для корректной работы с объектами. Helper содержит набор статических методов, выполняющих одни и те же рутинные действия. Класс с суффиксом Holder работает "оболочкой" для пользовательского типа, когда его нужно передать в качестве параметров операции объекта. Его генерация происходит не во всех случаях.

    Класс Helper всегда имеет статические методы для чтения и записи данных в поток read () и write (), упаковки данных в тип Any и распаковки (методы insert () и extract() ), а также методы определения типа type() и его идентификатора в репозитарии id().

    Класс Holder должен не только уметь записывать данные в поток методом _write(), читать их оттуда методом _ read () и возвращать код типа методом _ typecode (). В нем должна быть предусмотрена открытая переменная value, хранящая значение, и два конструктора: один - по умолчанию, т.е. без параметров, и второй - с параметром, инициализирующим переменную value.

    Модули

    Во время трансляции описания модулей превращаются в пакеты с теми же самыми именами, что и сами модули. Соответственно все описания типов внутри модулей после трансляции в классы и интерфейсы Java приобретают область видимости внутри сгенерированных пакетов. Если описания типов находятся за пределами модулей, то они транслируются в глобальный пакет Java,т. е. не включаются ни в один пакет. Для примера опишем следующий модуль:

    module UserModule
    {
    typedef string UserType;
    };

    После его трансляции в выходном каталоге появится подкаталог с именем модуля, и в нем будут сохранены файлы, появившиеся в результате генерации исходных текстов для типа UserType. А в самих этих текстах появится строка принадлежности к пакету UserModule:

    package UserModule;

    Интерфейсы

    В первую очередь создаются описания общедоступных интерфейсов Java,наследуемые от базового CORBA -интерфейса org.omg.CORBA.Object. Возьмем следующее описание на

    IDL:

    interface UserInterface
    {
    };

    После компиляции создается следующий интерфейс на языке Java:

    public interface UserInterface
    
    extends org.omg.CORBA.Object
    {
    
    }

    Внутри сгенерированного интерфейса описываются операции, которые компилятор обнаружит в IDL -файле. Для каждого из атрибутов интерфейса в них создаются описания методов чтения и записи. Если атрибут объявлен как readonly,для него генерируется лишь метод чтения. Исходный текст на IDL:

    attribute float UserAttribute;

    будет транслирован в следующие описания методов:

    float UserAttribute();

    void UserAttribute(float arg);

    Если IDL -интерфейс наследуется от другого интерфейса, то в его описании на Java также будет присутствовать наследование.

    Любой параметр операции с модификатором in транслируется в аргумент метода, имеющий соответствующий тип на языке Java.То же самое и с возвращаемым операцией значением. Параметры inout и out не могут транслироваться непосредственно в параметры методов на Java. Поэтому приходится пользоваться Holder классами. Программа-клиент подставляет в качестве параметра экземпляр подобного класса, в котором, как в контейнере, находится передаваемое значение. После передачи параметра по значению хранимые данные изменяются на серверной стороне и возвращаются клиенту, который "вскрывает контейнер" и извлекает новое значение аргумента. Например, показанная операция имеет параметр, объявленный как inout:

    void userOperation(inout double param);

    Компилятор IDL сделает из этого следующий метод на языке Java:

    void userOperation(org.omg.CORBA.DoubleHolder param);

    Такой подход, конечно, создает дополнительные сложности программиста: придется создать экземпляр Holder класса вручную.

    Для интерфейса также создаются класс Helper и класс Holder. В первом из них дополнительно к методам, описанным в разделе "Вспомогательные классы", генерируется метод narrow(), с помощью которого делается приведение к оригинальному типу интерфейса. Дело в том, что программе при запросе ссылки на объект возвращается ссылка типа org.omg.CORBA.Object, которую необходимо привести к запрошенному типу перед использованием, что и делает narrow(). При невозможности произвести эту операцию происходит исключение CORBA::BAD PARAM.

    Некоторые компиляторы (в том числе, idl2java из Visibroker) генерируют еще один метод. Он называется bind() и служит для получения ссылки на запрашиваемый объект. Этот метод не является частью спецификации CORBA.

    Часто у программистов возникают сложности с пониманием того, как транслируются вложенные в описания интерфейсов конструкции. Кажется, что достаточно сгенерировать новый пакет с его именем и поместить в него внутреннее содержимое интерфейса. Однако, по спецификации CORBA,во избежание конфликтов имен нельзя создавать пакеты, имена которых совпадают с уже имеющимися именами. Поэтому решено задавать имена пакетов, добавляя к имени интерфейсов суффикс Package.

    Опишем интерфейс, внутри которого объявляется пользовательский тип:

    interface UserInterface
    {
    typedef any UserType;
    };

    В результате трансляции получится пакет UserInterfacePackage, в который и будут помещены все сгенерированные для пользовательского типа файлы, и все они будут начинаться с директивы:

    package UserInterfacePackage;

    Простые типы

    Трансляция простых типов IDL приводит к появлению соответствующих идентификаторов, но уже имеющих Java -типы. В табл. 2.4 показано соответствие между ними: если данный тип может привести к возникновению исключительной ситуации, то она отмечена в графе "Исключения".

    Программист должен быть осторожен, когда работает с целочисленными типами IDL,объявленными как unsigned. Как известно, в Java нет беззнаковых типов, и это может стать причиной ошибок. Следовательно, требуется позаботиться о соблюдении знаковости транслированного числа.

    Holder классы для простых типов IDL определены в библиотеках, отвечающих за поддержку CORBA в пакете org.omg.CORBA. Имена этих классов начинаются с имени IDL -типа, написанного с заглавной буквы, и заканчиваются суффиксом Holder.

    Соответствие простых типов IDL типам Jav
    Тип IDL Тип Java Исключения
    boolean Boolean
    char Char CORBA::DATA_CONVERSION
    wchar Char
    octet Byte
    string java.lang.String CORBA::MARSHAL, CORBA::DATA_CONVERSION
    wstring java.lang.String CORBA::MARSHAL
    short Short
    unsigned short Short
    long Int
    unsigned long int
    long long long
    Unsigned long long long
    Float float
    Double double
    long double double (?)
    Fixed java.math.BigDecimal CORBA::DATA_CONVERSION
    Any org.omg.CORBA.Any CORBA::BAD_OPERATION

    Константы

    Константы внутри интерфейса.

    Константы, декларируемые внутри интерфейса, транслируются в поля, описанные как public final static, т. е. константы Java.Например, строки:

    interface UserInterface
    {
    const string constIntoInterface = "Hello!";
    };

    будут превращены компилятором java2idl в следующий исходный текст на языке Java:

    public interface UserInterface extends com.inprise.vbroker.CORBA.Object  {
    
    final public static java.lang.String
    constIntoInterface =   (java.lang.String)   "Hello!";
    }

    Константы вне интерфейса

    Константы, не включенные ни в один интерфейс, превращаются в интерфейс с тем же самым именем, что и константа. Внутри этого интерфейса помещается public static final поле с именем value. Например, следующий исходный текст:

    module UserModule  
    {
    const string constIntoInterface = "Hello!";
    };

    будет транслирован следующим образом:

    package UserModule;
    public interface constIntoInterface  
    {
    final public static java.lang.String value = (java.lang.String)"Hello!";
    }

    Конструируемые типы

    Перечислимые типы

    В результате трансляции перечисления получается класс с модификаторами public final и именем, соответствующим имени, описанному в IDL-файле.Для каждого элемента выбора внутри класса создаются два статических члена. Первый является уникальной целочисленной константой, а второй - ссылкой на экземпляр перечисления, инициализированный константным значением для данного выбора. Заодно генерируется закрытый конструктор, инициализируемый целочисленным значением. Еще один метод, value (), возвращает целое число, которым инициализировано перечисление, а в дополнение к нему имеется метод получения элемента перечисления по заданному числу from_int (). При недопустимом значении параметра этого метода возникает исключение CORBA::BAD_ PARAM. Например, исходный текст:

    enum UserEnum {  labelOne,   labelTwo,   labelThree  };

    будет транслирован следующим образом:

    public final class UserEnum 
    {
    public static final int  labelOne = 0, labelTwo = 1, labelThree = 2; 
    public static final UserEnum labelOne = new UserEnum(labelOne); 
    public static final UserEnum labelTwo = new UserEnum(labelTwo); 
    public static final UserEnum labelThree = new UserEnum(labelThree); 
    public int value() { return value; }
    public static final UserEnum from int(int i) throws org.omg.CORBA.BAD_PARAM  
    	{ switch (i)   
    		{
    		case _labelOne:
    		return labelOne;
    		case _labelTwo:
    		return labelTwo;
    		case _labelThree:
    		return labelThree;
    		default:
    		throw new org.omg.CORBA.BAD_PARAM();
    		}
    	}
    private UserEnum(int _value) 
    	{
    	this.value = value;
    	}
    private int _value;
    }

    Дискриминируемые объединения

    Объединение, описанное на языке IDL, транслируется в Java -класс тем же самым именем и с модификаторами public final. Внутри можно также найти:

  • конструктор по умолчанию (без параметров);
  • метод чтения дискриминатора discriminator() ;
  • метод чтения для каждого варианта с именем, заимствованным из декларируемого варианта;
  • методы модификации значения для каждого декларируемого варианта;
  • методы модификации значения для каждого декларируемого варианта, который объявляется для нескольких меток case ;
  • метод default (), если в нем есть необходимость.
  • В качестве примера рассмотрим следующее дискриминируемое объединение:

    enum UserEnum {Single,   Double, Any};
    union UserUnion switch   (UserEnum)   {
    case Single:
    case Double:  wchar anySymbol; 
    default:   any other;
    };

    и полученный в результате трансляции Java-код:

    final public class UserUnion  
    {
    private java.lang.Object _object; 
    private UserModule.UserEnum _disc;
    private UserModule.UserEnum _defdisc = UserModule.UserEnum.Any; 
    public UserUnion()   { }
    public UserModule.UserEnum discriminator()
    	{ return _disc;
    	} 
    public char anySymbol() {... } public org.omg.CORBA.Any other() {... } 
    public void anySymbol(char value) {... }
    public void anySymbol(UserModule.UserEnum disc, char value) {... } 
    public void other(org.omg.CORBA.Any value) {... }
    }

    Все методы чтения генерируют исключительную ситуацию CORBA:: BAD_OPERATION, если читаемое значение не установлено. Поэтому желательно сначала вызывать метод descriminator (), чтобы ознакомиться с текущим типом хранимого значения. Если не указать в объединении метку default, компилятор сверит все имеющиеся метки со всеми возможными значениями дискриминанта. Если таких значений больше, чем ветвей case, будет сгенерирован еще один метод default () (или _ default () в случае конфликта имен), в котором хранимое значение будет установлено так, чтобы оно было за пределами дискриминанта. Если в предыдущем примере удалить строку с меткой default, то сгенерируется следующий метод:

    public void _default() 
    { disc = defdisc; _object = null;
    }

    Структуры

    Тип struct во время компиляции транслируется в класс Java с модификаторами final и public. Имя полученного класса совпадает с именем структуры. В классе объявляются переменные-члены для каждого объявленного в IDL поля структуры. Тип переменных-членов уже относится к языку Java и выясняется в процессе трансляции полей структуры. Так же внутри полученного класса декларируются два конструктора: один - по умолчанию, т. е. без параметров, и другой - конструктор инициализации с параметрами для инициализации переменных-членов. Некоторые компиляторы создают также метод toString (), возвращающий строку, в текстовой форме отражающую содержимое полей класса.

    Например, объявлена следующая структура:

    struct UserStructure
    {any descriptor; Object reference;
    };

    После трансляции полученный класс UserStructure имеет следующий вид:

    public final class UserStructure  
    {
    public org.omg.CORBA.Any descriptor; 
    public org.omg.CORBA.Object reference; 
    public UserStructure()   {   }
    public UserStructure(org.omg.CORBA.Any __descriptor, org.omg.CORBA.Object reference)   
    	{
    	descriptor = __descriptor;
    	reference = __reference;
    	}
    }

    Последовательности и массивы

    Последовательности не создают в процессе трансляции исходного текста какого-либо исходного текста, но значительно усложняют класс Helper и класс Holder. Класс Holder теперь содержит массив с именем value для хранения элементов последовательности, а в методах класса Holder идет проверка диапазона передаваемого массива на предмет выхода за границы. Например, при трансляции:

    typedef sequence UserSequence;

    вызывает генерацию следующего массива в Holder классе:

    public byte[] value;

    и несколько мест, где происходит проверка границ массива:

    abstract public class UserSequenceHelper  
    {
    ...
    public static byte[]   read(org.omg.CORBA.portable.InputStream_input)   
    	{
    	if(_length3 > 128)   
    		{
    		throw new org.omg.CORBA.BAD_PARAM( "Sequence exceeded bound");
    		}
    	}
    public static void write(org.omg.CORBA.portable.OutputStream output, byte[] value)   
    	{
    	if(value.length > 128) 
    		{
    		throw new org.omg.CORBA.BAD_PARAM( "Sequence exceeded bound");
    		}
    	}
    }

    Трансляция массивов во многом похожа на трансляцию ограниченных последовательностей. Разница лишь в проверке границ. Если ограниченные последовательности, как показано выше, предполагают проверку на выход за границы размера, то массив проверяется на четкое соответствие размеру:

    public static void write(org.omg.CORBA.portable.OutputStream _output, byte[]   value)   
    {
    if(value.length != 128)   
    	{
    	throw new org.omg.CORBA.BAD_PARAM("Invalid array length");
    	}
    output.write octet array(value, 0, value.length);
    }

    Исключения

    Поскольку исключения имеют схожее со структурой строение, любое исключение, описанное пользователем на IDL,транслируется в класс с модификатором final public, ведущим свою родословную от org.omg.CORBA.UserException. Системные исключения CORBA,наоборот, наследуются от исключения java.lang.RuntimeException, которое, как правило, не перехватывается. Сгенерированный класс содержит по переменной для каждого описанного в IDL поля и два конструктора: по умолчанию (без параметров) и инициализации. Следующий пример:

    exception UserException  { string why;
    octet errorCode;
    };

    показывает, как происходит трансляция исключения:

    public final class UserException extends org.omg.CORBA.UserException  
    { 
    public String why; 
    public byte errorCode; 
    public UserException() { super(); }
    public UserException(String __why, byte __errorCode) 
    	{ 
    	super(); 
    	why = __why;
    	errorCode = __errorCode;
    	}
    }

    В CORBA имеется ряд предопределенных системных исключений, каждое из которых косвенно наследует класс java.lang.RuntimeException через другой класс org.omg.CORBA.SystemException.

    Псевдонимы типов (typedef)

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

    Создание CORBA-систем

    Для того чтобы создать CORBA -систему, сначала необходимо установить и настроить соответствующий инструментарий. В этом разделе будет вкратце описаны основные действия на каждом этапе разработки, а затем в разделе с примерами эти действия будут рассмотрены на конкретном примере.

    Инструменты и их конфигурирование

    Среди наиболее популярных и доступных инструментов для создания CORBA -систем брокер для Java от Sun Microsystems,входящий в стандартную поставку Java, VisiBroker от Inprise/Borland, WebLogic.

    Порядок действия при создании CORBA-системы

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

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

  • объектно-ориентированный анализ и моделирование;
  • описание и трансляция объектов;
  • создание сервера;
  • создание клиента;
  • отладка объектов.
  • Объектно-ориентированный анализ и моделирование

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

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

    Описание и трансляция интерфейсов

    Готовая модель системы содержит классы, которые должны быть описаны с помощью языка IDL.Далее это описание можно транслировать с помощью IDL -компилятора в базовые исходные тексты на конкретном языке программирования (заглушки и скелетоны) или добавить IDL -описания в репозиторий интерфейсов.

    Примеры

    Пример "Служба мгновенных сообщений"

    Функциональность

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

    (рис 2.6) Окно регистрации и аутентификации

    В окне регистрации/аутентификации (Рис. 2.6) пользователь вводит свое имя и пароль, а потом нажимает одну из кнопок Регистрация (Register) или Аутентификация (Login).При регистрации сервер запоминает имя и пароль пользователя (если такое имя еще не зарегистрировано) и выполняет вход в систему с этими параметрами. При аутентификации сервер проверяет наличие комбинации имя/пароль среди ранее зарегистрированных пользователей и в случае совпадения выполняет вход в систему.

    (рис 2.7) Окно сообщений

    У Ника открыто окно обмена сообщениями с Лео (Рис. 2.7). Сообщения Лео появляются на экране сразу после отправки. Кэйт зарегистрирована на сервере, но не подключена к системе в данный момент. Питер написал Нику сообщение, о чем свидетельствует синий цвет кнопки. Ник прочитает это сообщение после того, как переключится в режим общения с Питером, нажав на кнопку с его именем.

    Инструменты

    Разработка будет вестись на языках Java и C++ с помощью средств Java 2 SE 1.4.2 и Microsoft Visual Studio 8 (2005). В качестве брокера объектных запросов (ORB) для обоих языков будем использовать Borland VisiBroker 7.На настройке брокера остановимся подробнее.

    На момент написания этого текста седьмая версия VisiBroker - единственная официально бесплатно распространяемая для ознакомительного использования ( trial, 60 дней). Стабильную версию Inprise обещает выпустить в следующем году. Описываемые здесь способы настройки могут не потребоваться при использовании окончательной версии. Временную (на 60 дней) лицензию можно получить на официальном сайте. После окончания ее действия штатная замена лицензии не помогает, требуется удалить старую лицензию как файл, и после этого установить новую.

    В качестве C+ + компилятора VisiBroker 7,в отличие от предыдущих версий, поддерживает только компилятор из Visual Studio 8 (компилятор от Borland не поддерживается). Java поставляется в комплекте, причем две версии - 1.4.2 и 1.5. Несоответствие версий стало одной из причин неспособности данного брокера работать с Java без модификации. Для компиляции и выполнения написанных на Java CORBA-объектов используются входящие в VisiBroker программы vbj и vbjc вместо java и javac соответственно. При этом для компиляции используется версия JDK 1.5,а для выполнения - 1.4.2, что приводит к неработоспособности. Можно было бы добавить опцию компиляции с целью 1.4.2, но проще выбрать одну версию, в нашем случае 1.4.2. Для этого требуется заменить в файле <VBDir>\bin\toolsj dk.config строку

    "javahome $var(installRoot)/jdk/jdk1.5.0"на

    "javahome $var(installRoot)/jdk/jdk1.4.2".

    Кроме vbj, vbjc из программ от VisiBroker нам потребуются SmartAgent и Naming Service.Первый должен быть запущен для выполнения всех примеров (кроме первого). Второй проще запускать указанным в соответствующих примерах образом. Несмотря на то, что согласно документации запуск Naming Service с соответствующими параметрами является альтернативой использованию SmartAgent,для работы текущей версии сервиса имен требуется и запуск SmartAgent,и явное указание параметров.

    Интерфейс

    Реализацию службы мгновенных сообщений с использованием CORBA мы начнем с описания интерфейсов CORBA -объектов на языке IDL. Создадим IDL -файл с описанием модуля Message, в котором содержатся два интерфейса - MessageReceiver и MessagingService. Методы этих интерфейсов будут доступны для вызова клиентам CORBA -объектов. Первый интерфейс реализуется на стороне клиента службы мгновенных сообщений, а второй - на стороне сервера.

    module Message  
    {
    interface MessageReceiver  
    {
    void newMessage(in unsigned long from,   in string text); 
    void userStatusNotification(in unsigned long uid,   in string name,   in boolean online);
    };
    interface MessagingService  
    {
    unsigned long registerUser(string userName,   in string password); 
    unsigned long login(in string userName, in string password, in MessageReceiver receiver); 
    boolean sendMessage(in unsigned long from, in string password, in unsigned long to, in string msg);
    boolean logout(in unsigned long uid, in string password);
    };

    Интерфейс MessageReceiver предназначен для уведомления клиента о событиях в системе. Метод newMessage уведомляет клиента о новом предназначенном ему сообщении, а метод userStatusNotification - об изменении статуса других пользователей службы.

    Интерфейс сервера MessagingService предоставляет методы для регистрации пользователя ( registerUser ), аутентификации ( login ), отправки сообщения конкретному пользователю ( sendMessage ) и выхода из системы ( logout ). В целях гарантии аутентичности каждый метод сервера требует передачи пароля в качестве одного из параметров.

    Далее IDL -файл необходимо скомпилировать, чтобы получить классы заглушек, скелетонов и другие вспомогательные классы на целевом языке программирования. Для компиляции IDL -описания в Java будем использовать команду:

    idl2java [params] Message.idl

    Здесь в параметрах указывается тип используемого объектного адаптера. От него зависит, какие вспомогательные классы будут созданы. Тип объектного адаптера по умолчанию - POA.Для компиляции в C+ + используется команда:

    idl2cpp [params] Message.idl

    IDL -файл необязательно компилировать отдельно. Имена создаваемых автоматически файлов вполне предсказуемы, поэтому их можно использовать в коде реализации и до компиляции. На практике удобно создать единый makefile, с помощью которого будет компилироваться сначала IDL -описание, а затем классы реализации на целевом языке.

    Прямое задание IOR

    В нашем первом примере и сервер и клиент службы мгновенных сообщений будут реализованы на языке Java.Для вызова методов CORBA -объекта необходимо сначала получить ссылку на этот объект. В данном примере ссылка на сервер задается явно в виде Interoperable Object Reference (IOR).

    Начнем с реализации сервера службы мгновенных сообщений. Он выполняет следующие действия:

  • Инициализация ORB.

    ORB orb = ORB.init(args,null);

  • Создание серванта (объекта реализации).

    Message.MessagingService messagingService = new MessagingServiceImpl();

  • Получение IOR серванта и сохранение его в файл.

    String ior = orb.object_to_string(messagingService);

    ...

    FileWriter fw = new FileWriter("MS.ior"); fw.write(ior);

  • Ожидание подключения клиентов.
  • Ниже приведен полный листинг кода сервера.

    import org.omg.CORBA.*; 
    import java.io.*;
    public class Server  
    {
    public static void main(String[]   args)   
    { 
    ORB orb = ORB.init(args,null); 
    Message.MessagingService messagingService =new MessagingServiceImpl(); 
    String ior = orb.object to string(messagingService); 
    System.out.println(messagingService + " is ready.\n" + ior);
    try 	
    	{
    	FileWriter fw = new FileWriter("MS.ior");
    	fw.write(ior);
    	fw.close();
    	System.out.println("IOR written to file"); 
    	}   
    catch(IOException e)   
    	{ 
    	System.out.println("Failed to write IOR to file. Exception: "); 
    	e.printStackTrace();
    	}
    try 	
    	{
    	Thread.currentThread().join();
    	}  
    catch(InterruptedException ex)   
    	{}
    }

    Далее опишем сервант, реализующий интерфейс MessagingService. Как видно из листинга, приведенного ниже, его необходимо унаследовать от класса-скелетона Message.MessagingServiceImplBase, сгенерированного автоматически компилятором IDL.

    import org.omg.CORBA.*;
    import org.omg.Messaging.*;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.HashMap;
    
    public class MessagingServiceImpl extends Message.MessagingServiceImplBase  
    { 
    private HashMap nameToId; 
    private ArrayList users; 
    public MessagingServiceImpl()   
    	{
    	System.out.println("Constructing MessagingServiceImpl");
    	nameToId = new HashMap();
    	users = new ArrayList();
    	}
    public int registerUser (String userName, String password)   
    	{ 
    	if (nameToId.containsKey(userName))   
    	{
    	System.out.println("User " + userName + " already registered"); 
    	return 0; 
    	}  
    	else  
    	{
    	int uid = nameToId.size();
    	nameToId.put(userName,  new Integer(uid)); 
    	users.add(new User(userName,  password)); 
    	notifyAllUsers(++uid,   false); 
    	System.out.println("User " + userName + " registered successfully,  user ID is  " + (new Integer(uid)).toString());
    	return uid;
    	}
    	}
    ...
    private void notifyAllUsers(int uid,  boolean online)
    	{
    	String userName = ((User)users.get(uid - 1)).name;
    	for (int i = 0;  i < users.size(); ++i)	
    		{
    		if (i   != uid - 1)   
    			{
    			User buddy = (User)users.get(i);
    			if (buddy.online)   
    				{
    				buddy.receiver.userStatusNotification( uid,  userName,   online);
    				}
    			}
    		}
    	}
    class User {... } 
    class Msg    {... }
    }

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

  • Инициализация ORB.

    ORB orb = ORB.init(args, null);

  • Получение IOR сервера из файла.

    String ior = br.readLine();

  • Преобразование IOR сервера в CORBA -объект и приведение его к соответствующему типу.

    org.omg.CORBA.Object obj = orb.string_to_object(ior);

    Message.MessagingService service =

    Message.MessagingServiceHelper.narrow(obj);

  • Создание окон пользовательского интерфейса и серванта.
  • Полный листинг класса клиента приведен ниже. Листинги классов пользовательского интерфейса можно найти в файлах с примерами.

    import org.omg.CORBA.*;
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.util.ArrayList;
    import java.util.Vector;
    import java.io.*;
    
    public class Client  
    {
    public static void main(String[]   args)   
    {
    try 
    	{
    	ORB orb = ORB.init(args, null); 
    	FileReader fr = new FileReader("MS.ior"); 
    	BufferedReader br = new BufferedReader(fr); 
    	String ior = br.readLine();
    	org.omg.CORBA.Object obj = orb.string_to_object(ior); 
    	System.out.println(ior);
    	Message.MessagingService service = Message.MessagingServiceHelper.narrow(obj);
    	MessagingFrame mf = new MessagingFrame(service);
    	Message.MessageReceiver receiver = new MessageReceiverImpl(mf);
    	LoginFrame loginFrame = new LoginFrame(service,   receiver,  mf);
    	loginFrame.show(); 
    	}   
    catch   (Throwable t)   
    	{	t.printStackTrace();
    	}
    }

    Наконец, создадим сервант, реализующий интерфейс MessageReceiver. По аналогии с сервантом сервера, он наследуется от скелетона Message._MessageReceiverImplBase.

    import javax.swing.*; 
    import org.omg.CORBA.*; 
    import java.awt.*; 
    import java.awt.event.*; 
    import javax.swing.*; 
    import java.util.ArrayList; 
    import java.util.Vector;
    
    public class MessageReceiverImpl extends Message.MessageReceiverImplBase 
    { 
    public MessageReceiverImpl(MessagingFrame mf)   
    	{ 
    	mesFrm = mf;
    	}
    
    public void newMessage(int from,   String text)   
    	{
     	mesFrm.newMessage(from,   text); 
    	System.out.println("New message from " + 
    	(new Integer(from)).toString()   + ":   " + text);
    	}
    public void userStatusNotification(int uid,   String name,  boolean online)   
    	{ 
    	mesFrm.buddyStateChanged(uid,  name,   online); 
    	System.out.println(name + " is now " + (online ? "online" : "offline"));
    	}
    private MessagingFrame mesFrm;
    }

    Использование BOA

    Одним из существенных недостатков реализации службы мгновенных сообщений в первом примере была необходимость передачи IOR сервера клиенту (например, путем копирования файла). Более удобный способ получения ссылки на объект - использование механизма bind. Этот механизм не является стандартным,то есть не предусмотрен стандартом CORBA, но предоставляется VisiBroker.

    В качестве объектного адаптера в данном примере используется Basic Object Adapter (BOA).Он менее переносим (однако, более прост в использовании), чем более новый Portable Object Adapter (POA).

    Отметим, что, поскольку здесь (как и в первом примере) используется BOA,при компиляции IDL-файла в качестве параметра требуется указать -boa.

    В приведенном ниже листинге класса сервера изменения по сравнению с предыдущим примером выделены жирным шрифтом. Их смысл заключается в том, что теперь сервер инициализирует BOA и сообщает ему о готовности CORBA -объекта и его серванта. Теперь при создании серванта в качестве параметра ему передается имя CORBA -объекта. Это имя регистрируется конструктором родителя серванта ( Message.MessagingServiceImplBase ) для последующего использования механизмом bind.

    public class Server  
    {
    public static void main(String[]   args)   
    	{
    	org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    	com.inprise.vbroker.CORBA.BOA boa = ((com.inprise.vbroker.CORBA.ORB)orb).BOA_init();
    	Message.MessagingService messagingService =new MessagingServiceImpl("MessagingService");
    	boa.obj_is_ready(messagingService);
    	System.out.println(messagingService + " is ready."); 
    	boa.impl_is_ready();
    	}
    }

    Реализация серванта остается прежней. Единственное изменение: добавляется вызов родительского конструктора, которому передается имя CORBA -объекта.

    public MessagingServiceImpl(String name)   
    { 
    super(name);
    System.out.println("Constructing MessagingServiceImpl"); 
    nameToId = new HashMap(); 
    users = new ArrayList();
    }

    В реализации клиента добавилась инициализация BOA. Кроме того, ссылка на сервер теперь не получается напрямую из IOR, а находится по имени MessagingService, зарегистрированному сервером. Поиск выполняется при помощи метода bind класса MessagingServiceHelper, который был автоматически сгенерирован из IDL -описания.

    public class Client  
    {
    public static void main(String[]   args)   
    	{
    	try 
    		{
    		org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    		com.inprise.vbroker.CORBA.BOA boa =
    		((com.inprise.vbroker.CORBA.ORB)orb).BOA_init();
    		Message.MessagingService service =
    		Message.MessagingServiceHelper.bind(orb,   "MessagingService");
    		MessagingFrame mf = new MessagingFrame(service); 
    		Message.MessageReceiver receiver = new MessageReceiverImpl(mf); 
    		LoginFrame loginFrame = new LoginFrame(service,   receiver,  mf); 
    		loginFrame.show(); 
    		}  
    	catch   (Throwable t)   
    		{ 
    		t.printStackTrace();
    		}
    	}
    }

    Использование POA

    В данном примере используется другой вид объектного адаптера - Portable Object Adapter (POA).В связи с этим в реализации сервера произошли следующие изменения:

  • За инициализацией ORB следует получение ссылки на корневой объектный адаптер rootPOA. Корневой адаптер всегда существует, ссылку на него можно получить с помощью метода ORB resolve_initial_references.
  • Далее создается массив политик, который после передается в качестве одного из параметров конструктору нового объектного адаптера myPOA. Адаптер myPOA создается внутри корневого объектного адаптера ( POA можно вкладывать друг в друга).
  • После создания серванта имя будущего CORBA -объекта представляется в виде набора байт. Далее объектному адаптеру дается команда активировать CORBA- объект с заданным именем и ссылкой на сервант.
  • В корневом объектном адаптере активируется менеджер объектных адаптеров.
  • Для ожидания подключений в данном примере используется метод ORB run(). В предыдущих примерах использовался альтернативный способ - присоединение сервера к некоторому потоку.
  • import org.omg.PortableServer.*;
    public class Server  
    {
    public static void main(String[]   args)   
    	{
    	try 
    		{
    		org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null);
    		POA rootPOA = POAHelper.narrow(orb.resolve initial references("RootPOA"));
    		org.omg.CORBA.Policy[] policies = {rootPOA.create lifespan policy( LifespanPolicyValue.PERSISTENT)};
    		POA myPOA = rootPOA.create_POA("messaging poa", rootPOA.the_POAManager(),  policies  ); 
    		MessagingServiceImpl messagingServant =new MessagingServiceImpl();
    		byte[]  messagingId = "MessagingService".getBytes(); 
    		myPOA.activate object with id(messagingId,  messagingServant); 
    		rootPOA.the_POAManager().activate();
    		System.out.println(myPOA.servant to reference(messagingServant)   + " is ready."); 
    		orb.run(); 
    		}  
    	catch   (Exception e)   
    		{
    		e.printStackTrace();
    		}
    	}
    }

    Класс серванта теперь наследуется от другого скелетона - Message.MessagingServicePOA. Здесь нет необходимости указывать имя CORBA- объекта в качестве параметра конструктора родителя.

    public class MessagingServiceImpl extends Message.MessagingServicePOA 
    {
    private HashMap nameToId; 
    private ArrayList users; 
    public MessagingServiceImpl()   
    	{
    	System.out.println("Constructing MessagingServiceImpl");
    	nameToId = new HashMap();
    	users = new ArrayList();
    	}
    }

    Изменения в клиенте аналогичны изменениям в сервере:

  • После получения ссылки на корневой объектный адаптер создаются политики и адаптер callbackPOA, в котором будет размещен CORBA -объект получатель сообщений ( MessageReceiver ).
  • Далее с помощью механизма bind получаем ссылку на сервер.
  • Создание пользовательского интерфейса.
  • Активация получателя сообщений и менеджера объектных адаптеров.
  • Получение ссылки на MessageReceiver для последующей передачи серверу.
  • public static void main(String[]   args)   
    {
    try 
    	{
    	org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args,null); 
    	POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA")); 
    	org.omg.CORBA.Policy[] policies = {rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT)};
    	POA callbackPOA = rootPOA.create_POA("MessageReceive",  rootPOA.the_POAManager(), policies); 
    	byte[] messagingId = "MessagingService".getBytes(); 
    	Message.MessagingService service =
    		Message.MessagingServiceHelper.bind(orb,   "/messaging_poa", messagingId);
    	MessagingFrame mf = new MessagingFrame(service);
    	MessageReceiverImpl mr = new MessageReceiverImpl(mf);
    	callbackPOA.activate_object(mr);
    	callbackPOA.the_POAManager().activate();
    	Message.MessageReceiver receiver = 
    		Message.MessageReceiverHelper.narrow(callbackPOA.servant_to_reference(mr));
    	LoginFrame loginFrame = new LoginFrame(service,   receiver,  mf);
    	loginFrame.show();
    	}  
    catch   (Throwable t)   
    	{
    	t.printStackTrace();
    	}
    }

    Сервер получателя сообщений теперь наследуется от другого скелетона:

    static class MessageReceiverImpl extends Message.MessageReceiverPOA...

    Использование сервиса имен

    В данном примере рассмотрен еще один способ получения ссылки на CORBA -объект -использование сервиса имен. Этот способ более удобен, чем прямое задание IOR, и, в отличие от механизма bind, является стандартным.Основные изменения в реализации сервера состоят в следующем:

  • Подключение пакета CosNaming
  • Получение ссылки на сервис имен
  • Создание пути размещения объекта в виде последовательности компонентов имени (в данном случае имя состоит из одного компонента, однако в более сложных случаях удобно использовать иерархическую структуру)
  • Установление соответствия между полным именем объекта и ссылкой на него. Метод rebind контекста именования отличается от его метода bind тем, что при конфликте имен он будет разрешен в пользу нового сопоставления.
  • import org.omg.PortableServer.*;
    import org.omg.CosNaming.*;
    import org.omg.CORBA.*;
    public class Server  
    {
    public static void main(String[]   args)   
    {
    try 
    	{
    	ORB orb = ORB.init(args,null);
    	POA rootPOA = (POA)orb.resolve_initial_references("RootPOA");
    	rootPOA.the_POAManager().activate();
    	MessagingServiceImpl messagingServant = new MessagingServiceImpl(orb);
    	org.omg.CORBA.Object rootObj =orb.resolve_initial_references("NameService"); 
    	NamingContext root = NamingContextHelper.narrow(rootObj); 
    	NameComponent[] path ={new NameComponent("MessagingService",   "")}; 
    	org.omg.CORBA.Object ref =rootPOA.servant_to_reference(messagingServant); 
    	root.rebind(path,  ref); 
    	System.out.println("Ready"); 
    	orb.run(); 
    	}   
    catch   (Exception e)   
    	{
    	e.printStackTrace();
    	}
    }

    Класс серванта по сравнению с предыдущим примером остается неизменным. Изменения в клиенте аналогичны изменениям в сервере. Метод resolve контекста именования позволяет получить ссылку на CORBA -объект по его полному имени.

    public static void main(String[]   args)   
    { 
    try 
    	{
    	ORB orb = ORB.init(args,null); 
    	org.omg.CORBA.Object rootObj  = orb.resolve initial references("NameService"); 
    	NamingContext root = NamingContextHelper.narrow(rootObj);
    	NameComponent[]   path = {new NameComponent("MessagingService", "")};
    	org.omg.CORBA.Object msgObj  = root.resolve(path); 
    	Message.MessagingService service =Message.MessagingServiceHelper.narrow(msgObj); 
    	MessagingFrame mf = new MessagingFrame(service);
    	MessageReceiverlmpl mr = new MessageReceiverlmpl(mf);
    	POA rootPOA =   (POA)orb.resolve_initial_references("RootPOA");
    	rootPOA.the_POAManager().activate();
    	Message.MessageReceiver receiver =
    		Message.MessageReceiverHelper.narrow(
    	rootPOA.servant_to_reference(mr));
    	LoginFrame loginFrame
    	new LoginFrame(service,  mf,   orb.object to string(receiver));
    	loginFrame.show();
    	}
    catch   (Throwable t)   
    	{
    	t.printStackTrace();
    	}
    }

    При использовании сервиса имен необходимо использовать дополнительные параметры командам запуска сервера и клиента. Кроме того, необходимо запустить сам сервис имен. Это делается следующим образом:

    start nameserv -J-Dvbroker.se.iiop_tp.scm.iiop_tp.listener.port=<port> NameService
    start vbj Server -ORBInitRef NameService=iioploc://<host>:<port>/NameService
    start vbj Client -ORBInitRef NameService=iioploc://<host>:<port>/NameService

    Здесь host - идентификатор узла, на котором запущен сервис имен. Порт может быть любым свободным, но должен согласовываться при запуске сервиса имен, клиента и сервера.

    Сервер на C++

    Данный пример демонстрирует взаимодействие компонентов, реализованных на различных языках программирования. А именно, сервер службы мгновенных сообщений реализован на С++.

    Для корректного взаимодействия прямая передача ссылки на CORBA -объект получателя сообщений заменена передачей IOR. Это приводит к изменению в IDL -описании (измененное описание используется в этом и предыдущем примерах):

    interface MessagingService  
    {
    unsigned long login(in string userName, in string password, in string receiverlOR);
    };

    Приведем реализацию сервера на С++. Функциональность, по сравнению с реализацией на Java,не изменилась.

    #include "CosNaming c.hh" #include "Messlmpl.h"
    USE_STD_NS
    int main(int argc,   char* const* argv)
    {
    try 
    	{
    	// Инициализируем брокер объектных запросов 
    	CORBA::ORB_var orb = CORBA::ORB_init(argc,   argv); 
    	// Получаем ссылку на корневой объектный адаптер
    	PortableServer::POA_var rootPOA = 
    		PortableServer::POA::_narrow( orb->resolve_initial_references("RootPOA"));
    
    	// Определяем политику для дочернего объектного адаптера 
    	CORBA::PolicyList policies; 
    	policies.length(l); 
    	policies[(CORBA::ULong)0]   =
    		rootPOA->create_lifespan_policy(PortableServer::PERSISTENT); 
    		
    	// Получаем ссылку на мэнеджер объектных адаптеров 
    	PortableServer::POAManager_var poa_manager = rootPOA->the_POAManager();
    	
    	// Создаем дочерний адаптер
    	PortableServer::POA_var myPOA = rootPOA->create_POA("messaging_poa",  poa_manager,policies);
    	
    	// Создаем сервер   (реализацию объекта) 
    	MessagingServiceImpl messagingServant(orb);
    	
    	// Получение идентификатора серванта 
    	PortableServer::ObjectId_var messagingId =PortableServer::string_to_ObjectId("MessagingService"); 
    
    	// Активация идентификатора серванта в дочернет объектном адаптере 
    	myPOA->activate object with id(messagingId,   messagingServant);
    	
    	// Активация мэнеджера объектных адаптеров 
    	poa_manager->activate(); 
    	CORBA::Object_var reference =
    		myPOA->servant to reference(messagingServant); 
    	
    	// Получаем ссылку на сервис имен 
    	CosNaming::NamingContext_var rootContext = 
    		CosNaming::NamingContext::_narrow(orb->resolve_initial_references("NameService")); 
    
    	// Регистрация в сервисе имен 
    	CosNaming::Name name; 
    	name.length(l);
    	name[0].id = (const char *) "MessagingService"; 
    	name[0].kind = (const char *) ""; 
    	rootContext->rebind(name, reference); 
    	cout << reference << " is ready" << endl; 
    	// Ожидание соединений orb->run(); 
    	}   
    catch(const CORBA::Exception e)   
    	{ cerr << e << endl;
    	return l;
    	}
    return 0;
    }

    Ниже приведен частичный листинг серванта на С++ (полную версию можно найти в электронном варианте):

    #include "message s.hh" 
    #include <math.h> 
    #include <string> 
    #include <vector> 
    #include <map>
    
    USE_STD_NS
    
    class Msg  {...};     // Сообщение
    class User  {...};   // Информация о клиенте
    class MessagingServiceImpl : public virtual POA_Message::MessagingService,
    public virtual PortableServer::RefCountServantBase
    {
    public:	MessagingServiceImpl(CORBA::ORB_var _orb)   : orb(_orb)   {}
    CORBA::ULong registerUser(const char* _userName, const char* _password)
    	{
    	string userName(_userName); 
    	string password(_password);
    	if (nameTold.find(userName) != nameToId.end())   
    		{ 
    		std::cout << "User " << userName <<" already registered" << std::endl; 
    		return (CORBA::ULong)  0; 
    		}  
    	else  
    		{
    		int uid = nameToId.size(); 
    		nameToId[userName]  = uid; 
    		User u(userName,  password); 
    		users.push_back(u); 
    		notifyAllUsers(++uid, false); 
    		std::cout << "User " << userName <<" registered successfully,  user ID is  " <<
    		uid << std::endl;
    		return (CORBA::ULong) uid;
    		}
    	private:	
    		CORBA::ORB_var orb;
    		void notifyAllUsers(int uid,  bool online)
    			{
    			string userName = users[uid - l].name; 
    			for   (int i = 0;  i < users.size();  ++i)   
    				{
    				if (i != uid - l) 
    				{
    				User buddy = users[i]; 
    				if (buddy.online)   
    					{
    					buddy.receiver->userStatusNotification( uid, userName.c_str(), online);
    					}
    				}
    				}
    			}
    	map<string, int> nameToId; 
    	vector<User> users;
    };

    Запуск сервера осуществляется следующей командой:

    start Server -ORBInitRef NameService=iioploc://<host>:<port>/NameService

    Пример "Банковская система"

    Общее описание

    Разработаем простейшую банковскую систему, поддерживающую следующий набор функций:

  • хранение информации о счетах клиентов;
  • выдачу сведений по запросу авторизованных клиентов;
  • модификация счетов (снятие/зачисление денег, перевод на другой счет) авторизованными клиентами;
  • открытие и закрытие счетов;
  • выдачу отчетов о транзакциях за заданный клиентом период времени.
  • Система должна обеспечивать актуальность данных, отображаемых пользователю, а так же обеспечивать безопасность работы: перевод денег на счет может осуществляться любым клиентом, а снятие денег, перевод со счета и закрытие счета - только его владельцем. В качестве ORB используется Borland $$\text{\textregistered}$$ Visibroker.

    Создание таблиц в базе данных

    (рис 2.8) Мифологическая модель базы данных

    Информация о счетах клиентов и транзакциях будет храниться в базе данных, инфологическая модель которой приведена на Рис. 2.8. Эта база данных состоит из двух основных таблиц:

  • таблицы accounts, хранящей информацию о текущем состоянии счетов клиентов. В таблице хранятся имя клиента ( name ), пароль для доступа к счету ( password ), текущий остаток на счете ( balance ) и признак закрытия счета ( closed );
  • таблицы transactions, хранящей информацию обо всех транзакциях. В таблице хранятся дата и время проведения транзакции ( transaction_date ), сумма ( amount ) и комментарий к транзакции ( transactioncomment ). Номера счетов источника ( source_account_id ) и получателя ( dest_account_id ) транзакции связаны с таблицей accounts по внешнему ключу.
  • Для того, чтобы создать таблицы в базе данных Oracle 9i,необходимо выполнить следующий SQL-код.Помимо создания таблиц в базе данных, данный SQL-код создает процедуру, сообщающую серверу системы о появлении новых записей в таблице transactions и триггер, инициирующий выполнение этой процедуры. Так же для каждой таблицы определен триггер, автоматически генерирующий поле id для каждой добавленной строки таблицы.

    delete from transactions;
    drop table transactions;
    drop sequence transactions id;
    
    delete from accounts;
    drop table accounts;
    drop sequence accounts id;
    
    create table accounts   (id number,
    name varchar2(12 8), password varchar2(12 8), balance number default 0, closed char(1),
    constraint accounts pk primary key   (id));
    
    create table transactions (id number, transaction date number, source account id number, 
    	dest account id number, amount number, transaction comment varchar2(12 8), 
    	constraint transactions pk primary key (id), 
    	constraint source account fk foreign key (source account id) references accounts(id),
    	constraint dest account fk foreign key (dest account id)   references accounts(id));
    
    create sequence transactions id start with 1 increment by 1; 
    create sequence accounts id start with 1 increment by 1;
    
    create or replace and compile java source named "Trigger" as
    
    import java.net.*;
    import java.io.*;
    import java.math.BigInteger;
    
    public class Trigger  
    {
    private static java.net.Socket s = null; 
    private static OutputStream outputStream = null;
    public static synchronized void trig(long id,   String host,   int port)   
    	{
    	try 
    		{
    		byte[]   b = new byte[8];
    		for (int i = 0;  i < 8;  ++i)
    			{
    			b[i] = ( byte )(id % 256); 
    			id >>= 8;
    			}
    		if (connect(host, port)) 
    			{ 
    			outputStream.write(b); 
    			outputStream.flush();
    			}
    		}   
    	catch   (Exception e)   
    		{
    		System.out.println("Failed"); e.printStackTrace();
    		}
    	}
    private static boolean connect(String host,   int port)   throws Exception  
    	{
    	if ( s == null )
    		s = new Socket( host,  port  );
    	outputStream = s.getOutputStream();
    	return true;
    	public static void disconnect()   
    		{
    		try 
    			{
    			s.close(); 
    			s = null; 
    			}   
    		catch   (Exception e)   
    			{}
    		}
    	}
    
    /
    show errors java source "Trigger"
    create or replace procedure account changed(accountId number, host varchar2, port number)   as
    language java name   'Trigger.trig(long,   java.lang.String,   int)';
    
    /
    create or replace procedure disconnect as
    
    language java name   'Trigger.disconnect()';
    /
    
    create or replace trigger transactions trigger after insert on transactions
    referencing new as new for each row 
    begin
    account changed( rnew.source account id, 'smal', 12345); 
    account changed( rnew.dest account id,  'smal', 12345); 
    end;
    
    /
    
    show errors

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

    Интерфейсы системы

    Клиентским приложениям система доступна посредством интерфейсов Bank и Account и структуры Transaction. Кроме того, посредством интерфейса AccountEvents сервер сообщает клиентам об изменениях на счете. Для сообщения об ошибках добавлены исключения BankException и AccountException, содержащие информацию об ошибках в работе системы.

    Интерфейс Bank

    Это первый интерфейс, получаемый клиентами, после подключения к серверу системы. Интерфейс описан в IDL-файле следующим образом:

    interface Bank 
    {
    AccountId CreateAccount(  in wstring usrName,     in wstring usrPasswd ) raises(BankException);
    Account	OpenAccount ( in AccountId id,	in wstring usrPasswd )
    raises(BankException);
    void	CloseAccount (in AccountId id,	in wstring usrPasswd )
    raises(BankException);
    };

    Посредством этого интерфейса клиент может залогиниться в систему ( OpenAccount ), открыть новый ( CreateAccount ) или закрыть существующий ( CloseAccount ) счет.

    Класс интерфейса Bank

    Класс интерфейса Bank существует в единственном экземпляре все время жизни сервера. Следующий код добавляет объект этого класса в POA,делая его доступным внешним приложениям:

    CORBA::Object var obj  = orb->resolve initial references("RootPOA"); 
    PortableServer::POA var rootPOA = PortableServer::POA::  narrow(obj);
    
    CORBA::PolicyList policies;
    policies.length(1);
    
    policies[(CORBA::ULong)0]   = rootPOA->create lifespan policy(PortableServer::PERSISTENT );
    
    // get the POA Manager
    PortableServer::POAManager var poa manager = rootPOA->the POAManager();
    PortableServer::POA var
    bankPOA = rootPOA->create POA(   "banking poa",  poa manager,  policies  );
    const int MAXBUF = 1024; char ior[MAXBUF];
    
    // Convert from string to object 
    std::ifstream in(argv[1]); 
    if   (   !in )
    throw std::runtime error(  std::string("Can't open file \"")   + argv[1] + "\""  );
    in.getline(ior,  MAXBUF);
    
    // Create the servant
    ParamsPtr params = ReadParams();
    BankImpl bankServant(orb,   ior,   *params);
    
    // Decide on the ID for the servant
    PortableServer::ObjectId var bankId = PortableServer::string_to_ObjectId("Bank");
    
    // Activate the servant with the ID on bankPOA
    bankPOA->activate object with id(bankId,   bankServant);
    
    // Activate the POA Manager 
    poa manager->activate();
    CORBA::Object var reference = bankPOA->servant to reference(  bankServant);
    
    // Wait for incoming requests
    orb->run();

    Класс BankImpl реализует интерфейс Bank, а так же занимается информированием клиентов об изменении их счетов. Для этого в члене channels хранится отображение номеров счетов на открытые в настоящий момент соединения с сервером. Фактически, основную работу выполняет класс database::Bank, который непосредственно модифицирует базу данных. Фактически, объект класса BankImpl хранит указатель на объект класса database::Bank и делегирует ему запросы клиентского приложения на работу со счетом.

    ::CORBA::LongLong BankImpl::CreateAccount(  const wchar t*    usrName,   const wchar t*    usrPasswd )
    {
    database::AccountId id;
    CheckResult( bank ->CreateAccount(    usrName,    usrPasswd,   id )   ); 
    return id;
    }
    
    banking::Account ptr BankImpl::OpenAccount(   ::CORBA::LongLong    id,   const wchar t*    usrPasswd )
    {
    database::AccountPtr db acc;
    CheckResult( bank ->OpenAccount(id, usrPasswd, db acc ));
    AccountsMap::iterator it = accounts.find(id );
    if (it == accounts.end())
    	{
    	VISMutex var lock(channelsMutex);
    	std::string const channelIOR  (  CreateChannel( id )); 
    	AccountImpl * servant = new AccountImpl( db acc,   channelIOR ); 
    	CORBA::Object var ref (accountPOA ->servant to reference(servant));
    	servant-> remove ref();
    	banking::Account var account = banking::Account:: narrow(ref); 
    	it = accounts.insert(  std::make pair(    id,   account  )   ).first;
    	}
    return banking::Account::  duplicate(it->second);
    }
    
    void BankImpl::CloseAccount(::CORBA::LongLong    id,   const wchar t*    usrPasswd)
    {
    CheckResult( bank ->CloseAccount( id, usrPasswd ));
    }

    Упомянутая в коде функция CheckResult формирует исключения в случае, если запрос к базе данных завершился неудачей. Кроме того, класс BankImpl сообщает клиентам об операциях, затрагивающих их счет, формируя сообщения в канал сообщений.

    void BankImpl::OnDBChanged( database::AccountId id )
    {
    VISMutex var lock(  channelsMutex    );
    EventChannelsMap::const iterator it = channels.find(id );
    if (it != channels .end()   )
    it->second->add message(  id );
    }

    Интерфейс Account

    Интерфейс Account позволяет получать информацию о текущем состоянии счета, переводить средства на другой счет и запрашивать у сервера список транзакций. В IDL- файле этот интерфейс описан следующим образом:

    interface Account
    {
    readonly attribute wstring	HolderName;
    readonly attribute Money	Balance;
    readonly attribute AccountId	Id;
    readonly attribute string	ChannelIOR;
    TransactionList      Transactions();
    Transaction	ProcessTransaction(  in AccountId dest, in Money amount,   in wstring comment  )
    
    raises(AccountException)
    };

    Класс интерфейса Account

    Интерфейс Account реализуется объектами класса AccountImpl. Как и в случае класса BankImpl, объекты данного класса не взаимодействуют с базой данных напрямую, а делегируют вызовы объекту промежуточного класса. Кроме того, в момент первичного создания счета на него помещается стартовая сумма в 1000 денежных единиц. Полный код класса BankImpl приведен ниже.

    class AccountImpl
    :  public virtual POA banking::Account
    ,  public virtual PortableServer::RefCountServantBase
    {
    public:
    AccountImpl( database::AccountPtr account, std::string const channelIOR )
    :   account	(account)
    ,   channelIOR_ (channelIOR )
    	{
    	account_->PutMoney(  1000,   L"Initial balance", 0); 
    	std::cout << "Account created" << std::endl;
    	}
    ~AccountImpl() 
    	{
    	std::cout << "Account destructed" << std::endl;
    	}
    
    
    virtual wchar_t* 			HolderName() 	{ return CORBA::wstring_dup( account_->HolderName() ); }
    virtual ::CORBA::Float 		Balance() 	{ return account_->Balance(); }
    virtual ::CORBA::LongLong 	Id() 		{ return account_->Id(); }
    virtual char * 			ChannelIOR() 	{ return ORBA::string_dup(channelIOR_.c_str() ; }
    
    virtual banking::TransactionList* Transactions() 
    {
    database::TransactionsListPtr tl = account_->Transactions();
    banking::TransactionList_var res = new banking::TransactionList; 
    res->length(  tl->size() );
    CORBA::ULong i = 0;
    
    for ( database::TransactionsList::const_iterator it = tl->begin();  it != tl->end();  ++it )
    	{
    	res[i]=CreateTransaction( *it );
    	++i;
    	return res._retn();
    	}
    
    virtual banking::Transaction * ProcessTransaction( ::CORBA::LongLong _dest, ::CORBA::Float amount, const wchar t* comment)
    	{
    	database::Transaction trans;
    	CheckResult(  account_->ProcessTransaction( _dest,  _amount,  _comment, trans));
    	return new banking::Transaction(  CreateTransaction(  trans ));
    	}
    
    private:
    database::AccountPtr const account_;
    std::string	const channelIOR_;
    };

    Структура Transaction

    Структура Transaction содержит информацию об отдельно проведенной транзакции, затрагивающей текущий счет. Фактически, структура содержит одну строку из таблицы transactions. В IDL -файле эта структура описана следующим образом:

    struct Transaction
    {
    TransactionId id;
    Money amount;
    Time date; 
    AccountId source;
    AccountId destination;
    wstring comment; 
    };

    Классы работы с базой данных

    Все классы работы с базой данных вынесены в отдельное пространство имен database. Эти классы реализуют набор интерфейсов, практически полностью повторяющий внешние

    интерфейсы системы, что позволяет достаточно прозрачным для серверного приложения

    образом менять способ хранения данных. Рассмотрим код работы с базой данных на примере метода database ::AccountImpl:: ProcessTransaction. Код работы с SQL- сервером выделен жирным.

    ACCOUNT RESULT ProcessTransaction( AccountId dest, Money amount, String comment, Transaction * trans  )
    if (!IsAccountValid(  conn ,dest))
    	return AR_INVALID_ACCOUNT_ID;
    if  ( Balance() - amount < 0 )
    	return AR_NO_ENOUGH_MONEY;
    
    TransactionId id = ( conn_->Execute() << "select transactions_id.nextval from dual")->Fields->
    	GetItem("nextval")->Value;conn_->BeginTransaction();conn_->Execute() <<"update accounts set 
    	balance=(select balance+(" << amount<< ")   
    	from accounts where id=" << dest << ")  
    	where id=" << dest;conn_->Execute() << "update accounts set balance=(select balance-(" << amount<< ") 
    	from accounts where id=" << id_ << ")  
    where id=" << id_;conn_->Execute()  << "insert into transactions(id,source_account_id, 
    	dest_account_id, "<< "amount,  transaction_comment,  transaction_date)  
    	values ("<< id << ", " << id_ << ", 
    	" << dest << ", " << amount << ",'" << comment << "', 
    	"<< time( NULL )  << ")";conn_->CommitTransaction();
    if (trans)
    	*trans = ReadTransaction( id );
    return AR_OK;
    }

    Остальные классы пространства имен database взаимодействуют с базой данных аналогичным образом.

    Получение сообщений от базы данных о новых транзакциях

    В описании кода создания базы данных было уделено внимание передаче серверу сообщения о появлении новых записей в таблице transactions. Теперь рассмотрим код, принимающий эти сообщения. Ниже полностью приведен текст функции, работающей в отдельном потоке и получающей сообщения от сервера базы данных.

    DWORD 	stdcall ServerProc(  LPVOID data  )
    {
    database::IOnDBChanged * consumer = reinterpret_cast<database::IOnDBChanged *>(data);
    SOCKET sock = socket( AF_INET,   SOCK_STREAM,   IPPROTO_TCP  ); 
    hostent * localHost = gethostbyname(   ""  );
    char* localIP = inet_ntoa(  *(struct in addr *)*localHost->h_addr_list  );
    sockaddr_in saServer; 
    saServer.sin_family = AF_INET;
    saServer.sin addr.s_addr = inet addr(  localIP  ); 
    saServer.sin port = htons(  12345  );
    int res = bind(  sock,   (  sockaddr *  )   saServer,   sizeof(  saServer )   );
    assert( res != SOCKET_ERROR );
    res = listen(  sock,   SOMAXCONN );
    assert( res != SOCKET_ERROR );
    fd_set readfds; 
    FD_ZERO(  readfds  ); 
    FD_SET(  sock,   readfds  );
    
    while   (   !endListenSocket  select(  0,   readfds,  NULL,  NULL,  NULL )   != SOCKET_ERROR )
    	{
    	SOCKET incoming = accept(  sock,   0,   0  );
    	static const int SIZE = sizeof(database::AccountId); 
    	char buf[SIZE];
    	while (( res = recv( incoming, buf, SIZE, 0)) > 0) 
    		{
    		database::AccountId id = *reinterpret_cast< database::AccountId * >(buf );
    		consumer->OnDBChanged( id );
    		}
    	closesocket( incoming );
    	FD_ZERO( readfds  ); 
    	FD_SET( sock, readfds  );
    	}
    return 0;
    }

    Первые строки этой функции получают -адрес сервера банковской системы и создают слушающий сокет. Затем при поступлении соединения читаются 8 байт, формирующих номер счета участника транзакции, и, наконец, вызывается метод сервера банковской системы, который уже шлет сообщение об изменении счета клиентскому приложению.

    Клиентская часть

    Рабочее место оператора представляет собой standalone java -приложение. На рабочем месте оператора должна быть установлена Java и VisiBroker для поддержки событий с сервера. При запуске приложения, оно подключается к CORBA-серверу для получения банковского интерфейса (Рис. 2.9).

    (рис 2.9) Окно входа в систему
    byte[] bankId = "Bank".getBytes();
    orb_ = org.omg.CORBA.ORB.init(args,  null);
    remoteBanking = banking.BankHelper.bind(orb , "/banking poa", bankId);
    try 
    	{
    	org.omg.CORBA.Object obj  = orb.resolve_initial_references("RootPOA"); 
    	rootPOA = POAHelper.narrow(obj); 
    	}   
    catch   (InvalidName invalidName)   
    	{ invalidName.printStackTrace();
    	}

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

    (рис 2.10) Окно регистрации нового пользователя
    try 
    {
    acc = clientApplet_.getRemoteBanking().OpenAccount(account,  new String(PINPasswordField.getPassword()));
    }   
    catch   (BankException el)   
    {
    JOptionPane.showMessageDialog(clientApplet , el.message, "Error", JOptionPane.ERROR_MESSAGE);
    return;
    }

    В случае проблем с авторизацией на сервере произойдет Exception, который будет обработан на клиенте и будет показано соответствующее сообщение, описывающее причину отказа.

    При регистрации нового клиента (Рис. 2.10) производится соответствующий вызов банковского интерфейса.

    long accId = clientApplet.getRemoteBanking().CreateAccount( accountTextField.getText(), new String(PINPasswordField.getPassword()));
    
    JOptionPane.showMessageDialog(clientApplet, "Write down and remember your unique account number : 
    	" + accId, "Succeded", JOptionPane.INFORMATION_MESSAGE);

    При успешной авторизации клиента отображается список транзакций совершенных данным клиентом (Рис. 2.11).

    (рис 2.11) Основное окно клиентской программы

    Для обработки событий сервера и обновления списка транзакций используется PushViewModel

    pv = new PushView(clientApplet.getOrb(), account.ChannelIOR())   
    {
    public void push(org.omg.CORBA.Any data)   throws Disconnected 
    	{
    	UpdateAccountTable();
    	}
    public void disconnect push consumer()   
    	{
    	System.out.println("View.disconnect push consumer");
    	}
    };

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

    Обработка IDL-файла

    IDL-файл в чистом виде не пригоден для компиляции ни компиляторами языка С++,ни компиляторами Java.Для создания файлов, обрабатываемых этими компиляторами, необходимы дополнительные инструменты, которые "переведут" IDL -описания интерфейсов на язык, понятный компилятору. Для C++ соответствующий инструмент называется midl, а для Java - idl2java.Запуск midl производится средой разработки Microsoft $$\text{\textregistered}$$ Visual Studio™ 2005,а для генерации Java -файлов создан пакетный файл, который вызывает idl2java с корректно установленными путями.

    Java Stored Procedures

    В коде создания базы данных был упомянут интересный фрагмент, начинающийся со строки create or replace and compile java source named "Trigger" as.Данный фрагмент иллюстрирует очень мощную особенность СУБД Oracle,называемую хранимыми процедурами на языке Java (Java Stored Procedures).Эти процедуры исполняются виртуальной машиной Java,встроенной в сервер базы данных. Хранимые процедуры на Java и классы, их содержащие, должны отвечать следующим требованиям:

  • не должно быть конструкторов;
  • переменные и методы должны быть объявленными как static;
  • необходимо использовать текущее соединение с базой данных (не требуется имя пользователя и пароль);
  • необходимо объявить output -переменные как массивы.
  • Затем эти хранимые процедуры могут вызываться из триггеров, а так же их выполнение может быть инициировано приложениями, подключенными к базе данных.

    Тестовый клиент

    В процессе отладки системы был создан тестовый клиент на языке C++, который создает два счета и переводит в несколько приемов средства с одного из этих счетов на уведомления от сервера об изменении счета. Полный исходный код тестового клиента может быть найден в каталоге с исходными текстами системы. Вывод тестового клиента в процессе работы приведен на Рис. 2.12.

    (рис 2.12) Окно тестового клиента

    Состояния базы данных до и после выполнения тестового клиента приведены на Рис. 2.13 и Рис. 2.14 соответственно

    (рис 2.13) База данных до запуска тестового клиента(рис 2.14) База данных после запуска тестового клиента

    Пример "Книжный магазин "BookStore""

    Общее описание

    Разработаем систему заказов для книжного магазина, позволяющую:

  • обновлять и корректировать информацию о книгах, хранящуюся в базе данных (добавление новых книг, изменение цен и т.д.);
  • выполнять заказы клиентов и информировать их о произошедших изменениях;
  • обновлять информацию о книгах при ее изменении в базе данных.
  • Создание таблиц в базе данных

    Следующий код создает данную таблицу:

    create table BookStore
    (
    id	int primary key,
    name	varchar(255),
    author	varchar(255),
    publisher	varchar(255),
    price	float
    );

    Интерфейс системы

    Для связи СОRBA -объектов необходимо задать IDL -интерфейс, позволяющий выполнять запросы клиента и обеспечивать доступ к книгам и их модификацию.

    struct Book  
    {
    long	id;
    string	name;
    string	author;
    string	publisher;
    float price;
    }
    typedef sequence <Book> BookList;
    interface User  
    {
    BookList  getBookList ();
    string	getAdmin	();   // ior
    };
    
    interface Admin  
    {
    long	updateBook	(in Book book );
    };

    Структура Book позволяет описывать и хранить информацию о книге. Интерфейс User имеет два метода:

  • BookList getBookList (), данный метод возвращает список имеющихся на сервере книг, их названия, цену и прочую информацию;
  • string getAdmin (), данный метод возвращает строковое представление ior объекта для получения доступа к интерфейсу Admin.
  • Интерфейс Admin имеет единственный метод updateBook позволяющий добавлять новые книги и редактировать информацию о них.

    Обработка IDL-файла

    После описания IDL -интерфейс, необходимо сгенерировать вспомогательные классы для целевого языка, позволяющие реализовать методы интерфейса и предоставить их во внешнее пользование.

    Для клиента, написанного на Java,используется ORB из стандартной поставки JDK.

    idlj.exe -fall Server.idl

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

    Для сервера, написанного на C++,используется ORB из библиотеки ACE/TAO,соответственно, для генерации вспомогательных классов используется специальное приложение:

    tao_idl.exe -GI Server.idl

    Опция -GI позволяет сгенерировать дополнительные ServerI.h/.cpp файлы, в которых описан каркас реализации серванта, и остается лишь реализовать его методы. Полный список сгенерированных файлов при запуске указанной команды:

  • ServerC. h/.cpp - файлы, предоставляющие доступ к IDL -интерфейсам (аналогичные файлы используются в клиенте, реализованном на Java);
  • ServerS. h/. cpp - файлы, описывающие абстрактные классы необходимые для реализации сервантов;
  • ServerI.h/.cpp - каркас реализации серванта.
  • Получение доступа к CORBA-интерфейсу

    Клиенты, посылающие запросы к серверу реализованы на языке Java.В качестве ORB взят стандартный ORB, входящий в Java SDK.

    Для получения доступа к интерфейсу сервера, клиенту необходимо выполнить следующие действия.

  • Инициализация ORB и активизация POA -менеджера
    // create and initialize the ORB 
    ORB orb = ORB.init(args,  null);
    POA rootpoa =   (POA)orb.resolve_initial_references("RootPOA"); 
    rootpoa.the_POAManager().activate();
  • Получение доступа к объекту сервиса имен
    // get the root naming context
    org.omg.CORBA.Object objRef = orb.resolve initial references("NameService");
    // Use NamingContextExt instead of NamingContext.  This is 
    // part of the Interoperable naming Service.
    NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
  • Получение доступа к интерфейсу пользователя с помощью сервиса имен
    String name	= "BookStore.User";
    corbaUser     = UserHelper.narrow(ncRef.resolve_str(name));
  • Получение доступа к интерфейсу администратора с помощью значения ior
    String adminlor = corbaUser.getAdmin();
    org.omg.CORBA.Object adminObj  = orb.string_to_object(adminlor); 
    corbaAdmin = AdminHelper.narrow(adminObj);
  • Обратная связь. Сервис событий

    При изменении данных книги, сервер должен оповестить клиентов. Для этого можно использовать сервис событий (сервис EventService,входящий с состав пакета ACE/TAO).Для запуска сервиса событий необходимо выполнить следующие действия.

  • Получение доступа к объекту сервиса событий
    CORBA::Object var eventServiceObj = namingContext->resolve str("CosEventService"); 
    EventChannel_var eventChannel = EventChannel::_narrow(eventServiceObj.in());
  • Активизация сервиса и получение объекта поставщика событий
    SupplierAdmin_var supplierAdmin = eventChannel->for_suppliers(); 
    // Get a ProxyPushConsumer from the SupplierAdmin. 
    ProxyPushConsumer_var consumer =supplierAdmin->obtain_push_consumer(); 
    
    // Connect to the ProxyPushConsumer as a PushSupplier 
    //   (passing a nil PushSupplier object reference to it because 
    // we don't care to be notified about disconnects).
    consumer->connect_push_supplier(  CosEventComm::PushSupplier::_nil());
  • Для генерации события необходимо создать объект события и передать его поставщику событий:
    const CORBA::String var notificationMsg = CORBA::string_dup("BookStore.Notify"); 
    	  CORBA::Any any; any <<= notificationMsg; consumer->push(any);
  • Обработка событий

    На стороне клиента необходимо зарегистрировать обработчик событий. Для этого достаточно реализовать вспомогательный класс (в данном проекте это класс EventNotify ), унаследованный от базового класса - обработчика событий CosEventComm.PushConsumerPOA и реализующий методы, необходимые для обработки поступающих сообщений. Для регистрации обработчика событий необходимо:

  • Получить доступ к объекту сервиса событий
    // Find the EchoEventChannel.
    org.omg.CORBA.Object eventServiceObj = ncRef.resolve str("CosEventService"); 
    EventChannel echoEC =EventChannelHelper.narrow(eventServiceObj);
  • Создать обработчик событий
    // Instantiate an EchoEventConsumer_i servant. 
    notifier = new BookStore.Server.EventNotify(orb,   this); 
    // Register it with the RootPOA.
    byte  []  oid = rootpoa.activate object(notifier); 
    org.omg.CORBA.Object consumer_obj  =rootpoa.id_to_reference(oid); 
    CosEventComm.PushConsumer consumer =
    CosEventComm.PushConsumerHelper.narrow(consumer_obj);
  • Зарегистрировать обработчик событий
    // Get a ConsumerAdmin object from the EventChannel. 
    CosEventChannelAdmin.ConsumerAdmin consumerAdmin =echoEC.for_consumers(); 
    
    // Get a ProxyPushSupplier from the ConsumerAdmin. 
    CosEventChannelAdmin.ProxyPushSupplier supplier = consumerAdmin.obtain_push_supplier(); 
    
    // Connect to the ProxyPushSupplier,  passing our PushConsumer 
    // object reference to it.
    supplier.connect_push_consumer(consumer);
  • Связь с базой данных

    Для работы с базой данных сервер BookStore использует ODBC (Open Database Connectivity) драйвер. Покажем работу сервера с базой данных на примере запроса получения списка книг.

  • Инициализация драйвера и окружения ODBC
    SQLHENV henv;
    SQLAllocHandle(SQL_HANDLE_ENV,   NULL,   henv); 
    // tell to use 3rd ODBC version
    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3,
    SQL_IS_INTEGER); 
    SQLHDBC hdbc;
    SQLAllocHandle(SQL_HANDLE_DBC, henv, hdbc);
  • Соединение с базой данных
    SQLDriverConnect(hdbc, NULL, (SQLCHAR*)dsn, SQL_NTS,
    outStr, MAX_NUM, outStrLen, SQL_DRIVER_COMPLETE);

    При соединении указывается DSN (Data Source Name) - имя источника данных, установленных на компьютере, либо специфичные для конкретного драйвера параметры соединения.

  • Отправка запроса
    SQLAllocHandle(SQL_HANDLE_STMT, hdbc, hstmt);
    static SQLCHAR querySql[] = "select id,  name, author, publisher,  price from BookStore";
    SQLExecDirect(hstmt, querySql, SQL_NTS);
  • Обработка результатов запроса
    SQLBindCol(hstmt, i + 1, SQL_C_SLONG, (SQLPOINTER)uid, sizeof(uid)); 
    SQLBindCol(hstmt, i + 1, SQL_C_CHAR, (SQLPOINTER)name,  MAXLEN);
    SQLBindCol(hstmt, i + 1, SQL_C_CHAR,(SQLPOINTER)author, MAXLEN); 
    SQLBindCol(hstmt, i + 1, SQL_C_CHAR, (SQLPOINTER)publisher, MAXLEN); 
    SQLBindCol(hstmt, i + 1, SQL_C_DOUBLE, (SQLPOINTER)price, sizeof(price)); 
    for   (;;)   
    	{
    	if   (SQLFetch(hstmt)   == SQL_NO_DATA)   
    		{ break;
    		}
    	// process book info
    	}
  • Завершение обработки запроса
    SQLCloseCursor(hstmt);
  • Обратная связь: Oracle AQ

  • Создадим пользователя с правами на управление AQ
    create user BookAdmin identified by "12345" default tablespace USERS temporary tablespace TEMP;
    grant CONNECT, RESOURCE, CREATE SESSION, aq_administrator_role TO bookadmin; 
    grant EXECUTE on SYS.DBMS_AQ to BookAdmin;
    exec DBMS_AQADM.GRANT_SYSTEM_PRIVILEGE(privilege =>	'ENQUEUE_ANY', grantee =>'BookAdmin', admin option => FALSE);
    
    exec DBMS_AQADM.GRANT_SYSTEM_PRIVILEGE(privilege =>	'DEQUEUE_ANY', grantee =>'BookAdmin', admin option => FALSE);
  • Зайдем в Oracle под этим пользователем
    connect BookAdmin/12345;
  • Создадим очередь событий
    create type EventType as object(type int,   text varchar2(2000));
    exec DBMS_AQADM.CREATE_QUEUE_TABLE(queue_table =>  'EventTable', queue payload type =>  'BookAdmin.EventType');
    exec DBMS_AQADM.CREATE_QUEUE(queue_name =>  'EventQueue',   queue_table => 'EventTable');
    exec DBMS_AQADM.START_QUEUE(queue_name =>  'EventQueue');
  • Создадим триггер, помещающий событие в очередь при добавлении в таблицу
    create or replace trigger inserttrigger after insert on BookStore declare
    queue_options	DBMS_AQ.ENQUEUE_OPTIONS_T;
    message_properties   DBMS_AQ.MESSAGE_PROPERTIES_T;
    message id	RAW(16);
    my_message	BookAdmin.EventType;
    begin
    my_message	:= BookAdmin.EventType(1, 'insert');
    
    DBMS_AQ.ENQUEUE(
    queue_name =>  'BookAdmin.EventQueue', enqueue_options => queue_options, 
    	message_properties => message_properties, payload => my_message, msgid => message_id);
    end;
  • Сгенерируем Java имплементацию для типа BookAdmin.EventType
    set ORA_HOME=C:\oracle\ora92\
    set CLASSPATH=%ORA_HOME%jdbc\lib\classes12.zip;%ORA_HOME%sqlj\lib\translator.zip ;%ORA_HOME%sqlj\lib\runtime.zip
    
    jpub -user=BookAdmin/12345 -sql=EventType -usertypes=oracle -methods=false
  • Напишем код на Java,извлекающий события из очереди AQ
    String URL = "jdbc:oracle:thin:@server:1521:DB";
    String USER = "BookAdmin"; 
    String PASSWORD = "12345"; 
    String QUEUE_NAME = "EventQueue"; 
    
    // Connect to DB
    Class.forName("oracle.jdbc.driver.OracleDriver"); 
    Connection connection =DriverManager.getConnection(URL, USER, PASSWORD);
    connection.setAutoCommit(false); 
    
    // Connect to AQ
    Class.forName("oracle.AQ.AQOracleDriver");
    AQSession session = AQDriverManager.createAQSession(connection); 
    AQQueue queue = session.getQueue(USER,  QUEUE_NAME); 
    
    // Dequeue AQ message
    AQMessage message = queue.dequeue(new AQDequeueOption(), EVENTTYPE.getORADataFactory());
    
    EVENTTYPE m =(EVENTTYPE) message.getObjectPayload().getPayloadData();
  • Работа приложения

    Для доступа к базе данных книжного магазина необходимо запустить клиентское приложение r_admin.bat (Рис. 2.15).

    В центре окна отображается список книг. Получение списка книг осуществляется посредством использования метода getBookList объекта User, предоставляемого сервером.

    (рис 2.15) Клиентское приложение в действии

    После ввода информации добавляемой книги, а так же при изменении уже существующих параметров книги (Рис. 2.16), вызывается метод updateBook объекта Admin, предоставляемого сервером.

    (рис 2.16) Редактирование информации о книге

    На стороне сервера для предоставления соответствующих IDL -интерфейсов необходимо запустить серверное приложение r_server.bat.После запуска необходимо дождаться сообщения о завершении инициализации данных: запуск ORB,регистрация сервантов в сервисе имен, связь с базой данных (Рис. 2.17).

    (рис 2.17) Консоль запущенного сервера

    Приложение: словарь терминов CORBA

    BOA (Basic Object Adapter) - стандарт объектного адаптера до CORBA 2.2 (недостаточно полно специфицированный).

    CORBA (Common Object Request Broker Architecture) - технология создания распределенных приложений; стандарт, разработанный Object Management Group (OMG); независимая от языка реализации модель взаимодействия распределенных объектов. Позволяет создавать запросы между объектами на разных языках программирования.

    CORBA-объект - виртуальное понятие: нечто, посылающее запросы к другим CORBA- объектам - серверным объектам и получающее запросы от других CORBA -объектов - клиентов.

    IDL (Interface Definition Language) - язык описания интерфейсов в формате, который не зависит от языка программирования.

    IIOP (Internet Inter-ORB Protocol) - протокол передачи объектных запросов по TCP/IP.

    IOR (Interoperable Object Reference) - ссылка на объект, уникальная в пределах сервера (как правило, содержит идентификатор объекта как составную часть).

    Java IDL - не вполне корректное название реализации CORBA для Java (содержит не только компилятор IDL ).

    Java-IDL компилятор - компилятор описаний IDL в классы-заглушки и вспомогательные классы Java.

    ORB (Object Request Broker) - программа-транслятор межобъектного взаимодействия; работая на клиенте и на сервере, передает объектные запросы между ними.

    POA (Portable Object Adapter) - стандарт объектного адаптера начиная с CORBA 2.2 (достаточно полно специфицирован, является платформенно-независимым).

    Smart Agent - административная утилита, осуществляет поиск объектов в домене и балансировку нагрузки.

    Активация CORBA -объекта - запуск существующего CORBA -объекта для обработки клиентских запросов (в зависимости от политик объектного адаптера предполагает создание сервантов,занесение в карту активных объектов,и т.д.).

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

    Временный (transient) CORBA-объект - объект, который уничтожается с завершением активировавшего его потока.

    Деактивация CORBA -объекта - останов CORBA -объекта (разрыв связки между объектом и сервантом, в общем случае без разрушения объекта).

    Демон активации объектов (Object Activation Daemon, OAD) - демон, отслеживающий входящие запросы и активизирующий нужные объекты-серверы.

    Идентификатор объекта (Object ID) - уникальное имя объекта внутри его объектного адаптера.

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

    Карта активных объектов (Active Object Map) - таблица объектного адаптера,в которой он ведет реестр активных CORBA -объектов и связанных с ними сервантов (первые представлены в карте своими идентификаторами).

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

    Объектный адаптер - элемент технологии CORBA,отображающий понятие программно

    реализованных сервантов на концепцию CORBA -объектов; в его обязанности входит: создание CORBA -объектов и их объектных ссылок; демультиплексирование запросов на каждый серверный CORBA -объект; перенаправление запросы к соответствующему серванту, который обеспечивает реализацию серверного CORBA -объекта; активация и деактивация CORBA -объектов (соответственно, инкарнация и эфемеризация соответствующие серванты).

    Связывание языка программирования - правила трансляции IDL -описаний в код на данном языке; эти правила определены OMG.

    Сервант - физическая реализация CORBA -объекта; серверная программа, написанная на каком-либо из языков программирования и выполняющая CORBA -объект.

    Сервис именования (Naming Service) - CORBA -объект, который позволяет обнаружить другие объекты по имени. Может быть устойчивым (запоминать ссылки и имена после остановки) и временным (не запоминать).

    Скелетон - заготовка для серванта, генерируемая IDL -компилятором.

    Устойчивый (persistent) CORBA-объект - объект, который может существовать дольше, чем активировавший его поток.

    Эфемеризация серванта - разрушение связки CORBA -объект - сервант

    Вернуться к учебному плану