вторник, 26 апреля 2011 г.

Формирование MapMessage с помощью JMS адаптера Oracle SOA Suite


MapMessage является наиболее простым для разбора типом JMS-сообщения. Действительно, сообщение данного типа содержит лишь пары ключ - значение и не требует, например, парсинга XML или битовых преобразований для доступа к содержимому. Поэтому неудивительно, что такой формат сообщений завоевал определенную популярность среди Java-разработчиков. JMS-адаптер Oracle SOA Suite позволяет работать с данным типом сообщений. В заметке мы рассмотрим процесс создания MapMessage с помощью этого адаптера.

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

На первом шаге данного мастера необходимо указать наименование создаваемого адаптера.



Затем необходимо выбрать тип сервиса JMS. В простейшем случае можно использовать JMS-подсистему сервера приложений WebLogic.



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



После этого мастер предложит выбрать интерфейс адаптера, если таковой существует. Так как у нас нет WSDL-файла с описанием адаптера, то придется создать его с нуля. Для этого нужно выбрать пункт Define from operation and schema (specified later) и нажать Next.



JMS поддерживает три вида операций:
- Чтение сообщения (Consume Message);
- Запись сообщения (Produce Message);
- Синхронный обмен (Request/Reply);
В данном примере мы будем создавать MapMessage в очереди, поэтому необходимо выбрать пункт Produce Message.



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



Оставим значение поля Payload пустым и нажмем кнопку Next. Откроется последнее - информационное - окно мастера, в котором будет выведена информация о том, что произойдет после нажатия кнопки Finish.



После выполнения всех этих манипуляций, адаптер будет создан и настроен на работу с сообщениями типа MapMessage. Теперь можно добавить в композит BPEL-процесс, который должен иметь следующую структуру:



В принципе, процесс достаточно тривиален: он запускается с помощью оператора Receive, затем заполняет создаваемое сообщение с помощью оператора Assign и помещает его в JMS-очередь с помощью оператора Invoke.

