Teiidの新しいデータソース、パート2:翻訳者の作成

前のパートで DDLを使用してデータソースを記述する方法について簡単に説明しました。 しかし、ソースが特定のプロトコルを使用している場合はどうでしょうか? 直接データマッピングでは不十分な場合 データの前処理またはインラインプロシージャを追加する必要がありますか? 解決策があります:翻訳者を作成します。



一般に、翻訳者は4つの部分(クラス)で構成されます。 これは、 ExecutionFactory



クラスの子孫であり、 ResultSetExecution



ProcedureExecution



およびUpdateExecution



実装です。 さらに、標準のTeiidトランスレータは、メタデータプロセッサ(メタデータプロセッサ)を別のクラスに割り当てますが、インターフェイスを実装せず、何からも継承しません。 すべてを順番に考えてみましょう。



翻訳パーツ





さらに、これらの点について前進します。



基本設定



Teiidが翻訳者を見るためには、結果のjarMETA-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



ここでこれを行うことができます:

  1. 翻訳者の名前と説明を設定します。
  2. 翻訳者のプロパティを説明します。
  3. サポートするSQLコンストラクトを説明してください。
  4. サポートされているテーブルとプロシージャのメタデータを作成します。
  5. リクエストを処理するクラスインスタンスを作成します。


行きましょう。



翻訳者の名前と説明



 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つだけです。



  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つのことしかできなくなりました。

  1. これらのリクエストを実行します:

     select * from habr.habr; select w.* from (call habr.getHabr(username=>'elfuegobiz')) w;
          
          



  2. 言う:かっこよかった=)



All Articles