ExecutionFactory
class and implementations of the ResultSetExecution
, ProcedureExecution
and UpdateExecution
; In addition, Teiid's standard translators, for convenience, distinguish the metadata processor in a separate class, although it does not implement any interface and is not inherited from anything. Consider everything in order. ExecutionFactory
is the starting point of the translator, the place where its name and description are specified, its properties (properties), SQL constructs that it supports are described, metadata are generated (that is, descriptions of tables and procedures that the translator implements are created), and if necessary, instances of the above interfaces are created, directly performing the processing of SELECT, INSERT / UPDATE / DELETE queries and calls to embedded procedures.ResultSetExecution
is an interface whose implementations are used to process SELECT queries. Implementations may not be available in the translator.ProcedureExecution
- an interface whose implementations are used to handle calls to embedded procedures. Implementations may not be available in the translator.UpdateExecution
is an interface whose implementations are used to process INSERT / UPDATE / DELETE requests. Implementations may not be available in the translator. ru.habrahabr.HabrExecutionFactory
<subsystem xmlns="urn:jboss:domain:teiid:1.0"> [..] <translator name="habrahabr" module="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> [...]
<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>
<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>
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
annotation marks the class as a translator class, the name
parameter specifies the name under which it will be visible in the system, and description
- an arbitrary text description. We set the parameters for inheritance based on the fact that we use the standard WS connector Teiid.defaultUser
property value for the translator. Now we implement it in the translator code. 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
annotation provides a short and detailed description of the property. If this property is required, you can add the parameter required=true
to the annotation.ExecutionFactory
class has many methods with a supports*
mask that return a boolean
value indicating whether the translator supports this feature or not. Features that the translator does not support, Teiid implements by its own means. For example, if the translator does not know how to handle the function aggregate sum()
, then Teiid will request data from the translator, and then it will calculate the sum itself.count(*)
queries and the limit <>
constructions, then we will block the corresponding methods and return true
to them: @Override public boolean supportsAggregatesCountStar() { return true; } @Override public boolean supportsRowLimit() { return true; }
ExecutionFactory
class has a special getMetadata(MetadataFactory metadataFactory, WSConnection conn)
method getMetadata(MetadataFactory metadataFactory, WSConnection conn)
that needs to be overridden and implemented. We receive a copy of the MetadataFactory
class, which has methods for creating tables, procedures, and much more, as well as an object of an already established connection to our web service: if we need to request data about, for example, the number and names of tables from our source. In our case, we know in advance what we want to implement: @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); }
defaultUser
parameter, which we have assigned to the translator during configuration. Everything is simpler with the procedure, here we can both pass a parameter and get several values in response. I note in parentheses that procedures can return not only a fixed number of returned values, but also a whole table, like ResultSetExecution
: for this, when describing parameters, you need to use Type.Result
type, and also implement next()
method in the handler. 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
model, which we have registered in the configuration. The Teiid code uses this nice feature, and we use it: [..] 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
method can prefix the properties that are passed from the model configuration to the MetadataFactory
and are available via the metadataFactory.getImportProperties()
, and fill the specified POJO with them, taking into account type conversion and checking for the presence of properties in general. So we get a convenient way to transfer configuration parameters to the code.HabrExecutionFactory
class.convertToUppercase
and, if it is set to true
, the login is converted to uppercase letters. 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()
method is called by the system to execute the request. In our implementation, it performs a request to the service, using the connection passed to it, and stores an instance of the DataSource in the field. After that, the system calls the next()
method until it returns null
. In the first call, the method passes the saved DataSource to the extractResult()
method, which does all the work and also generates a data row: List<?>
, Each of whose elements corresponds to a field in the DB row. Because we have only one line of data; on the second and subsequent calls we return null
.execute()
method uses the user name passed in the parameter procedure to call the service.next()
method, the system selects data through the getOutputParameterValues()
method (which is called only once per call); The next()
method, as I mentioned earlier, can be called if the procedure can return a result set 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 { } }
select * from habr.habr; select w.* from (call habr.getHabr(username=>'elfuegobiz')) w;
Source: https://habr.com/ru/post/150878/
All Articles