Основная логика процесса сосредоточена в операторе Assign. Именно здесь происходит формирование сообщения. Для сообщений типа MapMessage Oracle SOA Suite генерирует специальную схему - {http://xmlns.oracle.com/pcbpel/adapter/jms/MapMessage}MapMessage:

<schema targetNamespace="http://xmlns.oracle.com/pcbpel/adapter/jms/MapMessage"

       xmlns="http://www.w3.org/2001/XMLSchema"

       xmlns:tns="http://xmlns.oracle.com/pcbpel/adapter/jms/MapMessage"

       elementFormDefault="qualified">

 <element name="MapMessage">

  <complexType>

   <choice maxOccurs="unbounded">

    <element name="entry">

     <complexType>

      <simpleContent>

       <extension base="string">

        <attribute name="name" type="string"/>

        <attribute name="dt" type="string"/>

       </extension>

      </simpleContent>

     </complexType>

    </element>

   </choice>

  </complexType>

 </element>

</schema>

 


Как видно из схемы, сообщение состоит из последовательности элементов entry, имеющих атрибут name, представляющий собою ключ, и атрибут dt, представляющий собою тип значения. Само значение передается как значение элемента entry. Таким образом создание сообщения типа MapMessage сводится к созданию нужного количества элементов entry и корректному заполнению их значений и значений атрибутов name и dt.



Исходный код оператора Assign следующий:

  1. <assign name="AssignForMapJMS">
  2.       <copy>
  3.         <from expression="'KEY1'"/>
  4.         <to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  5.            part="body" query="/ns2:MapMessage/ns2:entry/@name"/>
  6.       </copy>
  7.       <copy>
  8.         <from expression="'String'"/>
  9.         <to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  10.            part="body" query="/ns2:MapMessage/ns2:entry/@dt"/>
  11.       </copy>
  12.       <bpelx:append>
  13.         <bpelx:from expression="'VALUE1'"/>
  14.         <bpelx:to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  15.                  part="body" query="/ns2:MapMessage/ns2:entry"/>
  16.       </bpelx:append>
  17.       <bpelx:insertAfter>
  18.         <bpelx:from><ns2:entry name="" dt=""
  19.                               xmlns:ns2="http://xmlns.oracle.com/pcbpel/adapter/jms/MapMessage"/></bpelx:from>
  20.         <bpelx:to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  21.                  part="body" query="/ns2:MapMessage/ns2:entry"/>
  22.       </bpelx:insertAfter>
  23.       <copy>
  24.         <from expression="'KEY2'"/>
  25.         <to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  26.            part="body" query="/ns2:MapMessage/ns2:entry[2]/@name"/>
  27.       </copy>
  28.       <copy>
  29.         <from expression="'String'"/>
  30.         <to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  31.            part="body" query="/ns2:MapMessage/ns2:entry[2]/@dt"/>
  32.       </copy>
  33.       <bpelx:append>
  34.         <bpelx:from expression="'VALUE2'"/>
  35.         <bpelx:to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  36.                  part="body" query="/ns2:MapMessage/ns2:entry[2]"/>
  37.       </bpelx:append>
  38.     </assign>


Данный оператор имеет несколько особенностей.

Во-первых, по-умолчанию переменная типа MapMessage содержит только один элемент entry. Соответственно, прежде чем заполнять значениями второй и последующие (в BPEL нумерация ведется с единицы) элементы - их нужно создать. Делается это с помощью операции insertAfter - специфичного для Oracle расширения языка BPEL.

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

<entry name="KEY1" dt="String"/>


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

<entry>VALUE1</entry>


Т.е. значения атрибутов потеряются.

Если сделать наоборот, т.е. сначала заполнить собственное значение элемента, то он будет выглядеть как в примере выше, т.е. у него не будет атрибутов и попытка присвоить значение атрибуту закончится неудачно.

Чтобы решить данную проблему необходимо воспользоваться операцией append - еще одним специфичным для Oracle расширением языка BPEL. Логика следующая: сначала заполняются значения атрибутов, а затем с помощью операции append добавляется собственное значение элемента. Данное значение не заменит существующее, как происходит при выполнении операции copy, а добавится к нему.

Однако при использовании операции append есть одна особенность: если копируется не константа, а содержимое некоторой переменной, то данное содержимое должно быть очищено от обрамляющих тегов, например с помощью XPath-выражения string(). Рассмотрим подробнее. Пусть существует переменная value типа xsd:string:

<value>VALUE</value>


Если добавить ее значение к тегу entry с помощью операции append, то получится следующее:

<entry name="KEY" dt="String">

   <value>VALUE</value>

</entry>


В то время как ожидается:

<entry name="KEY" dt="String">VALUE</entry>


Поэтому стоит быть внимательным.

Сообщение, сформированное с помощью рассмотренного оператора Assign выглядит следующим образом:

  1. <InvokeWriteToMapJMS_Produce_Message_InputVariable>
  2.     <part  name="body">
  3.          <MapMessage>
  4.               <entry  name="KEY1"  dt="String">VALUE1</entry>
  5.               <ns2:entry  name="KEY2"  dt="String">VALUE2</ns2:entry>
  6.          </MapMessage>
  7.     </part>
  8. </InvokeWriteToMapJMS_Produce_Message_InputVariable>
  9.  


Данное сообщение MapMessage содержит поля:
KEY2 - VALUE2
KEY1 - VALUE1

и свойства:
tracking_ecid - c91ab9e973f25cc1:f2bd52d:12f8146465b:-8000-0000000000002671
tracking_compositeInstanceId - 20014
tracking_parentComponentInstanceId - bpel:20014
tracking_conversationId - urn:5BD35DF06D8D11E0BFD0DF9E097C905F
JMSXDeliveryCount - 1


Теперь вернемся к рассмотрению параметра Payload, который редактируется на седьмом шаге мастера настройки JMS-адаптера. Данный параметр доступен только если выбрать тип сообщения, равный MapMessage. Роль параметра следующая: если его значение не пустое, то он позволяет передавать в MapMessage единственное поле, ключ которого совпадает с заданным названием параметра, а значением является строка в формате XML.



Если задать некоторое значение для параметра Payload, например - payload, то добавится новый шаг мастера, позволяющий выбрать схему для XML-сообщения, которое будет значением единственного поля MapMessage. Здесь есть важная особенность: если в качестве схемы оставить {http://xmlns.oracle.com/pcbpel/adapter/jms/MapMessage}MapMessage:



то поведение адаптера не изменится, т.е. значение параметра Payload будет проигнорировано.

Рассмотрим на примере работу адаптера, если схема отлична от {http://xmlns.oracle.com/pcbpel/adapter/jms/MapMessage}MapMessage. Создадим файл mapdemo.xsd следующего содержания:

<?xml version="1.0" encoding="UTF-8" ?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"

           xmlns:map="http://samolisov.blogspot.com/mapdemo"

           targetNamespace="http://samolisov.blogspot.com/mapdemo"

           elementFormDefault="qualified">

  <xsd:element name="demo">

    <xsd:complexType>

      <xsd:sequence>

        <xsd:element name="key1" >

          <xsd:complexType>

            <xsd:attribute name="name" type="xsd:string"></xsd:attribute>

          </xsd:complexType>

        </xsd:element>

        <xsd:element name="key2">

          <xsd:complexType>

            <xsd:attribute name="name" type="xsd:string"></xsd:attribute>

          </xsd:complexType>

        </xsd:element>

      </xsd:sequence>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

 


и выберем его в качестве схемы для payloadJMS-адаптера:



Исходный код оператора Assign, заполняющего переменную типа demo, может быть следующим:

  1.     <assign name="AssignForMapJMS">
  2.       <copy>
  3.         <from expression="'ATTR1'"/>
  4.         <to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  5.            part="body" query="/ns3:demo/ns3:key1/@name"/>
  6.       </copy>
  7.       <bpelx:append>
  8.         <bpelx:from expression="'KEY1'"/>
  9.         <bpelx:to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  10.                  part="body" query="/ns3:demo/ns3:key1"/>
  11.       </bpelx:append>
  12.       <copy>
  13.         <from expression="'ATTR2'"/>
  14.         <to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  15.            part="body" query="/ns3:demo/ns3:key2/@name"/>
  16.       </copy>
  17.       <bpelx:append>
  18.         <bpelx:from expression="'KEY2'"/>
  19.         <bpelx:to variable="InvokeWriteToMapJMS_Produce_Message_InputVariable"
  20.                  part="body" query="/ns3:demo/ns3:key2"/>
  21.       </bpelx:append>
  22.     </assign>


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

<InvokeWriteToMapJMS_Produce_Message_InputVariable>

    <part  name="body">

        <demo>

            <key1  name="ATTR1">KEY1</key1>

            <key2  name="ATTR2">KEY2</key2>

        </demo>

    </part>

</InvokeWriteToMapJMS_Produce_Message_InputVariable>

 


Созданное в результате работы BPEL-процесса JMS-сообщение будет содержать единственное поле payload:

<?xml version="1.0" encoding="UTF-8" ?><demo xmlns="http://samolisov.blogspot.com/mapdemo">

   <key1 name="ATTR1">KEY1</key1>

   <key2 name="ATTR2">KEY2</key2>

</demo>


и следующие свойства:
prop: tracking_ecid - c91ab9e973f25cc1:f2bd52d:12f8146465b:-8000-0000000000002d41
prop: tracking_compositeInstanceId - 20015
prop: tracking_parentComponentInstanceId - bpel:20015
prop: tracking_conversationId - urn:61F836D06D9011E0BFD0DF9E097C905F
prop: JMSXDeliveryCount - 1


На седьмом шаге мастера помимо поля Payload существует галочка As Attachment, которая становится доступной, только если значение поля Payload не пустое. Данную галочку следует отметить, если предполагается использовать JMS-адаптер для передачи в очередь вложений. Вложение - это большая порция данных, например бинарный файл, адресуемая уникальным идентификатором, генерируемым Oracle SOA Suite. В общем случае, вложения не требуют явной обработки внутри композитного приложения и лишь передаются из адаптера в адаптер путем копирования идентификатора, что позволяет существенно повысить производительность. Oracle SOA Suite содержит средства, позволяющие обрабатывать вложения, в частности редактировать, но обсуждение данных средств выходит за рамки сегодняшнего разговора.



Если галочка As Attachment отмечена, то созданное с помощью адаптера сообщение будет содержать одно поле. В качестве ключа будет выступать значение параметра Payload, а значением будет массив байт, представляющий собой переданное вложение.

JMS-адаптер, настроенный на обработку вложений, описывается следующей XML-схемой:

  1. <schema targetNamespace="http://xmlns.oracle.com/pcbpel/adapter/jms/attachment/"
  2.        xmlns="http://www.w3.org/2001/XMLSchema"
  3.        xmlns:tns="http://xmlns.oracle.com/pcbpel/adapter/jms/attachment/"
  4.        elementFormDefault="qualified">
  5.  <element name="attachmentElement">
  6.   <complexType>
  7.    <attribute name="href" type="string"/>
  8.   </complexType>
  9.  </element>
  10. </schema>


В качестве примера рассмотрим чтение файла с помощью файлового адаптера как вложения и его передачу в JMS-очередь.

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



На первом шаге мастера необходимо указать название адаптера.



Затем определить интерфейс адаптера. Т.к. у нас нет соответствующего WSDL-файла, то следует выбрать пункт Define from operation and scheme (specified later).



На следующем шаге мастера необходимо определить операцию, выполняемую адаптером. Файловый адаптер поддерживает следующие типы операций:
- Чтение файла (Read File)
- Запись файла (Write File)
- Синхронное чтение файла (Synchronous Read File)
- Получение списка файлов (List Files).

Необходимо выбрать операцию Read File. На данном же шаге можно указать, что файлы будут считываться как вложения, отметив соответствующую галочку. После этого станут доступны поля, характеризующие вложение: Character Set, Encoding и Content Type - MIME-тип содержимого вложения.



На следующем шаге мастера необходимо выбрать расположение считываемых файлов и указать нужно ли обрабатывать файлы рекурсивно. Можно указать каталог, куда будут складываться архивные копии обработанных файлов. Одно замечание по поводу параметра Delete files after successful retrieval. В случае обработки файла как вложения данный параметр должен быть выключен, иначе при попытке удалить файл будет сгенерировано исключение.



После этого следует выбрать формат имени обрабатываемых файлов. Для примера будем обрабатывать бинарные файлы формата png - изображения.



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



Последняя страница мастера - информационная. На ней указано, что произойдет после нажатия кнопки Finish.



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



Схема, описывающая интерфейс созданного файлового адаптера следующая:

  1. <schema targetNamespace="http://xmlns.oracle.com/pcbpel/adapter/file/attachment/" xmlns="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  2.     <element name="attachmentElement" >
  3.         <complexType>
  4.             <attribute name="href" type="string" />
  5.         </complexType>
  6.     </element>
  7. </schema>


Видно, что данная схема похожа на схему JMS-адаптера, настроенного на обработку вложений. Для того, чтобы передать вложение из файлового адаптера в JMS-адаптер, нужно присвоить значение атрибута href элемента attachmentElement одного адаптера другому:



Результатом работы JMS-адаптера будет сообщение, у которого есть только одно поле с ключом payload и значением, равным байтовому представлению считанного файла. С целью тестирования можно, например, записать данное байтовое представление на диск. Должен получиться корректно обрабатываемый графический файл в формате png.

Мы рассмотрели все аспекты генерации JMS-сообщений типа MapMessage с помощью Oracle SOA Suite. В сети присутствует устаревшая документация, которая зачастую вводит в заблуждение. Как я понял, JMS адаптер некоторое время эволюционировал и корректная поддержка типа MapMessage в нем появилась не сразу.

У меня есть вопрос к уважаемым читателям. Является ли тема Oracle SOA Suite в общем и Oracle BPEL в частности востребованной? О каких аспектах применения данных технологий вам было бы интересно почитать?

Оставайтесь на связи!

Понравилось сообщение - подпишитесь на блог

6 комментариев:

Евгений Вершовский комментирует...

Добрый день и спасибо за статью в частности и блог в целом. Лично для меня в данный момент тема более чем востребована, поэтому в силу необъективности прошу не засчитывать мой ответ :)

У меня куча вопросов, так как знакомство с SOA (Orecle SOA Suite) произошло меньше месяца назад и сейчас полным ходом идет этап вникания и перелопачивания различных tutorial'ов и developer guide'ов. Огорчает практически полное отсутствие толковых материалов на русском языке с примерами (данный блог - приятное исключение). Потому как с Fusion Order Demo дело так и не наладилось. Был сильно удивлен количеству самых натуральных шаманств уже на этапе установки SOA Suite, что уж говорить про настройку и деплой Fusion Order Demo.
Как бы то ни было, буду рад любым статьям по теме BPEL и Oracle SOA Suite :)

Unknown комментирует...

Добрый день, Евгений.

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

Во-вторых, у Oracle все же довольно толковая документация, в которой можно найти много нового и интересного. Главное - читать ее внимательно. Это не SAP, здесь жить вполне можно. Не стоит грустить. С третьего-четвертого раза SOA Suite ставится вполне уверенно уже без мануала :)

