Те, кто сталкивались с необходимостью объединения нескольких источников данных, наверное, уже знают о JBoss Teiid, вводная статья о нём есть даже на хабре. Коротко говоря, эта система предназначена для для представления нескольких физических источников данных (например, СУБД) в виде одной виртуальной базы данных (virtual database, VDB) с доступом по SQL.
Штатно Teiid поддерживает многие источники данных, например, Oracle, DB2, M$ SQL Server, MySQL, PostgreSQL, SalesForce, но, вместе с тем, предоставляет также и удобные инструменты для работы с web-сервисами, XML, JSON. На базе этого инструментария можно легко построить доступ к несложному источнику данных (например, делать запросы в twitter, в поставке Teiid есть готовый пример), и обойтись при этом только DDL-описанием. Но для чего-либо посложнее уже требуется писать код.
В этой части мы рассмотрим способ описания с помощью DDL, в следующей будем писать транслятор.
В общем случае для организации доступа к источнику требуются две составных части: коннектор (connector) и транслятор (translator). Коннектор отвечает за непосредственно соединение, тогда как транслятор агрегирует исходные данные (запрос), отправляет их через соединение, предоставленное коннектором, получает ответ и преобразует его в понятную для Teeid форму.
Традиционно для иллюстрации описываемого в постах на хабре авторы используют что-нибудь, имеющее отношение к хабру. Почему бы и нет? В качестве источника данных мы возьмём хабр-апи: http://habrahabr.ru/api/profile/%name%.
Среди прочих коннекторов, поставляемых с Teiid, есть универсальный WS connector, подходящий для любого сервиса, предоставляющего доступ по http/https. Им мы и воспользуемся для доступа к web-сервису хабра.
Базовые настройки
Для начала необходимо прописать источник данных. Для JBoss 7 в файл %JBOSS_HOME%/standalone/configuration/standalone.xml (или ...domain...) нужно добавить такое:
<subsystem xmlns="urn:jboss:domain:resource-adapters:1.0">
[...]
<resource-adapters>
<resource-adapter>
<archive>teiid-connector-ws.rar</archive>
<transaction-support>NoTransaction</transaction-support>
<connection-definitions>
<connection-definition class-name="org.teiid.resource.adapter.ws.WSManagedConnectionFactory" jndi-name="java:/habrDS" enabled="true" use-java-context="true" pool-name="habr-ds"><!-- [1] -->
<config-property name="EndPoint">http://habrahabr.ru/api/profile/</config-property><!-- [2] -->
</connection-definition>
</connection-definitions>
</resource-adapter>
</resource-adapters>
[...]
Тут есть 2 важных момента: в строке с отметкой [1] мы задаем JNDI-имя нашего data source (это имя мы будем использовать в дальнейшем), а в строке с отметкой [2] — end point — URL нашего сервиса.
Пишем DDL
Я уже упоминал выше о примере реализации запросов в twitter, который поставляется с Teiid. Для того, чтобы получить доступ к хабр-апи таким же способом, достаточно написать правильный DDL в VDB-файле.
Итак, habr-vdb.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vdb name="habr" version="1">
<model name="habr">
<source name="habr" translator-name="rest" connection-jndi-name="java:/habrDS"/>
</model>
<model name="habrview" type="VIRTUAL">
<metadata type="DDL"><![CDATA[
CREATE VIRTUAL PROCEDURE getHabr(name varchar) RETURNS (login varchar(128), karma float, rating float,
ratingposition long) AS
select ha.* from
(call habr.invokeHTTP(action => 'GET', endpoint =>querystring(name))) w,
XMLTABLE('habrauser' passing XMLPARSE(document w.result) columns
login varchar(128) PATH 'login',
karma float PATH 'karma',
rating float PATH 'rating',
ratingposition long PATH 'ratingPosition') ha;
CREATE VIEW Habr AS select * FROM habrview.getHabr;
]]> </metadata>
</model>
<translator name="rest" type="ws">
<property name="DefaultBinding" value="HTTP"/>
<property name="DefaultServiceMode" value="MESSAGE"/>
</translator>
</vdb>
По сути, это — уже готовая реализация нашего источника данных, т.к. написанного достаточно, чтобы получить информацию о любом юзере через хабр-апи.
Разбираем код подробнее
<translator name="rest" type="ws"/>
— способ описание нового транслятора через наследование (type — имя родительского транслятора, name — имя создаваемого транслятора). Предназначен этот способ для того, чтобы можно было задавать значение свойств (properties), отличных от значений по умолчанию. Т.к. значение по умолчанию свойства DefaultBinding = SOAP12
, то мы не можем напрямую использовать транслятор ws.
Штатный WS connector реализует процедуру invokeHTTP(), через которую и работает весь его функционал. <model name="habr"/>
служит для подключения нашего источника данных habrDS (мы описывали его выше, в standalone.xml) и вновь созданного транслятора 'rest'. Таким образом, при вызове habr.invokeHTTP()
мы вызываем транслятор с конкретными параметрами и заданным URL web-сервиса.
Для непосредственной обработки данных мы должны создать дополнительную модель — <model name="habrview" type="VIRTUAL"/>
: дело в том, что описывать сущности БД через DDL мы можем только в моделях, имеющих type="VIRTUAL"
, с другой стороны, указывать источники данных и трансляторы мы можем только в не-виртуальных моделях, а нам необходимо и то, и другое.
Здесь всё просто: создаём виртуальную процедуру getHabr
, для которой описываем входной параметр и результаты, и которая реализуется через SELECT-запрос, который, в свою очередь, вызывает habr.invokeHTTP()
для выполнения GET-запроса к web-сервису (с назначением результату алиаса w
). Здесь параметр name
передаётся из виртуальной процедуры в реальную. habr.invokeHTTP()
возвращает полученные данные в параметре result, который далее передаётся во встроенную функцию XMLPARSE(document w.result)
, где document
означает, что это well-formed XML, а не фрагмент. Эта функция парсит полученные данные и уже в виде XML-дерева передаёт дальше, в функцию XMLTABLE
, для которой мы так же, как и для виртуальной процедуры, задаём список колонок с указанием типов, а кроме типов указываем ещё и пути, по которым значения будут доставаться из XML-документа. 'habrauser'
означает базовый путь.
И последний шаг: создаём view, которое реализуется, как вызов виртуальной процедуры и, соответственно, заимствует список её выходных параметров в качестве своей структуры.
Всё. Осталость только сделать запрос:
select * from habrview.habr where name='elfuegobiz'
Автор: elfuegobiz