mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[dbquery] Initial contribution (#8780)
* Initial commit Intial work history lost due to the repository shrunk done atc53e4aed26
(intially started from old unshrunked repo) Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Implement reconnect attempts If database can be connected at bridge initialization schedule retry attempts. Prevent query execution scheduling if bridge isn't online Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Minor documentation changes and fix trigger channel name Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Fix NPE bug initializing ThingActions Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Implement query actions and another fixes Implement actions to execute query and get last query result Correctly serialize as JSON non scalar results to result channels Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Update parameters and correct channel Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Fix formatting and forgot part on previous commit Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Improve documentation Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Add javadoc comment and license to all classes Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Code cleanup Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Untrack unused i18n file Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Fix log level for query actions trace information Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Add dbquery addon to bundles pom Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Temporary remove mqtt bindings that make travis build to fail Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Fix formatting issue Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Revert "Temporary remove mqtt bindings that make travis build to fail" This reverts commit21c09957b5
. Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Code clean up from static analysis Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Update code to be compatible with 3.1.0 Update dependencies version Update Copyright Other minor changes for new static analysis validations. Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Requested PR changes Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Update bundles/org.openhab.binding.dbquery/src/main/java/org/openhab/binding/dbquery/internal/JDBCBridgeHandler.java Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/src/main/java/org/openhab/binding/dbquery/internal/DatabaseBridgeHandler.java Co-authored-by: Matthew Skinner <matt@pcmus.com> * Apply suggestions from code review Co-authored-by: Matthew Skinner <matt@pcmus.com> * Suggestions from code review Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Update parent version Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Update bundles/org.openhab.binding.dbquery/src/main/resources/OH-INF/thing/thing-types.xml Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/src/main/resources/OH-INF/thing/thing-types.xml Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/src/main/resources/OH-INF/thing/thing-types.xml Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/src/main/resources/OH-INF/thing/jdbc-bridge.xml Co-authored-by: Matthew Skinner <matt@pcmus.com> * Update bundles/org.openhab.binding.dbquery/src/main/resources/OH-INF/thing/thing-types.xml Co-authored-by: Matthew Skinner <matt@pcmus.com> * Changes asked in PR review Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Update bundles/org.openhab.binding.dbquery/README.md Co-authored-by: Matthew Skinner <matt@pcmus.com> * README documentation imporovements Signed-off-by: Joan Pujol <joanpujol@gmail.com> * Fix format issue Signed-off-by: Joan Pujol <joanpujol@gmail.com> Co-authored-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
parent
642a2b79dc
commit
6cb5652955
@ -60,6 +60,7 @@
|
||||
/bundles/org.openhab.binding.dali/ @rs22
|
||||
/bundles/org.openhab.binding.danfossairunit/ @pravussum
|
||||
/bundles/org.openhab.binding.darksky/ @cweitkamp
|
||||
/bundles/org.openhab.binding.dbquery/ @lujop
|
||||
/bundles/org.openhab.binding.deconz/ @openhab/add-ons-maintainers
|
||||
/bundles/org.openhab.binding.denonmarantz/ @jwveldhuis
|
||||
/bundles/org.openhab.binding.digiplex/ @rmichalak
|
||||
|
@ -286,6 +286,11 @@
|
||||
<artifactId>org.openhab.binding.darksky</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.dbquery</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.deconz</artifactId>
|
||||
|
210
bundles/org.openhab.binding.dbquery/README.md
Normal file
210
bundles/org.openhab.binding.dbquery/README.md
Normal file
@ -0,0 +1,210 @@
|
||||
# DBQuery Binding
|
||||
|
||||
This binding allows creating items from the result of native database queries.
|
||||
It currently only supports InfluxDB 2.X.
|
||||
|
||||
You can use the addon in any situation where you want to create an item from a native query.
|
||||
The source of the query can be any supported database, and doesn't need to be the one you use as the persistence service in openHAB.
|
||||
Some use cases can be:
|
||||
|
||||
- Integrate a device that stores its data in a database
|
||||
- Query derived data from you openHAB persistence, for example with Influx2 tasks you can process your data to create a new one
|
||||
- Bypass limitations of current openHAB persistence queries
|
||||
|
||||
## Supported Things
|
||||
|
||||
There are two types of supported things: `influxdb2` and a `query`.
|
||||
For each different database you want to connect to, you must define a `Bridge` thing for that database.
|
||||
Then each `Bridge` can define as many `Query` things that you want to execute.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### Bridges
|
||||
|
||||
#### influxdb2
|
||||
|
||||
Defines a connection to an Influx2 database and allows creating queries on it.
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|--------------|----------|----------------------------------------- |
|
||||
| url | Yes | database url |
|
||||
| user | Yes | name of the database user |
|
||||
| token | Yes | token to authenticate to the database ([Intructions about how to create one](https://v2.docs.influxdata.com/v2.0/security/tokens/create-token/)) |
|
||||
| organization | Yes | database organization name |
|
||||
| bucket | Yes | database bucket name |
|
||||
|
||||
### query
|
||||
|
||||
The `Query` thing defines a native query that provides several channels that you can bind to items.
|
||||
|
||||
#### Query parameters
|
||||
|
||||
The query items support the following parameters:
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|--------------|----------|----------|-----------------------------------------------------------------------|
|
||||
| query | true | | Query string in native syntax |
|
||||
| interval | false | 0 | Interval in seconds in which the query is automatically executed |
|
||||
| hasParameters| false | false | True if the query has parameters, false otherwise |
|
||||
| timeout | false | 0 | Query execution timeout in seconds |
|
||||
| scalarResult | false | true | If query always returns a single value or not |
|
||||
| scalarColumn | false | | In case of multiple columns, it indicates which to use for scalarResult|
|
||||
|
||||
These are described further in the following subsections.
|
||||
|
||||
##### query
|
||||
|
||||
The query the items represents in the native language of your database:
|
||||
|
||||
- Flux for `influxdb2`
|
||||
|
||||
#### hasParameters
|
||||
|
||||
If `hasParameters=true` you can use parameters in the query string that can be dynamically set with the `setQueryParameters` action.
|
||||
|
||||
For InfluxDB use the `${paramName}` syntax for each parameter, and keep in mind that the values from that parameters must be from a trusted source as current
|
||||
parameter substitution is subject to query injection attacks.
|
||||
|
||||
#### timeout
|
||||
|
||||
A time-out in seconds to wait for the query result, if it's exceeded, the result will be discarded and the addon will do its best to cancel the query.
|
||||
Currently it's ignored and it will be implemented in a future version.
|
||||
|
||||
#### scalarResult
|
||||
|
||||
If `true` the query is expected to return a single scalar value that will be available to `result` channels as string, number, boolean,...
|
||||
If the query can return several rows and/or several columns per row then it needs to be set to `false` and the result can be retrieved in `resultString`
|
||||
channel as JSON or using the `getLastQueryResult` action.
|
||||
|
||||
#### scalarColumn
|
||||
|
||||
In case `scalarResult` is `true` and the select returns multiple columns you can use that parameter to choose which column to use to extract the result.
|
||||
|
||||
## Channels
|
||||
|
||||
Query items offer the following channels to be able to query / bind them to items:
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|-----------------|-----------|------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| execute | Switch | Send `ON` to execute the query manually. It also indicates if query is currently running (`ON`) or not running (`OFF`) |
|
||||
| resultString | String | Result of last executed query as a String |
|
||||
| resultNumber | Number | Result of last executed query as a Number, query must have `scalarResult=true` |
|
||||
| resultDateTime | DateTime | Result of last executed query as a DateTime, query must have `scalarResult=true` |
|
||||
| resultContact | Contact | Result of last executed query as Contact, query must have `scalarResult=true` |
|
||||
| resultSwitch | Switch | Result of last executed query as Switch, query must have `scalarResult=true` |
|
||||
| parameters | String | Contains parameters of last executed query as JSON|
|
||||
| correct | Switch | `ON` if the last executed query completed successfully, `OFF` if the query failed.|
|
||||
|
||||
All the channels, except `execute`, are updated when the query execution finishes, and while there is a query in execution they have the values from
|
||||
last previous executed query.
|
||||
|
||||
The `resultString` channel is the only valid one if `scalarResult=false`, and in that case it contains the query result serialized to JSON in that format:
|
||||
|
||||
{
|
||||
correct : true,
|
||||
data : [
|
||||
{
|
||||
column1 : value,
|
||||
column2 : value
|
||||
},
|
||||
{ ... }, //row2
|
||||
{ ... } //row3
|
||||
]
|
||||
}
|
||||
|
||||
### Channel Triggers
|
||||
|
||||
#### calculateParameters
|
||||
|
||||
Triggers when there's a need to calculate parameters before query execution.
|
||||
When a query has `hasParameters=true` it fires the `calculateParameters` channel trigger and pauses the execution until `setQueryParameters` action is call in
|
||||
that query.
|
||||
|
||||
In the case a query has parameters, it's expected that there is a rule that catches the `calculateParameters` trigger, calculate the parameters with the corresponding logic and then calls the `setQueryParameters` action, after that the query will be executed.
|
||||
|
||||
## Actions
|
||||
|
||||
### For DatabaseBridge
|
||||
|
||||
#### executeQuery
|
||||
|
||||
It allows executing a query synchronously from a script/rule without defining it in a Thing.
|
||||
|
||||
To execute the action you need to pass the following parameters:
|
||||
|
||||
- String query: The query to execute
|
||||
- Map<String,Object>: Query parameters (empty map if not needed)
|
||||
- int timeout: Query timeout in seconds
|
||||
|
||||
And it returns an `ActionQueryResult` that has the following properties:
|
||||
|
||||
- correct (boolean) : True if the query was executed correctly, false otherwise
|
||||
- data (List<Map<String,Object>>): A list where each element is a row that is stored in a map with (columnName,value) entries
|
||||
- isScalarResult: It returns if the result is scalar one (only one row with one column)
|
||||
- resultAsScalar: It returns the result as a scalar if possible, if not returns null
|
||||
|
||||
|
||||
Example (using Jython script):
|
||||
|
||||
from core.log import logging, LOG_PREFIX
|
||||
log = logging.getLogger("{}.action_example".format(LOG_PREFIX))
|
||||
map = {"time" : "-2h"}
|
||||
influxdb = actions.get("dbquery","dbquery:influxdb2:sampleQuery") //Get bridge thing
|
||||
result = influxdb.executeQuery("from(bucket: \"default\") |> range(start:-2h) |> filter(fn: (r) => r[\"_measurement\"] == \"go_memstats_frees_total\") |> filter(fn: (r) => r[\"_field\"] == \"counter\") |> mean()",{},5)
|
||||
log.info("execute query result is "+str(result.data))
|
||||
|
||||
|
||||
Use this action with care, because as the query is executed synchronously, it is not good to execute long-running queries that can block script execution.
|
||||
|
||||
### For Queries
|
||||
|
||||
#### setQueryParameters
|
||||
|
||||
It's used for queries with parameters to set them.
|
||||
To execute the action you need to pass the parameters as a Map.
|
||||
|
||||
Example (using Jython script):
|
||||
|
||||
params = {"time" : "-2h"}
|
||||
dbquery = actions.get("dbquery","dbquery:query:queryWithParams") //Get query thing
|
||||
dbquery.setQueryParameters(params)
|
||||
|
||||
#### getLastQueryResult
|
||||
|
||||
It can be used in scripts to get the last query result.
|
||||
It doesn't have any parameters and returns an `ActionQueryResult` as defined in `executeQuery` action.
|
||||
|
||||
Example (using Jython script):
|
||||
|
||||
dbquery = actions.get("dbquery","dbquery:query:queryWithParams") //Get query thing
|
||||
result = dbquery.getLastQueryResult()
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### The Simplest case
|
||||
|
||||
Define a InfluxDB2 database thing and a query with an interval execution.
|
||||
That executes the query every 15 seconds and punts the result in `myItem`.
|
||||
|
||||
# Bridge Thing definition
|
||||
Bridge dbquery:influxdb2:mydatabase "InfluxDB2 Bridge" [ bucket="default", user="admin", url="http://localhost:8086", organization="openhab", token="*******" ]
|
||||
|
||||
# Query Thing definition
|
||||
Thing dbquery:query:myquery "My Query" [ interval=15, hasParameters=false, scalarResult=true, timeout=0, query="from(bucket: \"default\") |> range(start:-1h) |> filter(fn: (r) => r[\"_measurement\"] == \"go_memstats_frees_total\") |> filter(fn: (r) => r[\"_field\"] == \"counter\") |> mean()", scalarColumn="_value" ]
|
||||
|
||||
# Item definition
|
||||
Number myItem "QueryResult" {channel="dbquery:query:myquery:resultNumber"}
|
||||
|
||||
### A query with parameters
|
||||
|
||||
Using the previous example you change the `range(start:-1h)` for `range(start:${time})`
|
||||
|
||||
Create a rule that is fired
|
||||
|
||||
- **When** `calculateParameters` is triggered in `myquery`
|
||||
- **Then** executes the following script action (in that example Jython):
|
||||
|
||||
map = {"time" : "-2h"}
|
||||
dbquery = actions.get("dbquery","dbquery:query:myquery")
|
||||
dbquery.setQueryParameters(map)
|
107
bundles/org.openhab.binding.dbquery/pom.xml
Normal file
107
bundles/org.openhab.binding.dbquery/pom.xml
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.dbquery</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: DBQuery Binding</name>
|
||||
|
||||
<properties>
|
||||
<bnd.importpackage>
|
||||
!javax.annotation;!android.*,!com.android.*,!com.google.appengine.*,!dalvik.system,!kotlin.*,!kotlinx.*,!org.conscrypt,!sun.security.ssl,!org.apache.harmony.*,!org.apache.http.*,!rx.*,!org.msgpack.*
|
||||
</bnd.importpackage>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- influxdb-client-java -->
|
||||
<dependency>
|
||||
<groupId>com.influxdb</groupId>
|
||||
<artifactId>influxdb-client-java</artifactId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>influxdb-client-core</artifactId>
|
||||
<groupId>com.influxdb</groupId>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>converter-gson</artifactId>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>converter-scalars</artifactId>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
<dependency> <!-- also used for querydb library -->
|
||||
<artifactId>gson</artifactId>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>gson-fire</artifactId>
|
||||
<groupId>io.gsonfire</groupId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>okio</artifactId>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<version>1.17.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<version>1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>json</artifactId>
|
||||
<groupId>org.json</groupId>
|
||||
<version>20180813</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<version>3.14.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>retrofit</artifactId>
|
||||
<groupId>com.squareup.retrofit2</groupId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<version>3.14.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<version>2.2.17</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>reactive-streams</artifactId>
|
||||
<groupId>org.reactivestreams</groupId>
|
||||
<version>1.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<groupId>io.swagger</groupId>
|
||||
<version>1.5.22</version>
|
||||
</dependency>
|
||||
<!-- end influxdb-client-java -->
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.dbquery-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-dbquery" description="DBQuery Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.dbquery/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.action;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Query result as it's exposed to users in thing actions
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionQueryResult {
|
||||
private final boolean correct;
|
||||
private List<Map<String, @Nullable Object>> data = Collections.emptyList();
|
||||
|
||||
public ActionQueryResult(boolean correct, @Nullable List<Map<String, @Nullable Object>> data) {
|
||||
this.correct = correct;
|
||||
if (data != null) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCorrect() {
|
||||
return correct;
|
||||
}
|
||||
|
||||
public List<Map<String, @Nullable Object>> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public @Nullable Object getResultAsScalar() {
|
||||
var firstResult = data.get(0);
|
||||
return isScalarResult() ? firstResult.get(firstResult.keySet().iterator().next()) : null;
|
||||
}
|
||||
|
||||
public boolean isScalarResult() {
|
||||
return data.size() == 1 && data.get(0).keySet().size() == 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.action;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.DatabaseBridgeHandler;
|
||||
import org.openhab.binding.dbquery.internal.QueryHandler;
|
||||
import org.openhab.binding.dbquery.internal.domain.ExecuteNonConfiguredQuery;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.openhab.binding.dbquery.internal.domain.ResultRow;
|
||||
import org.openhab.binding.dbquery.internal.error.UnnexpectedCondition;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "dbquery")
|
||||
@NonNullByDefault
|
||||
public class DBQueryActions implements IDBQueryActions, ThingActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(DBQueryActions.class);
|
||||
|
||||
private @Nullable QueryHandler queryHandler;
|
||||
private @Nullable DatabaseBridgeHandler databaseBridgeHandler;
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Execute query", description = "Execute query synchronously (use with care)")
|
||||
public ActionQueryResult executeQuery(String query, Map<String, @Nullable Object> parameters,
|
||||
int timeoutInSeconds) {
|
||||
logger.debug("executeQuery from action {} params={}", query, parameters);
|
||||
var currentDatabaseBridgeHandler = databaseBridgeHandler;
|
||||
if (currentDatabaseBridgeHandler != null) {
|
||||
QueryResult queryResult = new ExecuteNonConfiguredQuery(currentDatabaseBridgeHandler.getDatabase())
|
||||
.executeSynchronously(query, parameters, Duration.ofSeconds(timeoutInSeconds));
|
||||
logger.debug("executeQuery from action result {}", queryResult);
|
||||
return queryResult2ActionQueryResult(queryResult);
|
||||
} else {
|
||||
logger.warn("Execute queried ignored as databaseBridgeHandler is null");
|
||||
return new ActionQueryResult(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
private ActionQueryResult queryResult2ActionQueryResult(QueryResult queryResult) {
|
||||
return new ActionQueryResult(queryResult.isCorrect(),
|
||||
queryResult.getData().stream().map(DBQueryActions::resultRow2Map).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static Map<String, @Nullable Object> resultRow2Map(ResultRow resultRow) {
|
||||
Map<String, @Nullable Object> map = new HashMap<>();
|
||||
for (String column : resultRow.getColumnNames()) {
|
||||
map.put(column, resultRow.getValue(column));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Set query parameters", description = "Set query parameters for a query")
|
||||
public void setQueryParameters(@ActionInput(name = "parameters") Map<String, @Nullable Object> parameters) {
|
||||
logger.debug("setQueryParameters {}", parameters);
|
||||
var queryHandler = getThingHandler();
|
||||
if (queryHandler instanceof QueryHandler) {
|
||||
((QueryHandler) queryHandler).setParameters(parameters);
|
||||
} else {
|
||||
logger.warn("setQueryParameters called on wrong Thing, it must be a Query Thing");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Get last query result", description = "Get last result from a query")
|
||||
public ActionQueryResult getLastQueryResult() {
|
||||
var currentQueryHandler = queryHandler;
|
||||
if (currentQueryHandler != null) {
|
||||
return queryResult2ActionQueryResult(queryHandler.getLastQueryResult());
|
||||
} else {
|
||||
logger.warn("getLastQueryResult ignored as queryHandler is null");
|
||||
return new ActionQueryResult(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof QueryHandler) {
|
||||
this.queryHandler = ((QueryHandler) thingHandler);
|
||||
} else if (thingHandler instanceof DatabaseBridgeHandler) {
|
||||
this.databaseBridgeHandler = ((DatabaseBridgeHandler) thingHandler);
|
||||
} else {
|
||||
throw new UnnexpectedCondition("Not expected thing handler " + thingHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return queryHandler != null ? queryHandler : databaseBridgeHandler;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.action;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Defines rule actions for interacting with DBQuery addon Things.
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IDBQueryActions {
|
||||
ActionQueryResult executeQuery(String query, Map<String, @Nullable Object> parameters, int timeoutInSeconds);
|
||||
|
||||
ActionQueryResult getLastQueryResult();
|
||||
|
||||
void setQueryParameters(Map<String, @Nullable Object> parameters);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* Abstract the operation to update a channel
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ChannelStateUpdater {
|
||||
void updateChannelState(Channel channelUID, State value);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
/**
|
||||
* Abstract the action to get channels that need to be updated
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ChannelsToUpdateQueryResult {
|
||||
List<Channel> getChannels();
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* Common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DBQueryBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "dbquery";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_INFLUXDB2_BRIDGE = new ThingTypeUID(BINDING_ID, "influxdb2");
|
||||
public static final ThingTypeUID THING_TYPE_QUERY = new ThingTypeUID(BINDING_ID, "query");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_EXECUTE = "execute";
|
||||
|
||||
public static final String CHANNEL_PARAMETERS = "parameters";
|
||||
public static final String CHANNEL_CORRECT = "correct";
|
||||
public static final String TRIGGER_CHANNEL_CALCULATE_PARAMETERS = "calculateParameters";
|
||||
|
||||
public static final String RESULT_STRING_CHANNEL_TYPE = "result-channel-string";
|
||||
public static final String RESULT_NUMBER_CHANNEL_TYPE = "result-channel-number";
|
||||
public static final String RESULT_DATETIME_CHANNEL_TYPE = "result-channel-datetime";
|
||||
public static final String RESULT_CONTACT_CHANNEL_TYPE = "result-channel-contact";
|
||||
public static final String RESULT_SWITCH_CHANNEL_TYPE = "result-channel-switch";
|
||||
|
||||
private DBQueryBindingConstants() {
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import static org.openhab.binding.dbquery.internal.DBQueryBindingConstants.THING_TYPE_INFLUXDB2_BRIDGE;
|
||||
import static org.openhab.binding.dbquery.internal.DBQueryBindingConstants.THING_TYPE_QUERY;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* DBQuery binding factory that is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author Joan Pujol Espinar - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.dbquery", service = ThingHandlerFactory.class)
|
||||
public class DBQueryHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_INFLUXDB2_BRIDGE,
|
||||
THING_TYPE_QUERY);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_QUERY.equals(thingTypeUID)) {
|
||||
return new QueryHandler(thing);
|
||||
} else if (THING_TYPE_INFLUXDB2_BRIDGE.equals(thingTypeUID)) {
|
||||
return new InfluxDB2BridgeHandler((Bridge) thing);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.action.DBQueryActions;
|
||||
import org.openhab.binding.dbquery.internal.domain.Database;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base implementation common to all implementation of database bridge
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class DatabaseBridgeHandler extends BaseBridgeHandler {
|
||||
private static final long RETRY_CONNECTION_ATTEMPT_TIME_SECONDS = 60;
|
||||
private final Logger logger = LoggerFactory.getLogger(DatabaseBridgeHandler.class);
|
||||
private Database database = Database.EMPTY;
|
||||
private @Nullable ScheduledFuture<?> retryConnectionAttemptFuture;
|
||||
|
||||
public DatabaseBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
initConfig();
|
||||
|
||||
database = createDatabase();
|
||||
|
||||
connectDatabase();
|
||||
}
|
||||
|
||||
private void connectDatabase() {
|
||||
logger.debug("connectDatabase {}", database);
|
||||
var completable = database.connect();
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
completable.thenAccept(result -> {
|
||||
if (result) {
|
||||
logger.trace("Succesfully connected to database {}", getThing().getUID());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connect to database failed");
|
||||
if (retryConnectionAttemptFuture == null) {
|
||||
scheduleRetryConnectionAttempt();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void scheduleRetryConnectionAttempt() {
|
||||
logger.trace("Scheduled retry connection attempt every {}", RETRY_CONNECTION_ATTEMPT_TIME_SECONDS);
|
||||
retryConnectionAttemptFuture = scheduler.scheduleWithFixedDelay(this::connectDatabase,
|
||||
RETRY_CONNECTION_ATTEMPT_TIME_SECONDS, RETRY_CONNECTION_ATTEMPT_TIME_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected abstract void initConfig();
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelRetryConnectionAttemptIfPresent();
|
||||
disconnectDatabase();
|
||||
}
|
||||
|
||||
protected void cancelRetryConnectionAttemptIfPresent() {
|
||||
ScheduledFuture<?> currentFuture = retryConnectionAttemptFuture;
|
||||
if (currentFuture != null) {
|
||||
currentFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnectDatabase() {
|
||||
var completable = database.disconnect();
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
completable.thenAccept(result -> {
|
||||
if (result) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Successfully disconnected to database");
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Disconnect to database failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// No commands supported
|
||||
}
|
||||
|
||||
abstract Database createDatabase();
|
||||
|
||||
public Database getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return List.of(DBQueryActions.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dbquery.internal.config.InfluxDB2BridgeConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.dbimpl.influx2.Influx2Database;
|
||||
import org.openhab.binding.dbquery.internal.dbimpl.influx2.InfluxDBClientFacadeImpl;
|
||||
import org.openhab.binding.dbquery.internal.domain.Database;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
|
||||
/**
|
||||
* Concrete implementation of {@link DatabaseBridgeHandler} for Influx2
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InfluxDB2BridgeHandler extends DatabaseBridgeHandler {
|
||||
private InfluxDB2BridgeConfiguration config = new InfluxDB2BridgeConfiguration();
|
||||
|
||||
public InfluxDB2BridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
Database createDatabase() {
|
||||
return new Influx2Database(config, new InfluxDBClientFacadeImpl(config));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initConfig() {
|
||||
config = getConfig().as(InfluxDB2BridgeConfiguration.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Concrete implementation of {@link DatabaseBridgeHandler} for Influx2
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JDBCBridgeHandler extends BaseBridgeHandler {
|
||||
public JDBCBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.domain.Database;
|
||||
import org.openhab.binding.dbquery.internal.domain.Query;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryParameters;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Mantains information of a query that is currently executing
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryExecution {
|
||||
private final Logger logger = LoggerFactory.getLogger(QueryExecution.class);
|
||||
private final Database database;
|
||||
private final String queryString;
|
||||
private final QueryConfiguration queryConfiguration;
|
||||
|
||||
private QueryParameters queryParameters;
|
||||
private @Nullable QueryResultListener queryResultListener;
|
||||
|
||||
public QueryExecution(Database database, QueryConfiguration queryConfiguration,
|
||||
QueryResultListener queryResultListener) {
|
||||
this.database = database;
|
||||
this.queryString = queryConfiguration.getQuery();
|
||||
this.queryConfiguration = queryConfiguration;
|
||||
this.queryResultListener = queryResultListener;
|
||||
this.queryParameters = QueryParameters.EMPTY;
|
||||
}
|
||||
|
||||
public void setQueryParameters(QueryParameters queryParameters) {
|
||||
this.queryParameters = queryParameters;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
Query query;
|
||||
if (queryConfiguration.isHasParameters()) {
|
||||
query = database.queryFactory().createQuery(queryString, queryParameters, queryConfiguration);
|
||||
} else {
|
||||
query = database.queryFactory().createQuery(queryString, queryConfiguration);
|
||||
}
|
||||
|
||||
logger.trace("Execute query {}", query);
|
||||
database.executeQuery(query).thenAccept(this::notifyQueryResult).exceptionally(error -> {
|
||||
logger.warn("Error executing query", error);
|
||||
notifyQueryResult(QueryResult.ofIncorrectResult("Error executing query"));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyQueryResult(QueryResult queryResult) {
|
||||
var currentQueryResultListener = queryResultListener;
|
||||
if (currentQueryResultListener != null) {
|
||||
currentQueryResultListener.queryResultReceived(queryResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
queryResultListener = null;
|
||||
}
|
||||
|
||||
public QueryParameters getQueryParameters() {
|
||||
return queryParameters;
|
||||
}
|
||||
|
||||
public interface QueryResultListener {
|
||||
void queryResultReceived(QueryResult queryResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QueryExecution{" + "queryString='" + queryString + '\'' + ", queryParameters=" + queryParameters + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import static org.openhab.binding.dbquery.internal.DBQueryBindingConstants.CHANNEL_EXECUTE;
|
||||
import static org.openhab.binding.dbquery.internal.DBQueryBindingConstants.TRIGGER_CHANNEL_CALCULATE_PARAMETERS;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.action.DBQueryActions;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.domain.DBQueryJSONEncoder;
|
||||
import org.openhab.binding.dbquery.internal.domain.Database;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryParameters;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResultExtractor;
|
||||
import org.openhab.binding.dbquery.internal.domain.ResultValue;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages query thing, handling it's commands and updating it's channels
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(QueryHandler.class);
|
||||
// Relax nullable rules as config can be only null when not initialized
|
||||
private @NonNullByDefault({}) QueryConfiguration config;
|
||||
private @NonNullByDefault({}) QueryResultExtractor queryResultExtractor;
|
||||
|
||||
private @Nullable ScheduledFuture<?> scheduledQueryExecutionInterval;
|
||||
private @Nullable QueryResultChannelUpdater queryResultChannelUpdater;
|
||||
private Database database = Database.EMPTY;
|
||||
private final DBQueryJSONEncoder jsonEncoder = new DBQueryJSONEncoder();
|
||||
|
||||
private @Nullable QueryExecution currentQueryExecution;
|
||||
private QueryResult lastQueryResult = QueryResult.NO_RESULT;
|
||||
|
||||
public QueryHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(QueryConfiguration.class);
|
||||
queryResultExtractor = new QueryResultExtractor(config);
|
||||
|
||||
initQueryResultChannelUpdater();
|
||||
updateStateWithParentBridgeStatus();
|
||||
}
|
||||
|
||||
private void initQueryResultChannelUpdater() {
|
||||
ChannelStateUpdater channelStateUpdater = (channel, state) -> updateState(channel.getUID(), state);
|
||||
queryResultChannelUpdater = new QueryResultChannelUpdater(channelStateUpdater, this::getResultChannels2Update);
|
||||
}
|
||||
|
||||
private void scheduleQueryExecutionIntervalIfNeeded() {
|
||||
int interval = config.getInterval();
|
||||
if (interval != QueryConfiguration.NO_INTERVAL && scheduledQueryExecutionInterval == null) {
|
||||
logger.trace("Scheduling query execution every {} seconds for {}", interval, getQueryIdentifier());
|
||||
scheduledQueryExecutionInterval = scheduler.scheduleWithFixedDelay(this::executeQuery, 0, interval,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private ThingUID getQueryIdentifier() {
|
||||
return getThing().getUID();
|
||||
}
|
||||
|
||||
private void cancelQueryExecutionIntervalIfNeeded() {
|
||||
ScheduledFuture<?> currentFuture = scheduledQueryExecutionInterval;
|
||||
if (currentFuture != null) {
|
||||
currentFuture.cancel(true);
|
||||
scheduledQueryExecutionInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelQueryExecutionIntervalIfNeeded();
|
||||
cancelCurrentQueryExecution();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("handleCommand for channel {} with command {}", channelUID, command);
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
if (CHANNEL_EXECUTE.equals(channelUID.getId())) {
|
||||
executeQuery();
|
||||
}
|
||||
} else {
|
||||
logger.warn("Query Thing can only handle RefreshType commands as the thing is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void executeQuery() {
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
QueryExecution queryExecution = currentQueryExecution;
|
||||
if (queryExecution != null) {
|
||||
logger.debug("Previous query execution for {} discarded as a new one is requested",
|
||||
getQueryIdentifier());
|
||||
cancelCurrentQueryExecution();
|
||||
}
|
||||
|
||||
queryExecution = new QueryExecution(database, config, queryResultReceived);
|
||||
this.currentQueryExecution = queryExecution;
|
||||
|
||||
if (config.isHasParameters()) {
|
||||
logger.trace("{} triggered to set parameters for {}", TRIGGER_CHANNEL_CALCULATE_PARAMETERS,
|
||||
queryExecution);
|
||||
updateParametersChannel(QueryParameters.EMPTY);
|
||||
triggerChannel(TRIGGER_CHANNEL_CALCULATE_PARAMETERS);
|
||||
} else {
|
||||
queryExecution.execute();
|
||||
}
|
||||
} else {
|
||||
logger.debug("Execute query ignored because thing status is {}", getThing().getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void cancelCurrentQueryExecution() {
|
||||
QueryExecution current = currentQueryExecution;
|
||||
if (current != null) {
|
||||
current.cancel();
|
||||
currentQueryExecution = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStateWithQueryResult(QueryResult queryResult) {
|
||||
var currentQueryResultChannelUpdater = queryResultChannelUpdater;
|
||||
var localCurrentQueryExecution = this.currentQueryExecution;
|
||||
lastQueryResult = queryResult;
|
||||
if (currentQueryResultChannelUpdater != null && localCurrentQueryExecution != null) {
|
||||
ResultValue resultValue = queryResultExtractor.extractResult(queryResult);
|
||||
updateCorrectChannel(resultValue.isCorrect());
|
||||
updateParametersChannel(localCurrentQueryExecution.getQueryParameters());
|
||||
if (resultValue.isCorrect()) {
|
||||
currentQueryResultChannelUpdater.updateChannelResults(resultValue.getResult());
|
||||
} else {
|
||||
currentQueryResultChannelUpdater.clearChannelResults();
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
"QueryResult discarded as queryResultChannelUpdater nor currentQueryExecution are not expected to be null");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCorrectChannel(boolean correct) {
|
||||
updateState(DBQueryBindingConstants.CHANNEL_CORRECT, OnOffType.from(correct));
|
||||
}
|
||||
|
||||
private void updateParametersChannel(QueryParameters queryParameters) {
|
||||
updateState(DBQueryBindingConstants.CHANNEL_PARAMETERS, new StringType(jsonEncoder.encode(queryParameters)));
|
||||
}
|
||||
|
||||
private void updateStateWithParentBridgeStatus() {
|
||||
final @Nullable Bridge bridge = getBridge();
|
||||
DatabaseBridgeHandler databaseBridgeHandler;
|
||||
|
||||
if (bridge != null) {
|
||||
@Nullable
|
||||
BridgeHandler bridgeHandler = bridge.getHandler();
|
||||
if (bridgeHandler instanceof DatabaseBridgeHandler) {
|
||||
databaseBridgeHandler = (DatabaseBridgeHandler) bridgeHandler;
|
||||
database = databaseBridgeHandler.getDatabase();
|
||||
if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
|
||||
super.updateStatus(status, statusDetail, description);
|
||||
if (status == ThingStatus.ONLINE) {
|
||||
scheduleQueryExecutionIntervalIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
cancelCurrentQueryExecution();
|
||||
updateStateWithParentBridgeStatus();
|
||||
}
|
||||
|
||||
public void setParameters(Map<String, @Nullable Object> parameters) {
|
||||
final @Nullable QueryExecution queryExecution = currentQueryExecution;
|
||||
if (queryExecution != null) {
|
||||
QueryParameters queryParameters = new QueryParameters(parameters);
|
||||
queryExecution.setQueryParameters(queryParameters);
|
||||
queryExecution.execute();
|
||||
} else {
|
||||
logger.trace("setParameters ignored as there is any executing query for {}", getQueryIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
private final QueryExecution.QueryResultListener queryResultReceived = (QueryResult queryResult) -> {
|
||||
synchronized (QueryHandler.this) {
|
||||
logger.trace("queryResultReceived for {} : {}", getQueryIdentifier(), queryResult);
|
||||
updateStateWithQueryResult(queryResult);
|
||||
|
||||
currentQueryExecution = null;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return List.of(DBQueryActions.class);
|
||||
}
|
||||
|
||||
public QueryResult getLastQueryResult() {
|
||||
return lastQueryResult;
|
||||
}
|
||||
|
||||
private List<Channel> getResultChannels2Update() {
|
||||
return getThing().getChannels().stream().filter(channel -> isLinked(channel.getUID()))
|
||||
.filter(this::isResultChannel).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean isResultChannel(Channel channel) {
|
||||
@Nullable
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
return channelTypeUID != null && channelTypeUID.getId().startsWith("result");
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.error.UnnexpectedCondition;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* Updates a query result to needed channels doing needed conversions
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryResultChannelUpdater {
|
||||
private final ChannelStateUpdater channelStateUpdater;
|
||||
private final ChannelsToUpdateQueryResult channels2Update;
|
||||
private final Value2StateConverter value2StateConverter;
|
||||
|
||||
public QueryResultChannelUpdater(ChannelStateUpdater channelStateUpdater,
|
||||
ChannelsToUpdateQueryResult channelsToUpdate) {
|
||||
this.channelStateUpdater = channelStateUpdater;
|
||||
this.channels2Update = channelsToUpdate;
|
||||
this.value2StateConverter = new Value2StateConverter();
|
||||
}
|
||||
|
||||
public void clearChannelResults() {
|
||||
for (Channel channel : channels2Update.getChannels()) {
|
||||
channelStateUpdater.updateChannelState(channel, UnDefType.NULL);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateChannelResults(@Nullable Object extractedResult) {
|
||||
for (Channel channel : channels2Update.getChannels()) {
|
||||
Class<? extends State> targetType = calculateItemType(channel);
|
||||
State state = value2StateConverter.convertValue(extractedResult, targetType);
|
||||
channelStateUpdater.updateChannelState(channel, state);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<? extends State> calculateItemType(Channel channel) {
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
String channelID = channelTypeUID != null ? channelTypeUID.getId()
|
||||
: DBQueryBindingConstants.RESULT_STRING_CHANNEL_TYPE;
|
||||
switch (channelID) {
|
||||
case DBQueryBindingConstants.RESULT_STRING_CHANNEL_TYPE:
|
||||
return StringType.class;
|
||||
case DBQueryBindingConstants.RESULT_NUMBER_CHANNEL_TYPE:
|
||||
return DecimalType.class;
|
||||
case DBQueryBindingConstants.RESULT_DATETIME_CHANNEL_TYPE:
|
||||
return DateTimeType.class;
|
||||
case DBQueryBindingConstants.RESULT_SWITCH_CHANNEL_TYPE:
|
||||
return OnOffType.class;
|
||||
case DBQueryBindingConstants.RESULT_CONTACT_CHANNEL_TYPE:
|
||||
return OpenClosedType.class;
|
||||
default:
|
||||
throw new UnnexpectedCondition("Unexpected channel type " + channelTypeUID);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.domain.DBQueryJSONEncoder;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.openhab.binding.dbquery.internal.error.UnnexpectedCondition;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manage conversion from a value to needed State target type
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Value2StateConverter {
|
||||
private final Logger logger = LoggerFactory.getLogger(Value2StateConverter.class);
|
||||
private final DBQueryJSONEncoder jsonEncoder = new DBQueryJSONEncoder();
|
||||
|
||||
public State convertValue(@Nullable Object value, Class<? extends State> targetType) {
|
||||
if (value == null) {
|
||||
return UnDefType.NULL;
|
||||
} else {
|
||||
if (targetType == StringType.class) {
|
||||
return convert2String(value);
|
||||
} else if (targetType == DecimalType.class) {
|
||||
return convert2Decimal(value);
|
||||
} else if (targetType == DateTimeType.class) {
|
||||
return convert2DateTime(value);
|
||||
} else if (targetType == OnOffType.class) {
|
||||
@Nullable
|
||||
Boolean bool = convert2Boolean(value);
|
||||
return bool != null ? OnOffType.from(bool) : UnDefType.NULL;
|
||||
} else if (targetType == OpenClosedType.class) {
|
||||
@Nullable
|
||||
Boolean bool = convert2Boolean(value);
|
||||
if (bool != null) {
|
||||
return bool ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
} else {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
} else {
|
||||
throw new UnnexpectedCondition("Not expected targetType " + targetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private State convert2DateTime(Object value) {
|
||||
if (value instanceof Instant) {
|
||||
return new DateTimeType(ZonedDateTime.ofInstant((Instant) value, ZoneId.systemDefault()));
|
||||
} else if (value instanceof Date) {
|
||||
return new DateTimeType(ZonedDateTime.ofInstant(((Date) value).toInstant(), ZoneId.systemDefault()));
|
||||
} else if (value instanceof String) {
|
||||
return new DateTimeType((String) value);
|
||||
} else {
|
||||
logger.warn("Can't convert {} to DateTimeType", value);
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private State convert2Decimal(Object value) {
|
||||
if (value instanceof Integer) {
|
||||
return new DecimalType((Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
return new DecimalType((Long) value);
|
||||
} else if (value instanceof Float) {
|
||||
return new DecimalType((Float) value);
|
||||
} else if (value instanceof Double) {
|
||||
return new DecimalType((Double) value);
|
||||
} else if (value instanceof BigDecimal) {
|
||||
return new DecimalType((BigDecimal) value);
|
||||
} else if (value instanceof BigInteger) {
|
||||
return new DecimalType(new BigDecimal((BigInteger) value));
|
||||
} else if (value instanceof Number) {
|
||||
return new DecimalType(((Number) value).longValue());
|
||||
} else if (value instanceof String) {
|
||||
return DecimalType.valueOf((String) value);
|
||||
} else if (value instanceof Duration) {
|
||||
return new DecimalType(((Duration) value).toMillis());
|
||||
} else {
|
||||
logger.warn("Can't convert {} to DecimalType", value);
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private State convert2String(Object value) {
|
||||
if (value instanceof String) {
|
||||
return new StringType((String) value);
|
||||
} else if (value instanceof byte[]) {
|
||||
return new StringType(Base64.getEncoder().encodeToString((byte[]) value));
|
||||
} else if (value instanceof QueryResult) {
|
||||
return new StringType(jsonEncoder.encode((QueryResult) value));
|
||||
} else {
|
||||
return new StringType(String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Boolean convert2Boolean(Object value) {
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
} else if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue() != 0d;
|
||||
} else if (value instanceof String) {
|
||||
var svalue = (String) value;
|
||||
return Boolean.parseBoolean(svalue) || (svalue.equalsIgnoreCase("on")) || svalue.equals("1");
|
||||
} else {
|
||||
logger.warn("Can't convert {} to OnOffType or OpenClosedType", value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.config;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Contains fields mapping InfluxDB2 bridge configuration parameters.
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InfluxDB2BridgeConfiguration {
|
||||
private String url;
|
||||
private String user;
|
||||
private String token;
|
||||
private String bucket;
|
||||
private String organization;
|
||||
|
||||
public InfluxDB2BridgeConfiguration(String url, String user, String token, String organization, String bucket) {
|
||||
this.url = url;
|
||||
this.user = user;
|
||||
this.token = token;
|
||||
this.organization = organization;
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
public InfluxDB2BridgeConfiguration() {
|
||||
// Used only when configuration is created by reflection using ConfigMapper
|
||||
url = user = token = organization = bucket = "";
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getOrganization() {
|
||||
return organization;
|
||||
}
|
||||
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", InfluxDB2BridgeConfiguration.class.getSimpleName() + "[", "]")
|
||||
.add("url='" + url + "'").add("user='" + user + "'").add("token='" + "*".repeat(token.length()) + "'")
|
||||
.add("organization='" + organization + "'").add("bucket='" + bucket + "'").toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.config;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Contains fields mapping query things parameters.
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryConfiguration {
|
||||
public static final int NO_INTERVAL = 0;
|
||||
|
||||
private String query = "";
|
||||
private int interval;
|
||||
private int timeout;
|
||||
private boolean scalarResult;
|
||||
private boolean hasParameters;
|
||||
private @Nullable String scalarColumn = "";
|
||||
|
||||
public QueryConfiguration() {
|
||||
// Used only when configuration is created by reflection using ConfigMapper
|
||||
}
|
||||
|
||||
public QueryConfiguration(String query, int interval, int timeout, boolean scalarResult,
|
||||
@Nullable String scalarColumn, boolean hasParameters) {
|
||||
this.query = query;
|
||||
this.interval = interval;
|
||||
this.timeout = timeout;
|
||||
this.scalarResult = scalarResult;
|
||||
this.scalarColumn = scalarColumn;
|
||||
this.hasParameters = hasParameters;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public boolean isScalarResult() {
|
||||
return scalarResult;
|
||||
}
|
||||
|
||||
public @Nullable String getScalarColumn() {
|
||||
var currentScalarColumn = scalarColumn;
|
||||
return currentScalarColumn != null ? currentScalarColumn : "";
|
||||
}
|
||||
|
||||
public boolean isScalarColumnDefined() {
|
||||
return !getScalarColumn().isBlank();
|
||||
}
|
||||
|
||||
public boolean isHasParameters() {
|
||||
return hasParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", QueryConfiguration.class.getSimpleName() + "[", "]").add("query='" + query + "'")
|
||||
.add("interval=" + interval).add("timeout=" + timeout).add("scalarResult=" + scalarResult)
|
||||
.add("hasParameters=" + hasParameters).add("scalarColumn='" + scalarColumn + "'").toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryParameters;
|
||||
|
||||
/**
|
||||
* Provides a parser to substitute query parameters for database like InfluxDB that doesn't support that in it's client.
|
||||
* It's not ideal because it's subject to query injection attacks but it does the work if params are from trusted
|
||||
* sources.
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StringSubstitutionParamsParser {
|
||||
private final Pattern paramPattern = Pattern.compile("\\$\\{([\\w_]*?)}");
|
||||
private final String query;
|
||||
|
||||
public StringSubstitutionParamsParser(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public String getQueryWithParametersReplaced(QueryParameters queryParameters) {
|
||||
var matcher = paramPattern.matcher(query);
|
||||
int idx = 0;
|
||||
StringBuilder substitutedQuery = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
String nonParametersPart = query.substring(idx, matcher.start());
|
||||
String parameterName = matcher.group(1);
|
||||
substitutedQuery.append(nonParametersPart);
|
||||
substitutedQuery.append(parameterValue(parameterName, queryParameters));
|
||||
idx = matcher.end();
|
||||
}
|
||||
if (idx < query.length()) {
|
||||
substitutedQuery.append(query.substring(idx));
|
||||
}
|
||||
|
||||
return substitutedQuery.toString();
|
||||
}
|
||||
|
||||
private String parameterValue(String parameterName, QueryParameters queryParameters) {
|
||||
var parameter = queryParameters.getParameter(parameterName);
|
||||
if (parameter != null) {
|
||||
return parameter.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dbquery.internal.config.InfluxDB2BridgeConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.domain.Database;
|
||||
import org.openhab.binding.dbquery.internal.domain.Query;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryFactory;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.openhab.binding.dbquery.internal.error.DatabaseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.influxdb.query.FluxRecord;
|
||||
|
||||
/**
|
||||
* Influx2 implementation of {@link Database}
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Influx2Database implements Database {
|
||||
private final Logger logger = LoggerFactory.getLogger(Influx2Database.class);
|
||||
private final ExecutorService executors;
|
||||
private final InfluxDB2BridgeConfiguration config;
|
||||
private final InfluxDBClientFacade client;
|
||||
private final QueryFactory queryFactory;
|
||||
|
||||
public Influx2Database(InfluxDB2BridgeConfiguration config, InfluxDBClientFacade influxDBClientFacade) {
|
||||
this.config = config;
|
||||
this.client = influxDBClientFacade;
|
||||
executors = Executors.newSingleThreadScheduledExecutor();
|
||||
queryFactory = new Influx2QueryFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return client.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> connect() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
synchronized (Influx2Database.this) {
|
||||
return client.connect();
|
||||
}
|
||||
}, executors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> disconnect() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
synchronized (Influx2Database.this) {
|
||||
return client.disconnect();
|
||||
}
|
||||
}, executors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFactory queryFactory() throws DatabaseException {
|
||||
return queryFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<QueryResult> executeQuery(Query query) {
|
||||
try {
|
||||
if (query instanceof Influx2QueryFactory.Influx2Query) {
|
||||
Influx2QueryFactory.Influx2Query influxQuery = (Influx2QueryFactory.Influx2Query) query;
|
||||
|
||||
CompletableFuture<QueryResult> asyncResult = new CompletableFuture<>();
|
||||
List<FluxRecord> records = new ArrayList<>();
|
||||
client.query(influxQuery.getQuery(), (cancellable, record) -> { // onNext
|
||||
records.add(record);
|
||||
}, error -> { // onError
|
||||
logger.warn("Error executing query {}", query, error);
|
||||
asyncResult.complete(QueryResult.ofIncorrectResult("Error executing query"));
|
||||
}, () -> { // onComplete
|
||||
asyncResult.complete(new Influx2QueryResultExtractor().apply(records));
|
||||
});
|
||||
return asyncResult;
|
||||
} else {
|
||||
return CompletableFuture
|
||||
.completedFuture(QueryResult.ofIncorrectResult("Unnexpected query type " + query));
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Influx2Database{config=" + config + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.dbimpl.StringSubstitutionParamsParser;
|
||||
import org.openhab.binding.dbquery.internal.domain.Query;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryFactory;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryParameters;
|
||||
|
||||
/**
|
||||
* Influx2 implementation of {@link QueryFactory}
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Influx2QueryFactory implements QueryFactory {
|
||||
|
||||
@Override
|
||||
public Query createQuery(String query, @Nullable QueryConfiguration queryConfiguration) {
|
||||
return new Influx2Query(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createQuery(String query, QueryParameters parameters,
|
||||
@Nullable QueryConfiguration queryConfiguration) {
|
||||
return new Influx2Query(substituteParameters(query, parameters));
|
||||
}
|
||||
|
||||
private String substituteParameters(String query, QueryParameters parameters) {
|
||||
return new StringSubstitutionParamsParser(query).getQueryWithParametersReplaced(parameters);
|
||||
}
|
||||
|
||||
static class Influx2Query implements Query {
|
||||
private final String query;
|
||||
|
||||
public Influx2Query(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.openhab.binding.dbquery.internal.domain.ResultRow;
|
||||
|
||||
import com.influxdb.query.FluxRecord;
|
||||
|
||||
/**
|
||||
* Extracts results from Influx2 client query result to a {@link QueryResult}
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Influx2QueryResultExtractor implements Function<List<FluxRecord>, QueryResult> {
|
||||
|
||||
@Override
|
||||
public QueryResult apply(List<FluxRecord> records) {
|
||||
var rows = records.stream().map(Influx2QueryResultExtractor::mapRecord2Row).collect(Collectors.toList());
|
||||
return QueryResult.of(rows);
|
||||
}
|
||||
|
||||
private static ResultRow mapRecord2Row(FluxRecord record) {
|
||||
Map<String, @Nullable Object> values = record.getValues().entrySet().stream()
|
||||
.filter(entry -> !Set.of("result", "table").contains(entry.getKey()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
return new ResultRow(values);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.influxdb.Cancellable;
|
||||
import com.influxdb.query.FluxRecord;
|
||||
|
||||
/**
|
||||
* Facade to Influx2 client to facilitate testing
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface InfluxDBClientFacade {
|
||||
boolean connect();
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
boolean disconnect();
|
||||
|
||||
void query(String query, BiConsumer<Cancellable, FluxRecord> onNext, Consumer<? super Throwable> onError,
|
||||
Runnable onComplete);
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.config.InfluxDB2BridgeConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.influxdb.Cancellable;
|
||||
import com.influxdb.client.InfluxDBClient;
|
||||
import com.influxdb.client.InfluxDBClientFactory;
|
||||
import com.influxdb.client.InfluxDBClientOptions;
|
||||
import com.influxdb.client.QueryApi;
|
||||
import com.influxdb.client.domain.Ready;
|
||||
import com.influxdb.query.FluxRecord;
|
||||
|
||||
/**
|
||||
* Real implementation of {@link InfluxDBClientFacade}
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InfluxDBClientFacadeImpl implements InfluxDBClientFacade {
|
||||
private final Logger logger = LoggerFactory.getLogger(InfluxDBClientFacadeImpl.class);
|
||||
|
||||
private final InfluxDB2BridgeConfiguration config;
|
||||
|
||||
private @Nullable InfluxDBClient client;
|
||||
private @Nullable QueryApi queryAPI;
|
||||
|
||||
public InfluxDBClientFacadeImpl(InfluxDB2BridgeConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
var clientOptions = InfluxDBClientOptions.builder().url(config.getUrl()).org(config.getOrganization())
|
||||
.bucket(config.getBucket()).authenticateToken(config.getToken().toCharArray()).build();
|
||||
|
||||
final InfluxDBClient createdClient = InfluxDBClientFactory.create(clientOptions);
|
||||
this.client = createdClient;
|
||||
var currentQueryAPI = createdClient.getQueryApi();
|
||||
this.queryAPI = currentQueryAPI;
|
||||
|
||||
boolean connected = checkConnectionStatus();
|
||||
if (connected) {
|
||||
logger.debug("Successfully connected to InfluxDB. Instance ready={}", createdClient.ready());
|
||||
} else {
|
||||
logger.warn("Not able to connect to InfluxDB with config {}", config);
|
||||
}
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
private boolean checkConnectionStatus() {
|
||||
final InfluxDBClient currentClient = client;
|
||||
if (currentClient != null) {
|
||||
Ready ready = currentClient.ready();
|
||||
boolean isUp = ready != null && ready.getStatus() == Ready.StatusEnum.READY;
|
||||
if (isUp) {
|
||||
logger.debug("database status is OK");
|
||||
} else {
|
||||
logger.warn("database not ready");
|
||||
}
|
||||
return isUp;
|
||||
} else {
|
||||
logger.warn("checkConnection: database is not connected");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return checkConnectionStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnect() {
|
||||
final InfluxDBClient currentClient = client;
|
||||
if (currentClient != null) {
|
||||
currentClient.close();
|
||||
client = null;
|
||||
queryAPI = null;
|
||||
logger.debug("Succesfully disconnected from InfluxDB");
|
||||
} else {
|
||||
logger.debug("Already disconnected");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void query(String query, BiConsumer<Cancellable, FluxRecord> onNext, Consumer<? super Throwable> onError,
|
||||
Runnable onComplete) {
|
||||
var currentQueryAPI = queryAPI;
|
||||
if (currentQueryAPI != null) {
|
||||
currentQueryAPI.query(query, onNext, onError, onComplete);
|
||||
} else {
|
||||
logger.warn("Query ignored as current queryAPI is null");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
/**
|
||||
* Encodes domain objects to JSON
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DBQueryJSONEncoder {
|
||||
private final Gson gson;
|
||||
|
||||
public DBQueryJSONEncoder() {
|
||||
gson = new GsonBuilder().registerTypeAdapter(QueryResult.class, new QueryResultGSONSerializer())
|
||||
.registerTypeAdapter(ResultRow.class, new ResultRowGSONSerializer())
|
||||
.registerTypeAdapter(QueryParameters.class, new QueryParametersGSONSerializer()).create();
|
||||
}
|
||||
|
||||
public String encode(QueryResult queryResult) {
|
||||
return gson.toJson(queryResult);
|
||||
}
|
||||
|
||||
public String encode(QueryParameters parameters) {
|
||||
return gson.toJson(parameters);
|
||||
}
|
||||
|
||||
@NonNullByDefault({})
|
||||
private static class QueryResultGSONSerializer implements JsonSerializer<QueryResult> {
|
||||
@Override
|
||||
public JsonElement serialize(QueryResult src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("correct", src.isCorrect());
|
||||
if (src.getErrorMessage() != null) {
|
||||
jsonObject.addProperty("errorMessage", src.getErrorMessage());
|
||||
}
|
||||
jsonObject.add("data", context.serialize(src.getData()));
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResultRowGSONSerializer implements JsonSerializer<ResultRow> {
|
||||
@Override
|
||||
public JsonElement serialize(ResultRow src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
for (String columnName : src.getColumnNames()) {
|
||||
jsonObject.add(columnName, convertValueToJsonPrimitive(src.getValue(columnName)));
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueryParametersGSONSerializer implements JsonSerializer<QueryParameters> {
|
||||
@Override
|
||||
public JsonElement serialize(QueryParameters src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
for (Map.Entry<String, @Nullable Object> param : src.getAll().entrySet()) {
|
||||
jsonObject.add(param.getKey(), convertValueToJsonPrimitive(param.getValue()));
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonElement convertValueToJsonPrimitive(@Nullable Object value) {
|
||||
if (value instanceof Number) {
|
||||
return new JsonPrimitive((Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
return new JsonPrimitive((Boolean) value);
|
||||
} else if (value instanceof Character) {
|
||||
return new JsonPrimitive((Character) value);
|
||||
} else if (value instanceof Date) {
|
||||
return new JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(((Date) value).toInstant()));
|
||||
} else if (value instanceof Instant) {
|
||||
return new JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format((Instant) value));
|
||||
} else if (value != null) {
|
||||
return new JsonPrimitive(value.toString());
|
||||
} else {
|
||||
return JsonNull.INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dbquery.internal.error.DatabaseException;
|
||||
|
||||
/**
|
||||
* Abstracts database operations needed for query execution
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Database {
|
||||
boolean isConnected();
|
||||
|
||||
CompletableFuture<Boolean> connect();
|
||||
|
||||
CompletableFuture<Boolean> disconnect();
|
||||
|
||||
QueryFactory queryFactory() throws DatabaseException;
|
||||
|
||||
CompletableFuture<QueryResult> executeQuery(Query query);
|
||||
|
||||
Database EMPTY = new Database() {
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> connect() {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> disconnect() {
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFactory queryFactory() {
|
||||
return QueryFactory.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<QueryResult> executeQuery(Query query) {
|
||||
return CompletableFuture.completedFuture(QueryResult.ofIncorrectResult("Empty database"));
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Executes a non defined query in given database
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ExecuteNonConfiguredQuery {
|
||||
private final Logger logger = LoggerFactory.getLogger(ExecuteNonConfiguredQuery.class);
|
||||
private final Database database;
|
||||
|
||||
public ExecuteNonConfiguredQuery(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public CompletableFuture<QueryResult> execute(String queryString, Map<String, @Nullable Object> parameters,
|
||||
Duration timeout) {
|
||||
if (!database.isConnected()) {
|
||||
return CompletableFuture.completedFuture(QueryResult.ofIncorrectResult("Database not connected"));
|
||||
}
|
||||
|
||||
Query query = database.queryFactory().createQuery(queryString, new QueryParameters(parameters),
|
||||
createConfiguration(queryString, timeout));
|
||||
return database.executeQuery(query);
|
||||
}
|
||||
|
||||
public QueryResult executeSynchronously(String queryString, Map<String, @Nullable Object> parameters,
|
||||
Duration timeout) {
|
||||
var completableFuture = execute(queryString, parameters, timeout);
|
||||
try {
|
||||
if (timeout.isZero()) {
|
||||
return completableFuture.get();
|
||||
} else {
|
||||
return completableFuture.get(timeout.getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Query was interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return QueryResult.ofIncorrectResult("Query execution was interrupted");
|
||||
} catch (ExecutionException e) {
|
||||
logger.warn("Error executing query", e);
|
||||
return QueryResult.ofIncorrectResult("Error executing query " + e.getMessage());
|
||||
} catch (TimeoutException e) {
|
||||
logger.debug("Timeout executing query", e);
|
||||
return QueryResult.ofIncorrectResult("Timeout");
|
||||
}
|
||||
}
|
||||
|
||||
private QueryConfiguration createConfiguration(String query, Duration timeout) {
|
||||
return new QueryConfiguration(query, QueryConfiguration.NO_INTERVAL, (int) timeout.getSeconds(), false, null,
|
||||
true);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Marker interface for queries
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Query {
|
||||
Query EMPTY = new Query() {
|
||||
};
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
|
||||
/**
|
||||
* Abstracts operations needed to create a query from its thing configuration
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface QueryFactory {
|
||||
Query createQuery(String query, @Nullable QueryConfiguration queryConfiguration);
|
||||
|
||||
Query createQuery(String query, QueryParameters parameters, @Nullable QueryConfiguration queryConfiguration);
|
||||
|
||||
QueryFactory EMPTY = new QueryFactory() {
|
||||
@Override
|
||||
public Query createQuery(String query, @Nullable QueryConfiguration queryConfiguration) {
|
||||
return Query.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query createQuery(String query, QueryParameters parameters,
|
||||
@Nullable QueryConfiguration queryConfiguration) {
|
||||
return Query.EMPTY;
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Query parameters
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryParameters {
|
||||
public static final QueryParameters EMPTY = new QueryParameters(Collections.emptyMap());
|
||||
private final Map<String, @Nullable Object> params;
|
||||
|
||||
private QueryParameters() {
|
||||
this.params = new HashMap<>();
|
||||
}
|
||||
|
||||
public QueryParameters(Map<String, @Nullable Object> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public void setParameter(String name, @Nullable Object value) {
|
||||
params.put(name, value);
|
||||
}
|
||||
|
||||
public @Nullable Object getParameter(String paramName) {
|
||||
return params.get(paramName);
|
||||
}
|
||||
|
||||
public Map<String, @Nullable Object> getAll() {
|
||||
return Collections.unmodifiableMap(params);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return params.size();
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Query result
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryResult {
|
||||
public static final QueryResult NO_RESULT = QueryResult.ofIncorrectResult("No result");
|
||||
|
||||
private final boolean correct;
|
||||
private final @Nullable String errorMessage;
|
||||
private final List<ResultRow> data;
|
||||
|
||||
private QueryResult(boolean correct, String errorMessage) {
|
||||
this.correct = correct;
|
||||
this.errorMessage = errorMessage;
|
||||
this.data = Collections.emptyList();
|
||||
}
|
||||
|
||||
private QueryResult(List<ResultRow> data) {
|
||||
this.correct = true;
|
||||
this.errorMessage = null;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static QueryResult ofIncorrectResult(String errorMessage) {
|
||||
return new QueryResult(false, errorMessage);
|
||||
}
|
||||
|
||||
public static QueryResult of(ResultRow... rows) {
|
||||
return new QueryResult(List.of(rows));
|
||||
}
|
||||
|
||||
public static QueryResult of(List<ResultRow> rows) {
|
||||
return new QueryResult(rows);
|
||||
}
|
||||
|
||||
public static QueryResult ofSingleValue(String columnName, Object value) {
|
||||
return new QueryResult(List.of(new ResultRow(columnName, value)));
|
||||
}
|
||||
|
||||
public boolean isCorrect() {
|
||||
return correct;
|
||||
}
|
||||
|
||||
public @Nullable String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public List<ResultRow> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", QueryResult.class.getSimpleName() + "[", "]").add("correct=" + correct)
|
||||
.add("errorMessage='" + errorMessage + "'").add("data=" + data).toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Extracts a result from {@link QueryResult} to a single value to be used in channels
|
||||
* (after being converted that it's not responsability of this class)
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class QueryResultExtractor {
|
||||
private final Logger logger = LoggerFactory.getLogger(QueryResultExtractor.class);
|
||||
private final QueryConfiguration config;
|
||||
|
||||
public QueryResultExtractor(QueryConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public ResultValue extractResult(QueryResult queryResult) {
|
||||
if (queryResult.isCorrect()) {
|
||||
if (config.isScalarResult()) {
|
||||
return getScalarValue(queryResult);
|
||||
} else {
|
||||
return ResultValue.of(queryResult);
|
||||
}
|
||||
} else {
|
||||
return ResultValue.incorrect();
|
||||
}
|
||||
}
|
||||
|
||||
private ResultValue getScalarValue(QueryResult queryResult) {
|
||||
if (validateHasScalarValue(queryResult)) {
|
||||
var row = queryResult.getData().get(0);
|
||||
@Nullable
|
||||
Object value;
|
||||
if (config.isScalarColumnDefined()) {
|
||||
value = row.getValue(Objects.requireNonNull(config.getScalarColumn()));
|
||||
} else {
|
||||
value = row.getValue(row.getColumnNames().iterator().next());
|
||||
}
|
||||
return ResultValue.of(value);
|
||||
} else {
|
||||
return ResultValue.incorrect();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validateHasScalarValue(QueryResult queryResult) {
|
||||
boolean valid = false;
|
||||
String baseErrorMessage = "Can't get scalar value for result: ";
|
||||
if (queryResult.isCorrect()) {
|
||||
if (queryResult.getData().size() == 1) {
|
||||
boolean oneColumn = queryResult.getData().get(0).getColumnsSize() == 1;
|
||||
if (oneColumn || config.isScalarColumnDefined()) {
|
||||
valid = true;
|
||||
} else {
|
||||
logger.warn("{} Columns size is {} and scalarColumn isn't defined", baseErrorMessage,
|
||||
queryResult.getData().get(0).getColumnNames().size());
|
||||
}
|
||||
} else {
|
||||
logger.warn("{} Rows size is {}", baseErrorMessage, queryResult.getData().size());
|
||||
}
|
||||
} else {
|
||||
logger.debug("{} Incorrect result", baseErrorMessage);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Query result row
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ResultRow {
|
||||
private final Logger logger = LoggerFactory.getLogger(ResultRow.class);
|
||||
|
||||
private final LinkedHashMap<String, @Nullable Object> values;
|
||||
|
||||
public ResultRow(String columnName, @Nullable Object value) {
|
||||
this.values = new LinkedHashMap<>();
|
||||
put(columnName, value);
|
||||
}
|
||||
|
||||
public ResultRow(Map<String, @Nullable Object> values) {
|
||||
this.values = new LinkedHashMap<>();
|
||||
values.forEach(this::put);
|
||||
}
|
||||
|
||||
public Set<String> getColumnNames() {
|
||||
return values.keySet();
|
||||
}
|
||||
|
||||
public int getColumnsSize() {
|
||||
return values.size();
|
||||
}
|
||||
|
||||
public @Nullable Object getValue(String column) {
|
||||
return values.get(column);
|
||||
}
|
||||
|
||||
public static boolean isValidResultRowType(@Nullable Object object) {
|
||||
return object == null || object instanceof String || object instanceof Boolean || object instanceof Number
|
||||
|| object instanceof byte[] || object instanceof Instant || object instanceof Date
|
||||
|| object instanceof Duration;
|
||||
}
|
||||
|
||||
private void put(String columnName, @Nullable Object value) {
|
||||
if (!isValidResultRowType(value)) {
|
||||
logger.trace("Value {} of type {} converted to String as not supported internal type in dbquery", value,
|
||||
value.getClass());
|
||||
value = value.toString();
|
||||
}
|
||||
values.put(columnName, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return values.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A query result value as is extracted by {@link QueryResultExtractor} from a {@link QueryResult}
|
||||
* to be set in channels
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ResultValue {
|
||||
private final boolean correct;
|
||||
private final @Nullable Object result;
|
||||
|
||||
private ResultValue(boolean correct, @Nullable Object result) {
|
||||
this.correct = correct;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public static ResultValue of(@Nullable Object result) {
|
||||
return new ResultValue(true, result);
|
||||
}
|
||||
|
||||
public static ResultValue incorrect() {
|
||||
return new ResultValue(false, null);
|
||||
}
|
||||
|
||||
public boolean isCorrect() {
|
||||
return correct;
|
||||
}
|
||||
|
||||
public @Nullable Object getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.error;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception from a database operation
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DatabaseException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 5181127643040903150L;
|
||||
|
||||
public DatabaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DatabaseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.error;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* An unexpected error, aka bug
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UnnexpectedCondition extends RuntimeException {
|
||||
private static final long serialVersionUID = -7785815761302340174L;
|
||||
|
||||
public UnnexpectedCondition(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="dbquery" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>DBQuery Binding</name>
|
||||
<description>This is the binding for DBQuery that allows to execute native database queries and bind their results to
|
||||
items.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dbquery"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
<bridge-type id="influxdb2">
|
||||
<label>InfluxDB2 Bridge</label>
|
||||
<description>The InfluxDB 2.0 represents a connection to a InfluxDB 2.0 server</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="url" type="text" required="true">
|
||||
<context>url</context>
|
||||
<label>Url</label>
|
||||
<description>Database url</description>
|
||||
<default>http://localhost:9999</default>
|
||||
</parameter>
|
||||
<parameter name="user" type="text" required="true">
|
||||
<label>Username</label>
|
||||
<description>Name of the database user</description>
|
||||
</parameter>
|
||||
<parameter name="token" type="text" required="true">
|
||||
<label>Token</label>
|
||||
<context>password</context>
|
||||
<description>Token to authenticate to the database</description>
|
||||
</parameter>
|
||||
<parameter name="organization" type="text" required="true">
|
||||
<label>Organization</label>
|
||||
<description>Name of the database organization </description>
|
||||
</parameter>
|
||||
<parameter name="bucket" type="text" required="true">
|
||||
<label>Bucket</label>
|
||||
<description>Name of the database bucket </description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="dbquery"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="query">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="influxdb2"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Query Thing</label>
|
||||
<description>Thing that represents a native query</description>
|
||||
|
||||
<channels>
|
||||
<channel id="execute" typeId="execute-channel"/>
|
||||
<channel id="resultString" typeId="result-channel-string"/>
|
||||
<channel id="resultNumber" typeId="result-channel-number"/>
|
||||
<channel id="resultDateTime" typeId="result-channel-datetime"/>
|
||||
<channel id="resultContact" typeId="result-channel-contact"/>
|
||||
<channel id="resultSwitch" typeId="result-channel-switch"/>
|
||||
|
||||
<channel id="parameters" typeId="parameters-channel"/>
|
||||
<channel id="correct" typeId="correct-channel"/>
|
||||
<channel id="calculateParameters" typeId="calculate-parameters-channel"/>
|
||||
</channels>
|
||||
|
||||
|
||||
|
||||
<config-description>
|
||||
<parameter name="query" type="text" required="true">
|
||||
<label>Query Definition</label>
|
||||
<description>Query definition using native query language</description>
|
||||
<context>script</context>
|
||||
</parameter>
|
||||
<parameter name="hasParameters" type="boolean">
|
||||
<label>Query has Parameters</label>
|
||||
<description>True if the query has parameters, otherwise false</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="scalarResult" type="boolean">
|
||||
<label>Scalar Result</label>
|
||||
<description>True if the query always return only one single scalar value (only one row and one value-column in this
|
||||
row), otherwise false</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="scalarColumn" type="text" required="false">
|
||||
<label>Scalar Column Name</label>
|
||||
<description>The column's name that is used to extract scalarResult. If only one column is returned this
|
||||
parameter
|
||||
can be blank</description>
|
||||
</parameter>
|
||||
<parameter name="interval" type="integer" min="0">
|
||||
<label>Interval</label>
|
||||
<description>
|
||||
An interval, in seconds, the query will be repeatedly executed. Default values is 0, which means that
|
||||
query is never executed automatically. You need to send the ON command each time you wish to execute.
|
||||
</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" min="0">
|
||||
<label>Timeout Query</label>
|
||||
<description>
|
||||
A time-out in seconds to wait for the query result, if it's exceeded result will be discarded.
|
||||
Use 0 for
|
||||
no timeout
|
||||
</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
|
||||
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="execute-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Execute Query</label>
|
||||
<description>Send ON to execute the query, the current state tells if the query is running</description>
|
||||
</channel-type>
|
||||
<channel-type id="result-channel-string">
|
||||
<item-type>String</item-type>
|
||||
<label>String Result</label>
|
||||
<description>Execute query and binds result value to channel as a String</description>
|
||||
</channel-type>
|
||||
<channel-type id="result-channel-number">
|
||||
<item-type>Number</item-type>
|
||||
<label>Number Result</label>
|
||||
<description>Execute query and binds result value to channel as a Number</description>
|
||||
</channel-type>
|
||||
<channel-type id="result-channel-datetime">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>DateTime Result</label>
|
||||
<description>Execute query and binds result value to channel as a DateTime</description>
|
||||
</channel-type>
|
||||
<channel-type id="result-channel-contact">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Contact Result</label>
|
||||
<description>Execute query and binds result value to channel as a Contact</description>
|
||||
</channel-type>
|
||||
<channel-type id="result-channel-switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch Result</label>
|
||||
<description>Execute query and binds result value to channel as a Switch</description>
|
||||
</channel-type>
|
||||
<channel-type id="parameters-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>JSON Result</label>
|
||||
</channel-type>
|
||||
<channel-type id="correct-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Last Query Worked</label>
|
||||
<description>True if last query executed correctly</description>
|
||||
</channel-type>
|
||||
<channel-type id="calculate-parameters-channel">
|
||||
<kind>trigger</kind>
|
||||
<label>Calculate Parameters</label>
|
||||
<description>Event to calculate query parameters</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="START">START</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.dbquery.internal.config.QueryConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResult;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryResultExtractor;
|
||||
import org.openhab.binding.dbquery.internal.domain.ResultRow;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class Influx2QueryResultExtractorTest {
|
||||
public static final QueryResult ONE_ROW_ONE_COLUMN_RESULT = QueryResult.ofSingleValue("AnyValueName", "value");
|
||||
public static final QueryResult SEVERAL_ROWS_COLUMNS_RESULT = QueryResult.of(
|
||||
new ResultRow(Map.of("valueName", "value1", "column2", "value2")),
|
||||
new ResultRow(Map.of("valueName", "value1", "column2", "value2")));
|
||||
public static final QueryResult ONE_ROW_SEVERAL_COLUMNS_RESULT = QueryResult
|
||||
.of(new ResultRow(Map.of("valueName", "value1", "column2", "value2")));
|
||||
public static final QueryResult INCORRECT_RESULT = QueryResult.ofIncorrectResult("Incorrect result");
|
||||
|
||||
private static final QueryConfiguration SCALAR_VALUE_CONFIG = new QueryConfiguration("query", 10, 10, true, null,
|
||||
false);
|
||||
private static final QueryConfiguration NON_SCALAR_VALUE_CONFIG = new QueryConfiguration("query", 10, 10, false,
|
||||
null, false);
|
||||
private static final QueryConfiguration SCALAR_VALUE_CONFIG_WITH_SCALAR_COLUMN = new QueryConfiguration("query", 10,
|
||||
10, true, "valueName", false);
|
||||
|
||||
@Test
|
||||
void givenAResultWithOneRowAndOneColumnAndScalarConfigurationScalarValueIsReturned() {
|
||||
var extracted = new QueryResultExtractor(SCALAR_VALUE_CONFIG).extractResult(ONE_ROW_ONE_COLUMN_RESULT);
|
||||
|
||||
assertThat(extracted.isCorrect(), is(Boolean.TRUE));
|
||||
assertThat(extracted.getResult(), is("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAResultWithSeveralRowsAndScalarConfigurationIncorrectValueIsReturned() {
|
||||
var extracted = new QueryResultExtractor(SCALAR_VALUE_CONFIG).extractResult(SEVERAL_ROWS_COLUMNS_RESULT);
|
||||
|
||||
assertThat(extracted.isCorrect(), is(false));
|
||||
assertThat(extracted.getResult(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAResultWithSeveralColumnsAndScalarConfigurationIncorrectValueIsReturned() {
|
||||
var extracted = new QueryResultExtractor(SCALAR_VALUE_CONFIG).extractResult(ONE_ROW_SEVERAL_COLUMNS_RESULT);
|
||||
|
||||
assertThat(extracted.isCorrect(), is(false));
|
||||
assertThat(extracted.getResult(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAResultWithSeveralColumnsAndScalarConfigurationAndScalarColumnDefinedValueIsReturned() {
|
||||
var extracted = new QueryResultExtractor(SCALAR_VALUE_CONFIG_WITH_SCALAR_COLUMN)
|
||||
.extractResult(ONE_ROW_SEVERAL_COLUMNS_RESULT);
|
||||
|
||||
assertThat(extracted.isCorrect(), is(true));
|
||||
assertThat(extracted.getResult(), is("value1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAResultWithSeveralRowsAndNonScalarConfigQueryResultIsReturned() {
|
||||
var extracted = new QueryResultExtractor(NON_SCALAR_VALUE_CONFIG).extractResult(SEVERAL_ROWS_COLUMNS_RESULT);
|
||||
|
||||
assertThat(extracted.isCorrect(), is(true));
|
||||
assertThat(extracted.getResult(), is(SEVERAL_ROWS_COLUMNS_RESULT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAIncorrectResultIncorrectValueIsReturned() {
|
||||
var extracted = new QueryResultExtractor(NON_SCALAR_VALUE_CONFIG).extractResult(INCORRECT_RESULT);
|
||||
|
||||
assertThat(extracted.isCorrect(), is(false));
|
||||
assertThat(extracted.getResult(), nullValue());
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.anyOf;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.number.IsCloseTo.closeTo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault({})
|
||||
class Value2StateConverterTest {
|
||||
public static final BigDecimal BIG_DECIMAL_NUMBER = new BigDecimal("212321213123123123123123");
|
||||
private Value2StateConverter instance;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
instance = new Value2StateConverter();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = { StringType.class, DecimalType.class, DateTimeType.class, OpenClosedType.class,
|
||||
OnOffType.class })
|
||||
void givenNullValueReturnUndef(Class<State> classe) {
|
||||
assertThat(instance.convertValue(null, classe), is(UnDefType.NULL));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "", "stringValue" })
|
||||
void givenStringValueAndStringTargetReturnStringtype(String value) {
|
||||
var converted = instance.convertValue(value, StringType.class);
|
||||
assertThat(converted.toFullString(), is(value));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideValuesOfAllSupportedResultRowTypesExceptBytes")
|
||||
void givenValidObjectTypesAndStringTargetReturnStringtypeWithString(Object value) {
|
||||
var converted = instance.convertValue(value, StringType.class);
|
||||
assertThat(converted.toFullString(), is(value.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenByteArrayAndStringTargetReturnEncodedBase64() {
|
||||
var someBytes = "Hello world".getBytes(Charset.defaultCharset());
|
||||
var someBytesB64 = Base64.getEncoder().encodeToString(someBytes);
|
||||
var converted = instance.convertValue(someBytes, StringType.class);
|
||||
assertThat(converted.toFullString(), is(someBytesB64));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideNumericTypes")
|
||||
void givenNumericTypeAndDecimalTargetReturnDecimaltype(Number value) {
|
||||
var converted = instance.convertValue(value, DecimalType.class);
|
||||
assertThat(converted, instanceOf(DecimalType.class));
|
||||
assertThat(((DecimalType) converted).doubleValue(), closeTo(value.doubleValue(), 0.01d));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideNumericTypes")
|
||||
void givenNumericStringAndDecimalTargetReturnDecimaltype(Number value) {
|
||||
var numberString = value.toString();
|
||||
var converted = instance.convertValue(numberString, DecimalType.class);
|
||||
assertThat(converted, instanceOf(DecimalType.class));
|
||||
assertThat(((DecimalType) converted).doubleValue(), closeTo(value.doubleValue(), 0.01d));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenDurationAndDecimalTargetReturnDecimaltypeWithMilliseconds() {
|
||||
var duration = Duration.ofDays(1);
|
||||
var converted = instance.convertValue(duration, DecimalType.class);
|
||||
assertThat(converted, instanceOf(DecimalType.class));
|
||||
assertThat(((DecimalType) converted).longValue(), is(duration.toMillis()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenInstantAndDatetimeTargetReturnDatetype() {
|
||||
var instant = Instant.now();
|
||||
var converted = instance.convertValue(instant, DateTimeType.class);
|
||||
assertThat(converted, instanceOf(DateTimeType.class));
|
||||
assertThat(((DateTimeType) converted).getZonedDateTime(),
|
||||
is(ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()).withFixedOffsetZone()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenDateAndDatetimeTargetReturnDatetype() {
|
||||
var date = new Date();
|
||||
var converted = instance.convertValue(date, DateTimeType.class);
|
||||
assertThat(converted, instanceOf(DateTimeType.class));
|
||||
assertThat(((DateTimeType) converted).getZonedDateTime(),
|
||||
is(ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).withFixedOffsetZone()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "2019-10-12T07:20:50.52Z", "2019-10-12" })
|
||||
void givenValidStringDateAndDatetimeTargetReturnDatetype(String date) {
|
||||
var converted = instance.convertValue(date, DateTimeType.class);
|
||||
assertThat(converted, instanceOf(DateTimeType.class));
|
||||
var convertedDateTime = ((DateTimeType) converted).getZonedDateTime();
|
||||
assertThat(convertedDateTime.getYear(), is(2019));
|
||||
assertThat(convertedDateTime.getMonthValue(), is(10));
|
||||
assertThat(convertedDateTime.getDayOfMonth(), is(12));
|
||||
assertThat(convertedDateTime.getHour(), anyOf(is(7), is(0)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("trueValues")
|
||||
void givenValuesConsideratedTrueAndOnOffTargetReturnOn(Object value) {
|
||||
var converted = instance.convertValue(value, OnOffType.class);
|
||||
assertThat(converted, instanceOf(OnOffType.class));
|
||||
assertThat(converted, is(OnOffType.ON));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("falseValues")
|
||||
void givenValuesConsideratedFalseAndOnOffTargetReturnOff(Object value) {
|
||||
var converted = instance.convertValue(value, OnOffType.class);
|
||||
assertThat(converted, instanceOf(OnOffType.class));
|
||||
assertThat(converted, is(OnOffType.OFF));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("trueValues")
|
||||
void givenValuesConsideratedTrueAndOpenClosedTargetReturnOpen(Object value) {
|
||||
var converted = instance.convertValue(value, OpenClosedType.class);
|
||||
assertThat(converted, instanceOf(OpenClosedType.class));
|
||||
assertThat(converted, is(OpenClosedType.OPEN));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("falseValues")
|
||||
void givenValuesConsideratedFalseAndOpenClosedTargetReturnClosed(Object value) {
|
||||
var converted = instance.convertValue(value, OpenClosedType.class);
|
||||
assertThat(converted, instanceOf(OpenClosedType.class));
|
||||
assertThat(converted, is(OpenClosedType.CLOSED));
|
||||
}
|
||||
|
||||
private static Stream<Object> trueValues() {
|
||||
return Stream.of("true", "True", 1, 2, "On", "on", -1, 0.3);
|
||||
}
|
||||
|
||||
private static Stream<Object> falseValues() {
|
||||
return Stream.of("false", "False", 0, 0.0d, "off", "Off", "", "a value");
|
||||
}
|
||||
|
||||
private static Stream<Number> provideNumericTypes() {
|
||||
return Stream.of(1L, 1.2, 1.2f, -1, 0, BIG_DECIMAL_NUMBER);
|
||||
}
|
||||
|
||||
private static Stream<Object> provideValuesOfAllSupportedResultRowTypes() {
|
||||
return Stream.of("", "String", Boolean.TRUE, 1L, 1.2, 1.2f, BIG_DECIMAL_NUMBER,
|
||||
"bytes".getBytes(Charset.defaultCharset()), Instant.now(), new Date(), Duration.ofDays(1));
|
||||
}
|
||||
|
||||
private static Stream<Object> provideValuesOfAllSupportedResultRowTypesExceptBytes() {
|
||||
return provideValuesOfAllSupportedResultRowTypes().filter(o -> !(o instanceof byte[]));
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryParameters;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StringSubstitutionParamsParserTest {
|
||||
|
||||
@Test
|
||||
public void testMultipleParameters() {
|
||||
String query = "from(bucket:\\\"my-bucket\\\") |> range(start: ${start}) |> fill( value: ${fillValue})";
|
||||
var parser = new StringSubstitutionParamsParser(query);
|
||||
QueryParameters parameters = new QueryParameters(Map.of("start", "0", "fillValue", "1"));
|
||||
|
||||
var result = parser.getQueryWithParametersReplaced(parameters);
|
||||
|
||||
assertThat(result, equalTo("from(bucket:\\\"my-bucket\\\") |> range(start: 0) |> fill( value: 1)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatedParameter() {
|
||||
String query = "from(bucket:\\\"my-bucket\\\") |> range(start: ${start}) |> limit(n:${start})";
|
||||
var parser = new StringSubstitutionParamsParser(query);
|
||||
QueryParameters parameters = new QueryParameters(Map.of("start", "0"));
|
||||
|
||||
var result = parser.getQueryWithParametersReplaced(parameters);
|
||||
|
||||
assertThat(result, equalTo("from(bucket:\\\"my-bucket\\\") |> range(start: 0) |> limit(n:0)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullAndNotDefinedParametersAreSubstitutedByEmptyString() {
|
||||
String query = "from(bucket:\\\"my-bucket\\\") |> range(start: ${start}) |> limit(n:${start})";
|
||||
var parser = new StringSubstitutionParamsParser(query);
|
||||
var paramMap = new HashMap<String, @Nullable Object>();
|
||||
paramMap.put("start", null);
|
||||
QueryParameters parameters = new QueryParameters(paramMap);
|
||||
|
||||
var result = parser.getQueryWithParametersReplaced(parameters);
|
||||
|
||||
assertThat(result, equalTo("from(bucket:\\\"my-bucket\\\") |> range(start: ) |> limit(n:)"));
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
import org.eclipse.jdt.annotation.DefaultLocation;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.dbquery.internal.config.InfluxDB2BridgeConfiguration;
|
||||
import org.openhab.binding.dbquery.internal.domain.Query;
|
||||
import org.openhab.binding.dbquery.internal.domain.QueryParameters;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault(value = { DefaultLocation.PARAMETER })
|
||||
class Influx2DatabaseTest {
|
||||
private Influx2Database instance;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
instance = new Influx2Database(new InfluxDB2BridgeConfiguration(), new InfluxDBClientFacadeMock());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void clearDown() {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenQueryThatReturnsScalarResultGetValidScalarResult() throws Exception {
|
||||
instance.connect().get();
|
||||
Query query = instance.queryFactory().createQuery(InfluxDBClientFacadeMock.SCALAR_QUERY, QueryParameters.EMPTY,
|
||||
null);
|
||||
var future = instance.executeQuery(query);
|
||||
var queryResult = future.get();
|
||||
|
||||
assertThat(queryResult, notNullValue());
|
||||
assertThat(queryResult.isCorrect(), is(true));
|
||||
assertThat(queryResult.getData(), hasSize(1));
|
||||
assertThat(queryResult.getData().get(0).getColumnsSize(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenQueryThatReturnsMultipleRowsGetValidQueryResult() throws Exception {
|
||||
instance.connect().get();
|
||||
Query query = instance.queryFactory().createQuery(InfluxDBClientFacadeMock.MULTIPLE_ROWS_QUERY,
|
||||
QueryParameters.EMPTY, null);
|
||||
var future = instance.executeQuery(query);
|
||||
var queryResult = future.get();
|
||||
|
||||
assertThat(queryResult, notNullValue());
|
||||
assertThat(queryResult.isCorrect(), is(true));
|
||||
assertThat(queryResult.getData(), hasSize(InfluxDBClientFacadeMock.MULTIPLE_ROWS_SIZE));
|
||||
assertThat("contains expected result data", queryResult.getData().stream().allMatch(row -> {
|
||||
var value = (String) row.getValue(InfluxDBClientFacadeMock.VALUE_COLUMN);
|
||||
var time = row.getValue(InfluxDBClientFacadeMock.TIME_COLUMN);
|
||||
return value != null && value.contains(InfluxDBClientFacadeMock.MULTIPLE_ROWS_VALUE_PREFIX) && time != null;
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenQueryThatReturnsErrorGetErroneusResult() throws Exception {
|
||||
instance.connect().get();
|
||||
Query query = instance.queryFactory().createQuery(InfluxDBClientFacadeMock.INVALID_QUERY, QueryParameters.EMPTY,
|
||||
null);
|
||||
var future = instance.executeQuery(query);
|
||||
var queryResult = future.get();
|
||||
|
||||
assertThat(queryResult, notNullValue());
|
||||
assertThat(queryResult.isCorrect(), equalTo(false));
|
||||
assertThat(queryResult.getData(), is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenQueryThatReturnsNoRowsGetEmptyResult() throws Exception {
|
||||
instance.connect().get();
|
||||
Query query = instance.queryFactory().createQuery(InfluxDBClientFacadeMock.EMPTY_QUERY, QueryParameters.EMPTY,
|
||||
null);
|
||||
var future = instance.executeQuery(query);
|
||||
var queryResult = future.get();
|
||||
|
||||
assertThat(queryResult, notNullValue());
|
||||
assertThat(queryResult.isCorrect(), equalTo(true));
|
||||
assertThat(queryResult.getData(), is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNotConnectedClientShouldGetIncorrectQuery() {
|
||||
Query query = instance.queryFactory().createQuery(InfluxDBClientFacadeMock.SCALAR_QUERY, QueryParameters.EMPTY,
|
||||
null);
|
||||
var future = instance.executeQuery(query);
|
||||
assertThat(future.isCompletedExceptionally(), is(Boolean.TRUE));
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.dbimpl.influx2;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.dbquery.internal.error.DatabaseException;
|
||||
|
||||
import com.influxdb.Cancellable;
|
||||
import com.influxdb.query.FluxRecord;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InfluxDBClientFacadeMock implements InfluxDBClientFacade {
|
||||
public static final String INVALID_QUERY = "invalid";
|
||||
public static final String EMPTY_QUERY = "empty";
|
||||
public static final String SCALAR_QUERY = "scalar";
|
||||
public static final String MULTIPLE_ROWS_QUERY = "multiple";
|
||||
|
||||
public static final String SCALAR_RESULT = "scalarResult";
|
||||
public static final int MULTIPLE_ROWS_SIZE = 3;
|
||||
public static final String VALUE_COLUMN = "_value";
|
||||
public static final String TIME_COLUMN = "_time";
|
||||
public static final String MULTIPLE_ROWS_VALUE_PREFIX = "value";
|
||||
|
||||
boolean connected;
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
connected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disconnect() {
|
||||
connected = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void query(String queryString, BiConsumer<Cancellable, FluxRecord> onNext,
|
||||
Consumer<? super Throwable> onError, Runnable onComplete) {
|
||||
if (!connected) {
|
||||
throw new DatabaseException("Client not connected");
|
||||
}
|
||||
|
||||
if (INVALID_QUERY.equals(queryString)) {
|
||||
onError.accept(new RuntimeException("Invalid query"));
|
||||
} else if (EMPTY_QUERY.equals(queryString)) {
|
||||
onComplete.run();
|
||||
} else if (SCALAR_QUERY.equals(queryString)) {
|
||||
FluxRecord scalar = new FluxRecord(0);
|
||||
scalar.getValues().put("result", "_result");
|
||||
scalar.getValues().put("table", 0);
|
||||
scalar.getValues().put(VALUE_COLUMN, SCALAR_RESULT);
|
||||
onNext.accept(mock(Cancellable.class), scalar);
|
||||
onComplete.run();
|
||||
} else if (MULTIPLE_ROWS_QUERY.equals(queryString)) {
|
||||
onNext.accept(mock(Cancellable.class), createRowRecord(0, MULTIPLE_ROWS_VALUE_PREFIX + 1));
|
||||
onNext.accept(mock(Cancellable.class), createRowRecord(0, MULTIPLE_ROWS_VALUE_PREFIX + 2));
|
||||
onNext.accept(mock(Cancellable.class), createRowRecord(1, MULTIPLE_ROWS_VALUE_PREFIX + 3));
|
||||
onComplete.run();
|
||||
}
|
||||
}
|
||||
|
||||
private static FluxRecord createRowRecord(int table, String value) {
|
||||
FluxRecord record = new FluxRecord(0);
|
||||
record.getValues().put("result", "_result");
|
||||
record.getValues().put("table", table);
|
||||
record.getValues().put(VALUE_COLUMN, value);
|
||||
record.getValues().put(TIME_COLUMN, Instant.now());
|
||||
record.getValues().put("_start", Instant.now());
|
||||
record.getValues().put("_stop", Instant.now());
|
||||
record.getValues().put("_measurement", "measurementName");
|
||||
return record;
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.dbquery.internal.domain;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Joan Pujol - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class QueryResultJSONEncoderTest {
|
||||
public static final double TOLERANCE = 0.001d;
|
||||
private final DBQueryJSONEncoder instance = new DBQueryJSONEncoder();
|
||||
private final Gson gson = new Gson();
|
||||
private final JsonParser jsonParser = new JsonParser();
|
||||
|
||||
@Test
|
||||
void givenQueryResultIsSerializedToJson() {
|
||||
String json = instance.encode(givenQueryResultWithResults());
|
||||
|
||||
assertThat(jsonParser.parse(json), notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
void givenQueryResultItsContentIsCorrectlySerializedToJson() {
|
||||
String json = instance.encode(givenQueryResultWithResults());
|
||||
|
||||
Map<String, Object> map = gson.fromJson(json, Map.class);
|
||||
assertThat(map, Matchers.hasEntry("correct", Boolean.TRUE));
|
||||
assertThat(map, Matchers.hasKey("data"));
|
||||
List<Map> data = (List<Map>) map.get("data");
|
||||
assertThat(data, Matchers.hasSize(2));
|
||||
Map firstRow = data.get(0);
|
||||
|
||||
assertReadGivenValuesDecodedFromJson(firstRow);
|
||||
}
|
||||
|
||||
private void assertReadGivenValuesDecodedFromJson(Map<?, ?> firstRow) {
|
||||
assertThat(firstRow.get("strValue"), is("an string"));
|
||||
|
||||
Object doubleValue = firstRow.get("doubleValue");
|
||||
assertThat(doubleValue, instanceOf(Number.class));
|
||||
assertThat(((Number) doubleValue).doubleValue(), closeTo(2.3d, TOLERANCE));
|
||||
|
||||
Object intValue = firstRow.get("intValue");
|
||||
assertThat(intValue, instanceOf(Number.class));
|
||||
assertThat(((Number) intValue).intValue(), is(3));
|
||||
|
||||
Object longValue = firstRow.get("longValue");
|
||||
assertThat(longValue, instanceOf(Number.class));
|
||||
assertThat(((Number) longValue).longValue(), is(Long.MAX_VALUE));
|
||||
|
||||
Object date = Objects.requireNonNull(firstRow.get("date"));
|
||||
assertThat(date, instanceOf(String.class));
|
||||
var parsedDate = Instant.from(DateTimeFormatter.ISO_INSTANT.parse((String) date));
|
||||
assertThat(Duration.between(parsedDate, Instant.now()).getSeconds(), lessThan(10L));
|
||||
|
||||
Object instant = Objects.requireNonNull(firstRow.get("instant"));
|
||||
assertThat(instant, instanceOf(String.class));
|
||||
var parsedInstant = Instant.from(DateTimeFormatter.ISO_INSTANT.parse((String) instant));
|
||||
assertThat(Duration.between(parsedInstant, Instant.now()).getSeconds(), lessThan(10L));
|
||||
|
||||
assertThat(firstRow.get("booleanValue"), is(Boolean.TRUE));
|
||||
assertThat(firstRow.get("object"), is("an object"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
void givenQueryResultWithIncorrectResultItsContentIsCorrectlySerializedToJson() {
|
||||
String json = instance.encode(QueryResult.ofIncorrectResult("Incorrect"));
|
||||
|
||||
Map<String, Object> map = gson.fromJson(json, Map.class);
|
||||
assertThat(map, Matchers.hasEntry("correct", Boolean.FALSE));
|
||||
assertThat(map.get("errorMessage"), is("Incorrect"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenQueryParametersAreCorrectlySerializedToJson() {
|
||||
QueryParameters queryParameters = new QueryParameters(givenRowValues());
|
||||
|
||||
String json = instance.encode(queryParameters);
|
||||
|
||||
Map<?, ?> map = Objects.requireNonNull(gson.fromJson(json, Map.class));
|
||||
assertReadGivenValuesDecodedFromJson(map);
|
||||
}
|
||||
|
||||
private QueryResult givenQueryResultWithResults() {
|
||||
return QueryResult.of(new ResultRow(givenRowValues()), new ResultRow(givenRowValues()));
|
||||
}
|
||||
|
||||
private Map<String, @Nullable Object> givenRowValues() {
|
||||
Map<String, @Nullable Object> values = new HashMap<>();
|
||||
values.put("strValue", "an string");
|
||||
values.put("doubleValue", 2.3d);
|
||||
values.put("intValue", 3);
|
||||
values.put("longValue", Long.MAX_VALUE);
|
||||
values.put("date", new Date());
|
||||
values.put("instant", Instant.now());
|
||||
values.put("booleanValue", Boolean.TRUE);
|
||||
values.put("object", new Object() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "an object";
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}
|
||||
}
|
@ -92,6 +92,7 @@
|
||||
<module>org.openhab.binding.dali</module>
|
||||
<module>org.openhab.binding.danfossairunit</module>
|
||||
<module>org.openhab.binding.darksky</module>
|
||||
<module>org.openhab.binding.dbquery</module>
|
||||
<module>org.openhab.binding.deconz</module>
|
||||
<module>org.openhab.binding.denonmarantz</module>
|
||||
<module>org.openhab.binding.digiplex</module>
|
||||
|
Loading…
Reference in New Issue
Block a user