一般に、翻訳者は4つの部分(クラス)で構成されます。 これは、
ExecutionFactory
クラスの子孫であり、
ResultSetExecution
、
ProcedureExecution
および
UpdateExecution
実装です。 さらに、標準のTeiidトランスレータは、メタデータプロセッサ(メタデータプロセッサ)を別のクラスに割り当てますが、インターフェイスを実装せず、何からも継承しません。 すべてを順番に考えてみましょう。
翻訳パーツ
-
ExecutionFactory
の開始点、その名前と説明、そのプロパティが設定される場所、サポートするSQL構成要素が記述され、メタデータが生成されます(つまり、トランスレーターが実装するテーブルとプロシージャの記述が作成されます)必要に応じて、SELECT、INSERT / UPDATE / DELETEクエリおよび組み込みプロシージャコールを直接処理する上記のインターフェイスのインスタンスが作成されます。 -
ResultSetExecution
実装がSELECTクエリの処理に使用されるインターフェイス。 トランスレータでは実装が利用できない場合があります。 -
ProcedureExecution
組み込みプロシージャへの呼び出しを処理するために実装が使用されるインターフェイス。 トランスレータでは実装が利用できない場合があります。 -
UpdateExecution
実装がINSERT / UPDATE / DELETE要求の処理に使用されるインターフェース。 トランスレータでは実装が利用できない場合があります。
さらに、これらの点について前進します。
基本設定
Teiidが翻訳者を見るためには、結果のjarにMETA-INF \ services \ org.teiid.translator.ExecutionFactoryファイルを追加して、次の行を追加する必要があります。
ru.habrahabr.HabrExecutionFactory
さらに、JBoss 7の場合、これをファイル%JBOSS_HOME%/ standalone / configuration / standalone.xml (または[..] domain [..] )に追加する必要があります。
<subsystem xmlns="urn:jboss:domain:teiid:1.0"> [..] <translator name="habrahabr" module="ru.habrahabr"/> [..]
ここで、翻訳者が配置されているモジュールをサーバーに伝えます。 私たちの翻訳者と一緒にjar ファイルをru.habrahabrモジュールに配置する必要があることは明らかです。
そしてここで、前の記事と同様に、翻訳者のデータソースを登録する必要があります。
<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"> <config-property name="EndPoint">http://habrahabr.ru/api/profile/</config-property> </connection-definition> </connection-definitions> </resource-adapter> </resource-adapters> [...]
モデルの説明
最も単純なケースでは、モデル自体またはトランスレーターのいずれにもパラメーターが設定されていない場合、vdbファイルのモデル記述は次のようになります。
<model name="habr"> <source name="habr" translator-name="habrahabr" connection-jndi-name="java:/habrDS"/> </model>
それだけです
翻訳者のプロパティを設定する必要がある場合は、翻訳者の相続人を説明する必要があります。
<model name="habr"> <source name="habr" translator-name="habrahabr2" connection-jndi-name="java:/habrDS"/> </model> <translator name="habrahabr2" type="habrahabr"> <property name="defaultUser" value="elfuegobiz"/> </translator>
また、メタデータコレクターのパラメーターも指定する必要がある場合は、さらに1行追加されます。
<model name="habr"> <source name="habr" translator-name="habrahabr2" connection-jndi-name="java:/habrDS"/> <property name="importer.convertToUppercase" value="true"/> </model> <translator name="habrahabr2" type="habrahabr"> <property name="defaultUser" value="elfuegobiz"/> </translator>
ExecutionFactory
ここでこれを行うことができます:
- 翻訳者の名前と説明を設定します。
- 翻訳者のプロパティを説明します。
- サポートするSQLコンストラクトを説明してください。
- サポートされているテーブルとプロシージャのメタデータを作成します。
- リクエストを処理するクラスインスタンスを作成します。
行きましょう。
翻訳者の名前と説明
package ru.habrahabr; import javax.resource.cci.ConnectionFactory; import org.teiid.translator.ExecutionFactory; import org.teiid.translator.Translator; import org.teiid.translator.WSConnection; @Translator(name = "habrahabr", description = "A translator for Habrahabr API") public class HabrExecutionFactory extends ExecutionFactory<ConnectionFactory, WSConnection> { }
Translator
アノテーションはクラスを
Translator
クラスとしてマークし、
name
パラメーターはシステムで表示される名前を設定し、
description
任意のテキストの説明。 標準のWSコネクターTeiidを使用するという事実に基づいて、継承のパラメーターを設定します。
トランスレーターのプロパティ
上記の「モデルの説明」の章では、
defaultUser
プロパティ値を指定しました。 次に、トランスレータコードに実装します。
import org.teiid.translator.TranslatorProperty; [..] private String defaultUser; @TranslatorProperty(description="Default user name", display="Default user name to use for table queries", required=true) public String getDefaultUser() { return defaultUser; } public void setDefaultUser(String defaultUser) { this.defaultUser = defaultUser; }
ゲッターの
TranslatorProperty
アノテーションは、プロパティの短く詳細な説明を定義します。 このプロパティが必要な場合は、
required=true
パラメーターを注釈に追加できます。
翻訳者インスタンスを作成すると、Teiidはこのフィールドにvdbファイル内の翻訳者記述からの値を自動的に入力します。
SQLコンストラクトのサポート
ExecutionFactory
クラスには、トランスレータがこの機能をサポートしているかどうかを示す
boolean
値を返す多くのサポートされたマスクメソッドがあります。 翻訳者がサポートしていない機能、Teiidは独自の手段を実装しています。 たとえば、翻訳者が集計関数
sum()
を処理できない場合、Teiidは翻訳者にデータを要求し、金額を計算します。
これらの設定を変更するには、ほとんどすべての設定をオーバーラップする必要があり、コンストラクターで呼び出すことができる対応するセッターを持っているのはごくわずかです。 たとえば、
count(*)
クエリと
limit <>
構造のサポートを実装する場合、対応するメソッドをオーバーライドし、それらで
true
を返し
true
。
@Override public boolean supportsAggregatesCountStar() { return true; } @Override public boolean supportsRowLimit() { return true; }
データソース自体に実装用のAPIがある場合、これらすべての可能性を実装することは理にかなっています。 ただし、翻訳者は非常にシンプルです。ソースに合わせて、次の記事では、聴衆がトピックに興味を示している場合は、より複雑な翻訳者を作成することがあります。
テーブルとプロシージャのメタデータ
トランスレータに実装されたテーブルとプロシージャを記述するために、
ExecutionFactory
クラスには特別な
getMetadata(MetadataFactory metadataFactory, WSConnection conn)
メソッド
getMetadata(MetadataFactory metadataFactory, WSConnection conn)
、これをオーバーラップして実装する必要があります。
MetadataFactory
クラスのインスタンスを取得します。このクラスには、テーブル、プロシージャなどを作成するためのメソッドと、Webサービスへの既に確立された接続のオブジェクトがあります。たとえば、ソースからテーブルの数と名前に関するデータを要求する必要がある場合。 私たちの場合、何を実装したいかを事前に知っています。
@Override public void getMetadata(MetadataFactory metadataFactory, WSConnection conn) throws TranslatorException { Table table = metadataFactory.addTable("habr"); metadataFactory.addColumn("login", DefaultDataTypes.STRING, table); metadataFactory.addColumn("karma", DefaultDataTypes.FLOAT, table); metadataFactory.addColumn("rating", DefaultDataTypes.FLOAT, table); metadataFactory.addColumn("ratingposition", DefaultDataTypes.LONG, table); Procedure proc = metadataFactory.addProcedure("getHabr"); metadataFactory.addProcedureParameter("username", TypeFacility.RUNTIME_NAMES.STRING, Type.In, proc); metadataFactory.addProcedureParameter("ratingposition", TypeFacility.RUNTIME_NAMES.LONG, Type.ReturnValue, proc); metadataFactory.addProcedureParameter("rating", TypeFacility.RUNTIME_NAMES.FLOAT, Type.ReturnValue, proc); metadataFactory.addProcedureParameter("karma", TypeFacility.RUNTIME_NAMES.FLOAT, Type.ReturnValue, proc); metadataFactory.addProcedureParameter("login", TypeFacility.RUNTIME_NAMES.STRING, Type.ReturnValue, proc); }
4つのフィールドを持つテーブルと、1つのパラメーターと4つの戻り値を持つプロシージャについて説明しました。 なぜなら パラメータをテーブルに渡すことはできません。テーブルは
defaultUser
データを
defaultUser
ます。これは、設定時にトランスレータに割り当てたパラメータです。 手順はすべて簡単です。ここでは、パラメーターを渡し、それに応じていくつかの値を取得できます。 かっこ内のプロシージャは、返された値の固定数だけでなく、
ResultSetExecution
ようなテーブル全体を返すことができることに注意してください:このため、パラメーターを記述するときは、
Type.Result
型を使用し、ハンドラーで
next()
メソッドも実装する必要があります
ハンドラー
プロシージャの呼び出しとテーブルへのクエリを処理するには、ハンドラを提供する必要があります。 たとえば、このように。
public ResultSetExecution createResultSetExecution(QueryExpression command, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) throws TranslatorException { return new HabrResultSetExecution((Select) command, connection); } public ProcedureExecution createProcedureExecution(Call command, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) throws TranslatorException { return new HabrProcedureExecution(command, connection); }
複数のテーブルまたはプロシージャがあり、それぞれに異なるハンドラを提供する場合、これらのメソッドでチェックを実行できます。
public ResultSetExecution createResultSetExecution(QueryExpression command, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) throws TranslatorException { String tableName = ((NamedTable) command.getProjectedQuery().getFrom().get(0)). getMetadataObject().getName(); if ("habr".equalsIgnoreCase(tableName)) return new HabrResultSetExecution((Select) command, connection); if ("hrenabr".equalsIgnoreCase(tableName)) return new HrenabrResultSetExecution((Select) command, connection); return null; } public ProcedureExecution createProcedureExecution(Call command, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) throws TranslatorException { if ("getHabr".equalsIgnoreCase(command.getProcedureName())) return new HabrProcedureExecution(command, connection); if ("getHrenabr".equalsIgnoreCase(command.getProcedureName())) return new HrenabrProcedureExecution(command, connection); return null; }
しかし、翻訳者ではこれを行いません。必要はありません。
そうそう...小さな通りの魔法
注意深い読者は、これまでに構成で指定した
importer.convertToUppercase
モデルプロパティを使用していないことに気付いているはずです。 Teiidコードはこの素晴らしい機能を使用します。私たちもそれを使用します:
[..] private boolean convertToUppercase; public boolean isConvertToUppercase() { return convertToUppercase; } public void setConvertToUppercase(boolean convertToUppercase) { this.convertToUppercase = convertToUppercase; } [..] @Override public void getMetadata(MetadataFactory metadataFactory, WSConnection conn) throws TranslatorException { [..] PropertiesUtils.setBeanProperties(this, metadataFactory.getImportProperties(), "importer"); }
コードから推測できるように、
PropertiesUtils.setBeanProperties
メソッドは、
MetadataFactory
渡され、モデルの構成から
metadataFactory.getImportProperties()
を介してアクセス可能なプロパティにプレフィックスを付け、タイプ変換と一般的なプロパティのチェックを考慮して、指定されたPOJOでそれらを埋めることができます T.O. 構成パラメーターをコードに無料で渡す便利な方法があります。
ResultSetExecution
まず、入力データを解析して出力用のテーブル行を形成する単純なメソッドを記述し、それを
HabrExecutionFactory
クラスに追加します。
また、
convertToUppercase
プロパティの値も考慮され、
true
に設定されている
true
、ログインは大文字に変換されます。
protected List<Object> extractResult(DataSource dataSource) throws TranslatorException { List<Object> results = new ArrayList<Object>(); try { DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance(); DocumentBuilder builder; builder = xmlFact.newDocumentBuilder(); Document doc = builder.parse(dataSource.getInputStream()); dataSource.getInputStream().close(); final XPath xpath = XPathFactory.newInstance().newXPath(); Node node = (Node) xpath.compile("/habrauser/login").evaluate(doc, XPathConstants.NODE); String login = node.getTextContent(); if (convertToUppercase) login = login.toUpperCase(); results.add(login); node = (Node) xpath.compile("/habrauser/karma").evaluate(doc, XPathConstants.NODE); results.add(Float.valueOf(node.getTextContent())); node = (Node) xpath.compile("/habrauser/rating").evaluate(doc, XPathConstants.NODE); results.add(Float.valueOf(node.getTextContent())); node = (Node) xpath.compile("/habrauser/ratingPosition").evaluate(doc, XPathConstants.NODE); results.add(Long.valueOf(node.getTextContent())); } catch (Exception e) { throw new TranslatorException(e); } return results; }
これで、
HabrResultSetExecution
クラスを記述できます。
public class HabrResultSetExecution implements ResultSetExecution { private final WSConnection conn; private boolean closed; private DataSource dataSource; public HabrResultSetExecution(Select query, WSConnection conn) { this.conn = conn; } @Override public void execute() throws TranslatorException { closed = false; try { Dispatch<DataSource> dispatch = conn.createDispatch(HTTPBinding.HTTP_BINDING, defaultUser, DataSource.class, Mode.MESSAGE); dispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_METHOD, "GET"); dataSource = dispatch.invoke(null); } catch (Exception e) { throw new TranslatorException(e); } } @Override public List<?> next() throws TranslatorException, DataNotAvailableException { if (closed) return null; closed = true; return extractResult(dataSource); } @Override public void close() { closed = true; } @Override public void cancel() throws TranslatorException { closed = true; } }
execute()
メソッドは、リクエストを実行するためにシステムによって呼び出されます。 実装では、彼は渡された接続を使用してサービスへのリクエストを実行し、フィールドのDataSourceインスタンスを記憶します。 その後、システムは
null
返すまで
next()
メソッドを呼び出し
null
。 最初の呼び出しで、メソッドは格納されたDataSourceを
extractResult()
メソッドに渡します。このメソッドはすべての作業を実行し、データ行を生成します:
List<?>
、それぞれがデータベース行のフィールドに対応します。 なぜなら データは1行のみで、2回目以降の呼び出しでは
null
を返し
null
。
手順実行
このクラスは同様に機能します。 違いは2つだけです。
-
execute()
メソッドは、プロシージャパラメータに渡されたユーザー名を使用してサービスを呼び出します -
next()
メソッドの代わりに、データはgetOutputParameterValues()
メソッド(呼び出しごとに1回だけ呼び出されます)を介してシステムによって選択されます。 前述のように、next()
メソッドは、プロシージャが結果セットを返すことができる場合に呼び出すことができます
public class HabrProcedureExecution implements ProcedureExecution { private final Call procedure; private final WSConnection conn; private DataSource dataSource; public HabrProcedureExecution(Call procedure, WSConnection conn) { this.procedure = procedure; this.conn = conn; } @Override public void execute() throws TranslatorException { List<Argument> arguments = this.procedure.getArguments(); String username = (String) arguments.get(0).getArgumentValue().getValue(); try { Dispatch<DataSource> dispatch = conn.createDispatch(HTTPBinding.HTTP_BINDING, username, DataSource.class, Mode.MESSAGE); dispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_METHOD, "GET"); dataSource = dispatch.invoke(null); } catch (Exception e) { throw new TranslatorException(e); } } @Override public List<?> getOutputParameterValues() throws TranslatorException { return extractResult(dataSource); } @Override public List<?> next() throws TranslatorException, DataNotAvailableException { return null; } @Override public void close() { } @Override public void cancel() throws TranslatorException { } }
更新実行
視聴者がトピックにまったく関心を示している場合は、次の記事でこのハンドラを実装できます。
おわりに
だから私たちはこの点に到達しました。 これで、次の2つのことしかできなくなりました。
- これらのリクエストを実行します:
select * from habr.habr; select w.* from (call habr.getHabr(username=>'elfuegobiz')) w;
- 言う:かっこよかった=)