25 июля 2016 г.

Применение SAXON XSLT-процессора в Oracle SOA Suite 11g

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

В проектах по интеграции подсистем, зачастую возможна ситуация, когда необходимо использовать XSL-трансформации разработанные для другой подсистемы. В подсистемах, реализованных на разных платформах и/или использующих различные XSLT-процессоры, необходимо производить адаптацию трансформаций: заменять функции, способы приведение типов и прочее. Такую работу необходимо выполнять при каждом обновлении трансформаций, что крайне трудозатратно, подвержено ошибкам и изменению ожидаемого поведения.
Рассмотрим случай, когда на платформе Oracle SOA Suite 11g (OSS) требуется использовать сторонний XSLT-процессор.
Пример: OSS и Oracle JDK 7 по умолчанию использует XSLT-процессор Xalan, в то время как нашей системе предоставляются трансформации, написанные для SAXON, они регулярно обновляются и править их мы не можем, по каким-либо бизнес-ограничениям.

Мы имеем несколько путей решения:
1. Установить глобально, для всей платформы, XSLT-процессором по умолчанию SAXON.
Для этого скачиваем необходимую редакцию библиотеки с официального сайта проекта SAXON и добавляем её на наш сервер приложений, например, скопировав в $DOMAIN_HOME/lib.
Далее в дескрипторе развертывания weblogic-application.xml определяем стандартный класс для transformer-factory.
<xml>
    <parser-factory>
        <transformer-factory>net.sf.saxon.TransformerFactoryImpl</transformer-factory>
    </parser-factory>
</xml>
Плюсы:
  • простая реализация;
Минусы:
  • в уже существующей системе может потребоваться переработка ранее созданных трансформаций;
  • если ваша система развернута на общем с другими системами сервере приложений, это повлияет на их работу;
  • решение менять ключевой механизм, под который оптимизирована платформа, может породить больше проблем чем принести пользы.
2. Использовать Java Embedding для локального применения трансформаций (реализация будет описана ниже).
Плюсы:
  • отсутствие влияния на сторонние подсистемы сервера приложений;
  • простота реализации;
Минусы:
  • при наличии большого количества мест, в которых необходимо использовать данный функционал, для избегания дублирования кода, необходимо реализовать общее решение, например, отдельный веб-сервис (что не проблема для SOA), либо написать свой компонент для OSS, что несколько сложнее.
3. Написание прослойки для предобработки трансформаций и замены функций, но реализовать и сопровождать такой функционал крайне трудоёмко, так как не всегда есть аналогичные функции и их поведение может быть различным в зависимости от XSLT-процессора.

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

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

Использование Java Embedding для XSL-трансформаций в BPEL процессе.

Реализуем простой синхронный SOAP веб-сервис, стандартными средствами OSS, для чего создадим композит с одним BPEL-процессом. Единственной обязанностью веб-сервиса будет возврат результата выполнение XSL-преобразования над входными данными.

Первым делом необходимо добавить библиотеку SAXON в композит, для этого загруженную jar-библиотеку копируем в ProjectName\SCA-INF\lib, после чего добавляем её в classpath в свойствах проекта, как это показано на рисунке 1. 

Рисунок 1 - Свойства проекта

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

Определим входящее и исходящее сообщения в интерфейсе веб-сервиса, описанного в WSDL.

Входящее сообщение, 2 элемента:
  • данные подвергаемые трансформации;
  • тип данных (который будет совпадать с названием файла трансформации).
Исходящее сообщение, 2 элемента:
  • данные после трансформации;
  • результат работы (success/failure).
Первым шагом нам необходимо подготовить XSL-трансформацию.
Так как OSS платформой наложено ограничение на прямое обращение к MDS из BPEL-процесса, нам необходимо расположить трансформации в самом композите, например, в папке 'xsl'.

Рисунок 2 - BPEL процесс

С помощью функций ora:doc(), которая возвращает содержимое xml файла, загружаем трансформацию в переменную (рис.2 блок AssignXSLT). Вызов функции ora:doc() следует обернуть oraext:get-content-as-string(), для избежания проблем с переименованием корневых тэгов и работы с немодифицированным кодом трансформации.
<assign name="AssignXSLT">
    <copy>
        <from expression="concat('xsl/', bpws:getVariableData('inputVariable','payload','/client:processRequest/client:dataType'), '.xsl')"/>
        <to variable="xsltPath"/>
    </copy>
    <copy>
        <from expression="oraext:get-content-as-string(ora:doc($xsltPath))"/>
        <to variable="xsltVar"/>
    </copy>
</assign>
Используя комбинацию данных функций, возможно использовать файлы трансформаций расположенных как локально в композите, так и в MDS (обращаясь через префикс 'oramds:').

Вторым шагом, с помощью компонента Java Embedding, производим трансформацию (рис.2 блок JavaTransform). Самое важное в этом шаге инстанцировать фабрику реализацией SAXON (net.sf.saxon.TransformerFactoryImpl).
try { 
    String xsltVar = (String) getVariableData("xsltVar");  
    XMLElement xmlVarInput = (XMLElement) getVariableData("inputVariable", "payload", "/client:processRequest/client:inputData/*");  
    StringWriter xmlVarWriter = new StringWriter();  
    xmlVarInput.print(xmlVarWriter);  
    String xmlVar = xmlVarWriter.toString();   
    StringReader xsltReader = new StringReader(xsltVar);  
    TransformerFactory factory = TransformerFactoryImpl.newInstance("net.sf.saxon.TransformerFactoryImpl", null);  
    Transformer transformer = factory.newTransformer(new StreamSource(xsltReader));  
    StringReader xmlReader = new StringReader(xmlVar);  
    StringWriter resultWriter = new StringWriter();   
    transformer.transform(new StreamSource(xmlReader), new StreamResult(resultWriter));  
    setVariableData("xmlVar", resultWriter.toString());  
} catch (Exception ex) {  
    addAuditTrailEntry(ex.getMessage());  
}


Третьим и последним шагом записываем данные в ответное сообщение.
Так как в примере мы работали со строковым представлением XML, для дальнейшей работы необходимо распарсить её в XML, что можно сделать с помощью компонента Assign и стандартной функцией oraext:parseXML() (рис.2 блок AssignResult).
<assign name="AssignResult">
    <copy>
        <from expression="oraext:parseXML($xmlVar)"/>
        <to variable="outputVariable" part="payload"
            query="/client:processResponse/client:outputData"/>
    </copy>
    <copy>
        <from expression="'success'"/>
        <to variable="outputVariable" part="payload"
            query="/client:processResponse/client:resultState"/>
    </copy>
</assign>
Готовый веб-сервис необходимо собрать и развернуть на сервере приложений. Артефакт должен содержать используемую jar-библиотеку в папке ArtefactName.jar/SCA-INF/lib/, иначе, при разворачивании, возникнет ошибка неправильного classpath композита.

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

Таким образом, реализован механизм позволяющий локально использовать нестандартный XSLT-процессор. Рассмотренный подход, а именно поддержка Java и наличие стандартного компонента Java Embedding в платформе OOS, позволяет обеспечить большую гибкость и изоляцию механизмов платформы от реализуемого функционала.

Все примеры, используемые в статье, доступны на github.

Комментариев нет:

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