Те, кто сталкивались с необходимостью объединения нескольких источников данных, наверное, уже знают о 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