З.Ы. Fusion Order Demo я деплоить и исследовать не стал.

Евгений Вершовский комментирует...

Да. Действительно, раза с четвертого ставится всё довольно просто, по проторенной дорожке легче идти :)
Возник вот какой вопрос: BPEL композит, который пишет в JMS очередь построен, а как быть, если требуется при получении сообщения в JMS очередь выполнить какие-либо действия? Вариант с MDB возможен, но хотелось бы обойтись визуальным моделированием в SOA Suite. Пытался в пустом композите в левой колонке Partner's Link добавить JMS адаптер и от него делать invoke в BPEL process, но, похоже, такой возможности не предусмотрено. Как же всё-таки это можно реализовать?

Unknown комментирует...

Не очень понял, что вы хотите? Слушать факт получения сообщения из очереди или слушать саму очередь?

Если вы хотите слушать саму очередь, то нужно разработать композит, в котором определить JMS-адаптер с действием Consume, по которому и будет запускаться BPEL-процесс (через Receive или Pick). Как слушать факт считывания сообщения другим слушателем я не знаю.

З.Ы. Если вы хотите обмениваться сообщениями между композитами через JMS, то лучше создавать не MapMessage's, а TextMessage's с произвольным XML-ем внутри.

Евгений Вершовский комментирует...

Да. Именно это и нужно - слушать очередь и при появлении в ней сообщения запускать BPEL-процесс. Смущало, что при создании BPEL-процесса автоматически создавался сервис, его инициирующий, и было непонятно, как тогда делать вызов из JMS адаптера. А всего-то нужно было в типе BPEL-процесса указать Define Service Later :)
Спасибо.
p.s. в моей задаче ничего, кроме BytesMessage's не подходит (необходимо пересылать бинарники).

Unknown комментирует...

Автоматически генерируемый WebService-адаптер тоже можно оставить и использовать, например, для тестирования. Запуск процесса при этом производить не через Receive, а через Pick.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!