From ca36780a6e8301adf0d49f439d58ace673fe0aee Mon Sep 17 00:00:00 2001 From: "Jan N. Klug" Date: Sat, 23 Mar 2024 18:16:51 +0100 Subject: [PATCH] [http] Remove binding all changes have been merged in openhab-addons Signed-off-by: Jan N. Klug --- bom/smarthomej-addons/pom.xml | 5 - bundles/org.smarthomej.binding.http/NOTICE | 15 - bundles/org.smarthomej.binding.http/README.md | 190 -------- bundles/org.smarthomej.binding.http/pom.xml | 90 ---- .../src/main/feature/feature.xml | 8 - .../http/internal/HttpBindingConstants.java | 36 -- .../http/internal/HttpClientProvider.java | 40 -- .../http/internal/HttpHandlerFactory.java | 110 ----- .../http/internal/HttpThingHandler.java | 427 ----------------- .../binding/http/internal/Util.java | 68 --- .../http/internal/config/HttpAuthMode.java | 29 -- .../internal/config/HttpChannelConfig.java | 33 -- .../http/internal/config/HttpThingConfig.java | 77 ---- .../http/internal/http/HttpAuthException.java | 34 -- .../internal/http/HttpResponseListener.java | 111 ----- .../internal/http/HttpStatusListener.java | 36 -- .../internal/http/RateLimitedHttpClient.java | 226 --------- .../internal/http/RefreshingUrlCache.java | 163 ------- .../src/main/resources/OH-INF/addon/addon.xml | 10 - .../main/resources/OH-INF/config/config.xml | 431 ------------------ .../resources/OH-INF/thing/thing-types.xml | 209 --------- .../resources/OH-INF/update/instructions.xml | 19 - .../binding/http/AbstractWireMockTest.java | 70 --- .../http/RateLimitedHttpClientTest.java | 146 ------ .../binding/http/RefreshingUrlCacheTest.java | 261 ----------- .../org/smarthomej/binding/http/UtilTest.java | 60 --- .../xtend_examples.md | 63 --- bundles/pom.xml | 1 - 28 files changed, 2968 deletions(-) delete mode 100644 bundles/org.smarthomej.binding.http/NOTICE delete mode 100644 bundles/org.smarthomej.binding.http/README.md delete mode 100644 bundles/org.smarthomej.binding.http/pom.xml delete mode 100644 bundles/org.smarthomej.binding.http/src/main/feature/feature.xml delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpBindingConstants.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpClientProvider.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpHandlerFactory.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpThingHandler.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/Util.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpAuthMode.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpChannelConfig.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpThingConfig.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpAuthException.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpResponseListener.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpStatusListener.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RateLimitedHttpClient.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RefreshingUrlCache.java delete mode 100644 bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/addon/addon.xml delete mode 100644 bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/config/config.xml delete mode 100644 bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/thing/thing-types.xml delete mode 100644 bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/update/instructions.xml delete mode 100644 bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/AbstractWireMockTest.java delete mode 100644 bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RateLimitedHttpClientTest.java delete mode 100644 bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RefreshingUrlCacheTest.java delete mode 100644 bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/UtilTest.java delete mode 100644 bundles/org.smarthomej.binding.http/xtend_examples.md diff --git a/bom/smarthomej-addons/pom.xml b/bom/smarthomej-addons/pom.xml index e837e64c32..505650bdbd 100644 --- a/bom/smarthomej-addons/pom.xml +++ b/bom/smarthomej-addons/pom.xml @@ -31,11 +31,6 @@ org.smarthomej.binding.amazonechocontrol ${project.version} - - org.smarthomej.addons.bundles - org.smarthomej.binding.http - ${project.version} - org.smarthomej.addons.bundles org.smarthomej.binding.notificationsforfiretv diff --git a/bundles/org.smarthomej.binding.http/NOTICE b/bundles/org.smarthomej.binding.http/NOTICE deleted file mode 100644 index 68e64ecfc4..0000000000 --- a/bundles/org.smarthomej.binding.http/NOTICE +++ /dev/null @@ -1,15 +0,0 @@ -This content is produced and maintained by the SmartHome/J project. - -* Project home: https://www.smarthomej.org - -== Declared Project Licenses - -This program and the accompanying materials are made available under the terms -of the Eclipse Public License 2.0 which is available at -https://www.eclipse.org/legal/epl-2.0/. - -== Source Code - -https://github.com/smarthomej/addons - -Parts of this code have been forked from https://github.com/openhab/openhab-addons \ No newline at end of file diff --git a/bundles/org.smarthomej.binding.http/README.md b/bundles/org.smarthomej.binding.http/README.md deleted file mode 100644 index f701e0a7a0..0000000000 --- a/bundles/org.smarthomej.binding.http/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# HTTP Binding - -This binding allows using HTTP to bring external data into openHAB or execute HTTP requests on commands. - -## Supported Things - -Only one thing named `url` is available. -It can be extended with different channels. - -## Thing Configuration - -| parameter | optional | default | description | -|-------------------|----------|---------|-------------| -| `baseURL` | no | - | The base URL (including protocol `http://` or `https://`) for this thing. Can be extended in channel-configuration. | -| `refresh` | no | 30 | Time in seconds between two refresh calls for the channels of this thing. | -| `timeout` | no | 3000 | Timeout for HTTP requests in ms. | -| `bufferSize` | no | 2048 | The buffer size for the response data (in kB). | -| `delay` | no | 0 | Delay between two requests in ms (advanced parameter). | -| `username` | yes | - | Username for authentication (advanced parameter). | -| `password` | yes | - | Password for authentication (advanced parameter). Also used for the authentication token when using `TOKEN` authentication. | -| `authMode` | no | BASIC | Authentication mode, `BASIC`, `BASIC_PREEMPTIVE`, `TOKEN` or `DIGEST` (advanced parameter). | -| `stateMethod` | no | GET | Method used for requesting the state: `GET`, `PUT`, `POST`. | -| `commandMethod` | no | GET | Method used for sending commands: `GET`, `PUT`, `POST`. | -| `contentType` | yes | - | MIME content-type of the command requests. Only used for `PUT` and `POST`. | -| `encoding` | yes | - | Encoding to be used if no encoding is found in responses (advanced parameter). | -| `headers` | yes | - | Additional headers that are sent along with the request. Format is "header=value". Multiple values can be stored as `headers="key1=value1", "key2=value2", "key3=value3",`| -| `ignoreSSLErrors` | no | false | If set to true, ignores invalid SSL certificate errors. This is potentially dangerous.| -| `strictErrorHandling` | no | false | If set to true, thing status is changed depending on last request result (failed = `OFFLINE`). Failed requests result in `UNDEF` for channel values. | -| `userAgent` | yes | (yes ) | Sets a custom user agent (default is "Jetty/version", e.g. "Jetty/9.4.20.v20190813"). | - -*Note:* Optional "no" means that you have to configure a value unless a default is provided, and you are ok with that setting. - -*Note:* The `BASIC_PREEMPTIVE` mode adds basic authentication headers even if the server did not request authentication. -This is dangerous and might be misused. -The option exists to be able to authenticate when the server is not sending the proper 401/Unauthorized code. -Authentication might fail if redirections are involved as headers are stripper prior to redirection. - -*Note:* If you rate-limit requests by using the `delay` parameter you have to make sure that the time between two refreshes is larger than the time needed for one refresh cycle. - -**Attention:** `baseUrl` (and `stateExtension`/`commandExtension`) should not use escaping (e.g. `%22` instead of `"` or `%2c` instead of `,`). -URLs are properly escaped by the binding itself before the request is sent. -Using escaped strings in URL parameters may lead to problems with the formatting (see below). - -## Channels - -The thing has two channels of type `requestDateTime` which provide the timestamp of the last successful (`lastSuccess`) and last failed (`lastFailure`) request. - -Additionally, the thing can be extended with data channels. -Each item type has its own channel-type. -Depending on the channel-type, channels have different configuration options. -All channel-types (except `image`) have `stateExtension`, `commandExtension`, `stateTransformation`, `commandTransformation` and `mode` parameters. -The `image` channel-type supports `stateExtension` only. - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `stateExtension` | yes | - | Appended to the `baseURL` for requesting states. | -| `commandExtension` | yes | - | Appended to the `baseURL` for sending commands. If empty, same as `stateExtension`. | -| `stateTransformation ` | yes | - | One or more transformation applied to received values before updating channel. | -| `commandTransformation` | yes | - | One or more transformation applied to channel value before sending to a remote. | -| `stateContent` | yes | - | Content for state requests (if method is `PUT` or `POST`) | -| `mode` | no | `READWRITE` | Mode this channel is allowed to operate. `READONLY` means receive state, `WRITEONLY` means send commands. | - -Transformations need to be specified in the same format as -Some channels have additional parameters. -When concatenating the `baseURL` and `stateExtension` or `commandExtension` the binding checks if a proper URL part separator (`/`, `&` or `?`) is present and adds a `/` if missing. - -### Value Transformations (`stateTransformation`, `commandTransformation`) - -Transformations can be used if the supplied value (or the required value) is different from what openHAB internal types require. -Here are a few examples to unwrap an incoming value via `stateTransformation` from a complex response: - -| Received value | Tr. Service | Transformation | -|---------------------------------------------------------------------|-------------|-------------------------------------------| -| `{device: {status: { temperature: 23.2 }}}` | JSONPATH | `JSONPATH:$.device.status.temperature` | -| `23.2` | XPath | `XPath:/device/status/temperature/text()` | -| `THEVALUE:23.2°C` | REGEX | `REGEX::(.*?)°` | - -Transformations can be chained by separating them with the mathematical intersection character "∩". -Please note that the values will be discarded if one transformation fails (e.g. REGEX did not match). - -The same mechanism works for commands (`commandTransformation`) for outgoing values. - -### `color` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `onValue` | yes | - | A special value that represents `ON` | -| `offValue` | yes | - | A special value that represents `OFF` | -| `increaseValue` | yes | - | A special value that represents `INCREASE` | -| `decreaseValue` | yes | - | A special value that represents `DECREASE` | -| `step` | no | 1 | The amount the brightness is increased/decreased on `INCREASE`/`DECREASE` | -| `colorMode` | no | RGB | Mode for color values: `RGB` or `HSB` | - -All values that are not `onValue`, `offValue`, `increaseValue`, `decreaseValue` are interpreted as color value (according to the color mode) in the format `r,g,b` or `h,s,v`. - -### `contact` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `openValue` | no | - | A special value that represents `OPEN` | -| `closedValue` | no | - | A special value that represents `CLOSED` | - -### `dimmer` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `onValue` | yes | - | A special value that represents `ON` | -| `offValue` | yes | - | A special value that represents `OFF` | -| `increaseValue` | yes | - | A special value that represents `INCREASE` | -| `decreaseValue` | yes | - | A special value that represents `DECREASE` | -| `step` | no | 1 | The amount the brightness is increased/decreased on `INCREASE`/`DECREASE` | - -All values that are not `onValue`, `offValue`, `increaseValue`, `decreaseValue` are interpreted as brightness 0-100% and need to be numeric only. - -### `number` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `unit` | yes | - | The unit label for this channel | - -`number` channels can be used for `DecimalType` or `QuantityType` values. -If a unit is given in the `unit` parameter, the binding tries to create a `QuantityType` state before updating the channel, if no unit is present, it creates a `DecimalType`. -Please note that incompatible units (e.g. `°C` for a `Number:Density` item) will fail silently, i.e. no error message is logged even if the state update fails. - -### `player` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `playValue` | yes | - | A special value that represents `PLAY` | -| `pauseValue` | yes | - | A special value that represents `PAUSE` | -| `nextValue` | yes | - | A special value that represents `NEXT` | -| `previousValue` | yes | - | A special value that represents `PREVIOUS` | -| `fastforwardValue` | yes | - | A special value that represents `FASTFORWARD` | -| `rewindValue` | yes | - | A special value that represents `REWIND` | - -### `rollershutter` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `upValue` | yes | - | A special value that represents `UP` | -| `downValue` | yes | - | A special value that represents `DOWN` | -| `stopValue` | yes | - | A special value that represents `STOP` | -| `moveValue` | yes | - | A special value that represents `MOVE` | - -All values that are not `upValue`, `downValue`, `stopValue`, `moveValue` are interpreted as position 0-100% and need to be numeric only. - -### `switch` - -| parameter | optional | default | description | -|-------------------------|----------|-------------|-------------| -| `onValue` | no | - | A special value that represents `ON` | -| `offValue` | no | - | A special value that represents `OFF` | - -**Note:** Special values need to be exact matches, i.e. no leading or trailing characters and comparison is case-sensitive. - -## URL Formatting - -After concatenation of the `baseURL` and the `commandExtension` or the `stateExtension` (if provided) the URL is formatted using the [java.util.Formatter](http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html). -The URL is used as format string and two parameters are added: - -- the current date (referenced as `%1$`) -- the transformed command (referenced as `%2$`) - -After the parameter reference the format needs to be appended. -See the link above for more information about the available format parameters (e.g. to use the string representation, you need to append `s` to the reference, for a timestamp `t`). -When sending an OFF command on 2020-07-06, the URL - -``` -http://www.domain.org/home/lights/23871/?status=%2$s&date=%1$tY-%1$tm-%1$td -``` - -is transformed to - -``` -http://www.domain.org/home/lights/23871/?status=OFF&date=2020-07-06 -``` - -## Examples - -### `demo.things` - -``` -Thing http:url:foo "Foo" [ - baseURL="https://example.com/api/v1/metadata-api/web/metadata", - headers="key1=value1", "key2=value2", "key3=value3", - refresh=15] { - Channels: - Type string : text "Text" [ stateTransformation="JSONPATH:$.metadata.data" ] -} -``` diff --git a/bundles/org.smarthomej.binding.http/pom.xml b/bundles/org.smarthomej.binding.http/pom.xml deleted file mode 100644 index bae7a262cd..0000000000 --- a/bundles/org.smarthomej.binding.http/pom.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - 4.0.0 - - - org.smarthomej.addons.bundles - org.smarthomej.addons.reactor.bundles - 4.2.0-SNAPSHOT - - - org.smarthomej.binding.http - - SmartHome/J Add-ons :: Bundles :: HTTP Binding - - - 9.4.53.v20231009 - - - - - org.smarthomej.addons.bundles - org.smarthomej.commons - ${project.version} - provided - - - - - com.github.tomakehurst - wiremock - 2.27.2 - test - - - org.eclipse.jetty - jetty-server - - - org.eclipse.jetty - jetty-servlet - - - org.eclipse.jetty - jetty-servlets - - - org.eclipse.jetty - jetty-proxy - - - org.eclipse.jetty - jetty-webapp - - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - test - - - org.eclipse.jetty - jetty-servlet - ${jetty.version} - test - - - org.eclipse.jetty - jetty-servlets - ${jetty.version} - test - - - org.eclipse.jetty - jetty-proxy - ${jetty.version} - test - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - test - - - - diff --git a/bundles/org.smarthomej.binding.http/src/main/feature/feature.xml b/bundles/org.smarthomej.binding.http/src/main/feature/feature.xml deleted file mode 100644 index d12b25636c..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/feature/feature.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - openhab-runtime-base - mvn:org.smarthomej.addons.bundles/org.smarthomej.commons/${project.version} - mvn:org.smarthomej.addons.bundles/org.smarthomej.binding.http/${project.version} - - diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpBindingConstants.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpBindingConstants.java deleted file mode 100644 index 2722beb321..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpBindingConstants.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.type.ChannelTypeUID; - -/** - * The {@link HttpBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class HttpBindingConstants { - private static final String BINDING_ID = "http"; - - public static final ThingTypeUID THING_TYPE_URL = new ThingTypeUID(BINDING_ID, "url"); - - public static final ChannelTypeUID REQUEST_DATE_TIME_CHANNELTYPE_UID = new ChannelTypeUID(BINDING_ID, - "requestDateTime"); - public static final String CHANNEL_LAST_SUCCESS = "lastSuccess"; - public static final String CHANNEL_LAST_FAILURE = "lastFailure"; -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpClientProvider.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpClientProvider.java deleted file mode 100644 index 8725e94e70..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpClientProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jetty.client.HttpClient; - -/** - * The {@link HttpClientProvider} defines the interface for providing {@link HttpClient} instances to thing handlers - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public interface HttpClientProvider { - - /** - * get the secure http client - * - * @return a HttpClient - */ - HttpClient getSecureClient(); - - /** - * get the insecure http client (ignores SSL errors) - * - * @return q HttpClient - */ - HttpClient getInsecureClient(); -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpHandlerFactory.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpHandlerFactory.java deleted file mode 100644 index 502721dda0..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpHandlerFactory.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal; - -import static org.smarthomej.binding.http.internal.HttpBindingConstants.THING_TYPE_URL; - -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.openhab.core.io.net.http.HttpClientFactory; -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.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.commons.SimpleDynamicStateDescriptionProvider; - -/** - * The {@link HttpHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -@Component(configurationPid = "binding.http", service = ThingHandlerFactory.class) -public class HttpHandlerFactory extends BaseThingHandlerFactory implements HttpClientProvider { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_URL); - private final Logger logger = LoggerFactory.getLogger(HttpHandlerFactory.class); - - private final HttpClient secureClient; - private final HttpClient insecureClient; - - private final SimpleDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider; - - @Activate - public HttpHandlerFactory(@Reference HttpClientFactory httpClientFactory, - @Reference SimpleDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider) { - this.secureClient = new HttpClient(new SslContextFactory.Client()); - this.insecureClient = new HttpClient(new SslContextFactory.Client(true)); - // clear user agent, this needs to be set later in the thing configuration as additional header - this.secureClient.setUserAgentField(null); - this.insecureClient.setUserAgentField(null); - try { - this.secureClient.start(); - this.insecureClient.start(); - } catch (Exception e) { - // catching exception is necessary due to the signature of HttpClient.start() - logger.warn("Failed to start http client: {}", e.getMessage()); - throw new IllegalStateException("Could not create HttpClient", e); - } - this.httpDynamicStateDescriptionProvider = httpDynamicStateDescriptionProvider; - } - - @Deactivate - public void deactivate() { - try { - secureClient.stop(); - insecureClient.stop(); - } catch (Exception e) { - // catching exception is necessary due to the signature of HttpClient.stop() - logger.warn("Failed to stop insecure http client: {}", e.getMessage()); - } - } - - @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_URL.equals(thingTypeUID)) { - return new HttpThingHandler(thing, this, httpDynamicStateDescriptionProvider); - } - - return null; - } - - @Override - public HttpClient getSecureClient() { - return secureClient; - } - - @Override - public HttpClient getInsecureClient() { - return insecureClient; - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpThingHandler.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpThingHandler.java deleted file mode 100644 index 04a282dde8..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/HttpThingHandler.java +++ /dev/null @@ -1,427 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal; - -import static org.smarthomej.binding.http.internal.HttpBindingConstants.CHANNEL_LAST_FAILURE; -import static org.smarthomej.binding.http.internal.HttpBindingConstants.CHANNEL_LAST_SUCCESS; -import static org.smarthomej.binding.http.internal.HttpBindingConstants.REQUEST_DATE_TIME_CHANNELTYPE_UID; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Base64; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.api.Authentication; -import org.eclipse.jetty.client.api.AuthenticationStore; -import org.eclipse.jetty.client.util.BasicAuthentication; -import org.eclipse.jetty.client.util.DigestAuthentication; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.PointType; -import org.openhab.core.library.types.StringType; -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.binding.BaseThingHandler; -import org.openhab.core.thing.binding.generic.ChannelHandler; -import org.openhab.core.thing.binding.generic.ChannelHandlerContent; -import org.openhab.core.thing.binding.generic.ChannelMode; -import org.openhab.core.thing.binding.generic.ChannelTransformation; -import org.openhab.core.thing.binding.generic.converter.ColorChannelHandler; -import org.openhab.core.thing.binding.generic.converter.DimmerChannelHandler; -import org.openhab.core.thing.binding.generic.converter.FixedValueMappingChannelHandler; -import org.openhab.core.thing.binding.generic.converter.GenericChannelHandler; -import org.openhab.core.thing.binding.generic.converter.ImageChannelHandler; -import org.openhab.core.thing.binding.generic.converter.NumberChannelHandler; -import org.openhab.core.thing.binding.generic.converter.PlayerChannelHandler; -import org.openhab.core.thing.binding.generic.converter.RollershutterChannelHandler; -import org.openhab.core.thing.internal.binding.generic.converter.AbstractTransformingChannelHandler; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.types.StateDescription; -import org.openhab.core.types.StateDescriptionFragmentBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.http.internal.config.HttpChannelConfig; -import org.smarthomej.binding.http.internal.config.HttpThingConfig; -import org.smarthomej.binding.http.internal.http.HttpAuthException; -import org.smarthomej.binding.http.internal.http.HttpResponseListener; -import org.smarthomej.binding.http.internal.http.HttpStatusListener; -import org.smarthomej.binding.http.internal.http.RateLimitedHttpClient; -import org.smarthomej.binding.http.internal.http.RefreshingUrlCache; -import org.smarthomej.commons.SimpleDynamicStateDescriptionProvider; - -/** - * The {@link HttpThingHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class HttpThingHandler extends BaseThingHandler implements HttpStatusListener { - private static final Set URL_PART_DELIMITER = Set.of('/', '?', '&'); - - private final Logger logger = LoggerFactory.getLogger(HttpThingHandler.class); - private final HttpClientProvider httpClientProvider; - private final RateLimitedHttpClient rateLimitedHttpClient; - private final SimpleDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider; - - private HttpThingConfig config = new HttpThingConfig(); - private final Map urlHandlers = new HashMap<>(); - private final Map channels = new HashMap<>(); - private final Map channelUrls = new HashMap<>(); - - public HttpThingHandler(Thing thing, HttpClientProvider httpClientProvider, - SimpleDynamicStateDescriptionProvider httpDynamicStateDescriptionProvider) { - super(thing); - this.httpClientProvider = httpClientProvider; - this.rateLimitedHttpClient = new RateLimitedHttpClient(httpClientProvider.getSecureClient(), scheduler); - this.httpDynamicStateDescriptionProvider = httpDynamicStateDescriptionProvider; - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - ChannelHandler itemValueConverter = channels.get(channelUID); - if (itemValueConverter == null) { - logger.warn("Cannot find channel implementation for channel {}.", channelUID); - return; - } - - if (command instanceof RefreshType) { - String key = channelUrls.get(channelUID); - if (key != null) { - RefreshingUrlCache refreshingUrlCache = urlHandlers.get(key); - if (refreshingUrlCache != null) { - try { - refreshingUrlCache.get().ifPresentOrElse(itemValueConverter::process, () -> { - if (config.strictErrorHandling) { - itemValueConverter.process(null); - } - }); - } catch (IllegalArgumentException | IllegalStateException e) { - logger.warn("Failed processing REFRESH command for channel {}: {}", channelUID, e.getMessage()); - } - } - } - } else { - try { - itemValueConverter.send(command); - } catch (IllegalArgumentException e) { - logger.warn("Failed to convert command '{}' to channel '{}' for sending", command, channelUID); - } catch (IllegalStateException e) { - logger.debug("Writing to read-only channel {} not permitted", channelUID); - } - } - } - - @Override - public void initialize() { - config = getConfigAs(HttpThingConfig.class); - - if (config.baseURL.isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Parameter baseURL must not be empty!"); - return; - } - - // check protocol is set - if (!config.baseURL.startsWith("http://") && !config.baseURL.startsWith("https://")) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "baseURL is invalid: protocol not defined."); - return; - } - - // check SSL handling and initialize client - if (config.ignoreSSLErrors) { - logger.info("Using the insecure client for thing '{}'.", thing.getUID()); - rateLimitedHttpClient.setHttpClient(httpClientProvider.getInsecureClient()); - } else { - logger.info("Using the secure client for thing '{}'.", thing.getUID()); - rateLimitedHttpClient.setHttpClient(httpClientProvider.getSecureClient()); - } - rateLimitedHttpClient.setDelay(config.delay); - - int urlHandlerCount = urlHandlers.size(); - if (urlHandlerCount * config.delay > config.refresh * 1000) { - // this should prevent the rate limit queue from filling up - config.refresh = (urlHandlerCount * config.delay) / 1000 + 1; - logger.warn( - "{} channels in thing {} with a delay of {} incompatible with the configured refresh time. Refresh-Time increased to the minimum of {}", - urlHandlerCount, thing.getUID(), config.delay, config.refresh); - } - - // remove empty headers - config.headers.removeIf(String::isBlank); - - // configure authentication - try { - AuthenticationStore authStore = rateLimitedHttpClient.getAuthenticationStore(); - URI uri = new URI(config.baseURL); - - // clear old auths if available - Authentication.Result authResult = authStore.findAuthenticationResult(uri); - if (authResult != null) { - authStore.removeAuthenticationResult(authResult); - } - for (String authType : List.of("Basic", "Digest")) { - Authentication authentication = authStore.findAuthentication(authType, uri, Authentication.ANY_REALM); - if (authentication != null) { - authStore.removeAuthentication(authentication); - } - } - - if (!config.username.isEmpty() || !config.password.isEmpty()) { - switch (config.authMode) { - case BASIC_PREEMPTIVE: - config.headers.add("Authorization=Basic " + Base64.getEncoder() - .encodeToString((config.username + ":" + config.password).getBytes())); - logger.debug("Preemptive Basic Authentication configured for thing '{}'", thing.getUID()); - break; - case TOKEN: - if (!config.password.isEmpty()) { - config.headers.add("Authorization=Bearer " + config.password); - logger.debug("Token/Bearer Authentication configured for thing '{}'", thing.getUID()); - } else { - logger.warn("Token/Bearer Authentication configured for thing '{}' but token is empty!", - thing.getUID()); - } - break; - case BASIC: - authStore.addAuthentication(new BasicAuthentication(uri, Authentication.ANY_REALM, - config.username, config.password)); - logger.debug("Basic Authentication configured for thing '{}'", thing.getUID()); - break; - case DIGEST: - authStore.addAuthentication(new DigestAuthentication(uri, Authentication.ANY_REALM, - config.username, config.password)); - logger.debug("Digest Authentication configured for thing '{}'", thing.getUID()); - break; - default: - logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode, - thing.getUID()); - } - } else { - logger.debug("No authentication configured for thing '{}'", thing.getUID()); - } - } catch (URISyntaxException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Cannot create URI from baseUrl."); - } - // create channels - thing.getChannels().forEach(this::createChannel); - - updateStatus(ThingStatus.UNKNOWN); - } - - @Override - public void dispose() { - // stop update tasks - urlHandlers.values().forEach(RefreshingUrlCache::stop); - rateLimitedHttpClient.shutdown(); - - // clear lists - urlHandlers.clear(); - channels.clear(); - channelUrls.clear(); - - // remove state descriptions - httpDynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID()); - - super.dispose(); - } - - /** - * create all necessary information to handle every channel - * - * @param channel a thing channel - */ - private void createChannel(Channel channel) { - if (REQUEST_DATE_TIME_CHANNELTYPE_UID.equals(channel.getChannelTypeUID())) { - // do not generate refreshUrls for lastSuccess / lastFailure channels - return; - } - ChannelUID channelUID = channel.getUID(); - HttpChannelConfig channelConfig = channel.getConfiguration().as(HttpChannelConfig.class); - - String stateUrl = concatenateUrlParts(config.baseURL, channelConfig.stateExtension); - String commandUrl = channelConfig.commandExtension == null ? stateUrl - : concatenateUrlParts(config.baseURL, channelConfig.commandExtension); - - String acceptedItemType = channel.getAcceptedItemType(); - if (acceptedItemType == null) { - logger.warn("Cannot determine item-type for channel '{}'", channelUID); - return; - } - - ChannelHandler itemValueConverter; - switch (acceptedItemType) { - case "Color": - itemValueConverter = createChannelHandler(ColorChannelHandler::new, commandUrl, channelUID, - channelConfig); - break; - case "DateTime": - itemValueConverter = createGenericChannelHandler(commandUrl, channelUID, channelConfig, - DateTimeType::new); - break; - case "Dimmer": - itemValueConverter = createChannelHandler(DimmerChannelHandler::new, commandUrl, channelUID, - channelConfig); - break; - case "Contact": - case "Switch": - itemValueConverter = createChannelHandler(FixedValueMappingChannelHandler::new, commandUrl, channelUID, - channelConfig); - break; - case "Image": - itemValueConverter = new ImageChannelHandler(state -> updateState(channelUID, state)); - break; - case "Location": - itemValueConverter = createGenericChannelHandler(commandUrl, channelUID, channelConfig, PointType::new); - break; - case "Number": - itemValueConverter = createChannelHandler(NumberChannelHandler::new, commandUrl, channelUID, - channelConfig); - break; - case "Player": - itemValueConverter = createChannelHandler(PlayerChannelHandler::new, commandUrl, channelUID, - channelConfig); - break; - case "Rollershutter": - itemValueConverter = createChannelHandler(RollershutterChannelHandler::new, commandUrl, channelUID, - channelConfig); - break; - case "String": - itemValueConverter = createGenericChannelHandler(commandUrl, channelUID, channelConfig, - StringType::new); - break; - default: - logger.warn("Unsupported item-type '{}'", channel.getAcceptedItemType()); - return; - } - - channels.put(channelUID, itemValueConverter); - if (channelConfig.mode != ChannelMode.WRITEONLY) { - // we need a key consisting of stateContent and URL, only if both are equal, we can use the same cache - String key = channelConfig.stateContent + "$" + stateUrl; - channelUrls.put(channelUID, key); - Objects.requireNonNull(urlHandlers.computeIfAbsent(key, - k -> new RefreshingUrlCache(scheduler, rateLimitedHttpClient, stateUrl, config, - channelConfig.stateContent, config.contentType, this))) - .addConsumer(itemValueConverter::process); - } - - StateDescription stateDescription = StateDescriptionFragmentBuilder.create() - .withReadOnly(channelConfig.mode == ChannelMode.READONLY).build().toStateDescription(); - if (stateDescription != null) { - // if the state description is not available, we don't need to add it - httpDynamicStateDescriptionProvider.setDescription(channelUID, stateDescription); - } - } - - @Override - public void onHttpError(@Nullable String message) { - updateState(CHANNEL_LAST_FAILURE, new DateTimeType()); - if (config.strictErrorHandling) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - Objects.requireNonNullElse(message, "")); - } - } - - @Override - public void onHttpSuccess() { - updateState(CHANNEL_LAST_SUCCESS, new DateTimeType()); - updateStatus(ThingStatus.ONLINE); - } - - private void sendHttpValue(String commandUrl, String command) { - sendHttpValue(commandUrl, command, false); - } - - private void sendHttpValue(String commandUrl, String command, boolean isRetry) { - try { - // format URL - URI uri = Util.uriFromString(String.format(commandUrl, new Date(), command)); - - // build request - rateLimitedHttpClient.newPriorityRequest(uri, config.commandMethod, command, config.contentType) - .thenAccept(request -> { - request.timeout(config.timeout, TimeUnit.MILLISECONDS); - config.getHeaders().forEach(request::header); - - CompletableFuture<@Nullable ChannelHandlerContent> responseContentFuture = new CompletableFuture<>(); - responseContentFuture.exceptionally(t -> { - if (t instanceof HttpAuthException) { - if (isRetry || !rateLimitedHttpClient.reAuth(uri)) { - logger.warn( - "Retry after authentication failure failed again for '{}', failing here", - uri); - onHttpError("Authorization failed"); - } else { - sendHttpValue(commandUrl, command, true); - } - } - return null; - }); - - if (logger.isTraceEnabled()) { - logger.trace("Sending to '{}': {}", uri, Util.requestToLogString(request)); - } - - request.send(new HttpResponseListener(responseContentFuture, null, config.bufferSize, this)); - }); - } catch (IllegalArgumentException | URISyntaxException | MalformedURLException e) { - logger.warn("Creating request for '{}' failed: {}", commandUrl, e.getMessage()); - } - } - - private String concatenateUrlParts(String baseUrl, @Nullable String extension) { - if (extension != null && !extension.isEmpty()) { - if (!URL_PART_DELIMITER.contains(baseUrl.charAt(baseUrl.length() - 1)) - && !URL_PART_DELIMITER.contains(extension.charAt(0))) { - return baseUrl + "/" + extension; - } else { - return baseUrl + extension; - } - } else { - return baseUrl; - } - } - - private ChannelHandler createChannelHandler(AbstractTransformingChannelHandler.Factory factory, String commandUrl, - ChannelUID channelUID, HttpChannelConfig channelConfig) { - return factory.create(state -> updateState(channelUID, state), command -> postCommand(channelUID, command), - command -> sendHttpValue(commandUrl, command), - new ChannelTransformation(channelConfig.stateTransformation), - new ChannelTransformation(channelConfig.commandTransformation), channelConfig); - } - - private ChannelHandler createGenericChannelHandler(String commandUrl, ChannelUID channelUID, - HttpChannelConfig channelConfig, Function toState) { - AbstractTransformingChannelHandler.Factory factory = (state, command, value, stateTrans, commandTrans, - config) -> new GenericChannelHandler(toState, state, command, value, stateTrans, commandTrans, config); - return createChannelHandler(factory, commandUrl, channelUID, channelConfig); - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/Util.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/Util.java deleted file mode 100644 index 2d93209b7d..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/Util.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal; - -import java.net.IDN; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.http.HttpField; - -/** - * The {@link Util} is a utility class - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class Util { - - /** - * create a log string from a {@link org.eclipse.jetty.client.api.Request} - * - * @param request the request to log - * @return the string representing the request - */ - public static String requestToLogString(Request request) { - ContentProvider contentProvider = request.getContent(); - String contentString = contentProvider == null ? "null" - : StreamSupport.stream(contentProvider.spliterator(), false) - .map(b -> StandardCharsets.UTF_8.decode(b).toString()).collect(Collectors.joining(", ")); - return "Method = {" + request.getMethod() + "}, Headers = {" - + request.getHeaders().stream().map(HttpField::toString).collect(Collectors.joining(", ")) - + "}, Content = {" + contentString + "}"; - } - - /** - * create an URI from a string, escaping all necessary characters - * - * @param s the URI as unescaped string - * @return URI corresponding to the input string - * @throws MalformedURLException if parameter is not an URL - * @throws URISyntaxException if parameter could not be converted to an URI - */ - public static URI uriFromString(String s) throws MalformedURLException, URISyntaxException { - URL url = new URL(s); - URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), - url.getPath(), url.getQuery(), url.getRef()); - return URI.create(uri.toASCIIString()); - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpAuthMode.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpAuthMode.java deleted file mode 100644 index d49e48d652..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpAuthMode.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link HttpAuthMode} enum defines the method used for authentication. - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public enum HttpAuthMode { - BASIC_PREEMPTIVE, - BASIC, - DIGEST, - TOKEN -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpChannelConfig.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpChannelConfig.java deleted file mode 100644 index ed049a8657..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpChannelConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.binding.generic.ChannelValueConverterConfig; - -/** - * The {@link HttpChannelConfig} class contains fields mapping channel configuration parameters. - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class HttpChannelConfig extends ChannelValueConverterConfig { - - public @Nullable String stateExtension; - public @Nullable String commandExtension; - public @Nullable String stateTransformation; - public @Nullable String commandTransformation; - public String stateContent = ""; -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpThingConfig.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpThingConfig.java deleted file mode 100644 index 24556cdb0d..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/config/HttpThingConfig.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.config; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.util.Jetty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link HttpThingConfig} class contains fields mapping thing configuration parameters. - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class HttpThingConfig { - private final Logger logger = LoggerFactory.getLogger(HttpThingConfig.class); - - public String baseURL = ""; - public int refresh = 30; - public int timeout = 3000; - public int delay = 0; - - public String username = ""; - public String password = ""; - - public HttpAuthMode authMode = HttpAuthMode.BASIC; - public HttpMethod stateMethod = HttpMethod.GET; - - public HttpMethod commandMethod = HttpMethod.GET; - public int bufferSize = 2048; - - public @Nullable String encoding = null; - public @Nullable String contentType = null; - - public boolean ignoreSSLErrors = false; - public boolean strictErrorHandling = false; - - // ArrayList is required as implementation because list may be modified later - public ArrayList headers = new ArrayList<>(); - public String userAgent = ""; - - public Map getHeaders() { - Map headersMap = new HashMap<>(); - // add user agent first, in case it is also defined in the headers, it'll be overwritten - headersMap.put(HttpHeader.USER_AGENT.asString(), - userAgent.isBlank() ? "Jetty/" + Jetty.VERSION : userAgent.trim()); - headers.forEach(header -> { - String[] keyValuePair = header.split("=", 2); - if (keyValuePair.length == 2) { - headersMap.put(keyValuePair[0].trim(), keyValuePair[1].trim()); - } else { - logger.warn("Splitting header '{}' failed. No '=' was found. Ignoring", header); - } - }); - - return headersMap; - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpAuthException.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpAuthException.java deleted file mode 100644 index de523e225d..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpAuthException.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.http; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link HttpAuthException} is an exception after authorization errors - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class HttpAuthException extends Exception { - private static final long serialVersionUID = 1L; - - public HttpAuthException() { - super(); - } - - public HttpAuthException(String message) { - super(message); - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpResponseListener.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpResponseListener.java deleted file mode 100644 index 7b9f3f555e..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpResponseListener.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.http; - -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpStatus; -import org.openhab.core.thing.binding.generic.ChannelHandlerContent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link HttpResponseListener} is responsible for processing the result of a HTTP request - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class HttpResponseListener extends BufferingResponseListener { - private final Logger logger = LoggerFactory.getLogger(HttpResponseListener.class); - private final CompletableFuture<@Nullable ChannelHandlerContent> future; - private final HttpStatusListener httpStatusListener; - private final String fallbackEncoding; - - /** - * the HttpResponseListener is responsible - * - * @param future Content future to complete with the result of the request - * @param fallbackEncoding a fallback encoding for the content (UTF-8 if null) - * @param bufferSize the buffer size for the content in kB (default 2048 kB) - */ - public HttpResponseListener(CompletableFuture<@Nullable ChannelHandlerContent> future, - @Nullable String fallbackEncoding, int bufferSize, HttpStatusListener httpStatusListener) { - super(bufferSize * 1024); - this.future = future; - this.fallbackEncoding = fallbackEncoding != null ? fallbackEncoding : StandardCharsets.UTF_8.name(); - this.httpStatusListener = httpStatusListener; - } - - @Override - public void onComplete(Result result) { - Response response = result.getResponse(); - if (logger.isTraceEnabled()) { - logger.trace("Received from '{}': {}", result.getRequest().getURI(), responseToLogString(response)); - } - Request request = result.getRequest(); - if (response == null || (result.isFailed() && response.getStatus() != 401)) { - logger.debug("Requesting '{}' (method='{}', content='{}') failed: {}", request.getURI(), - request.getMethod(), request.getContent(), result.getFailure().getMessage()); - future.complete(null); - httpStatusListener.onHttpError(result.getFailure().getMessage()); - } else { - switch (response.getStatus()) { - case HttpStatus.OK_200: - case HttpStatus.CREATED_201: - case HttpStatus.ACCEPTED_202: - case HttpStatus.NON_AUTHORITATIVE_INFORMATION_203: - case HttpStatus.NO_CONTENT_204: - case HttpStatus.RESET_CONTENT_205: - case HttpStatus.PARTIAL_CONTENT_206: - case HttpStatus.MULTI_STATUS_207: - byte[] content = getContent(); - String encoding = getEncoding(); - if (content != null) { - future.complete(new ChannelHandlerContent(content, - encoding == null ? fallbackEncoding : encoding, getMediaType())); - } else { - future.complete(null); - } - httpStatusListener.onHttpSuccess(); - break; - case HttpStatus.UNAUTHORIZED_401: - logger.debug("Requesting '{}' (method='{}', content='{}') failed: Authorization error", - request.getURI(), request.getMethod(), request.getContent()); - future.completeExceptionally(new HttpAuthException()); - break; - default: - logger.debug("Requesting '{}' (method='{}', content='{}') failed: {} {}", request.getURI(), - request.getMethod(), request.getContent(), response.getStatus(), response.getReason()); - future.complete(null); - httpStatusListener.onHttpError(response.getReason()); - } - } - } - - private String responseToLogString(Response response) { - String logString = "Code = {" + response.getStatus() + "}, Headers = {" - + response.getHeaders().stream().map(HttpField::toString).collect(Collectors.joining(", ")) - + "}, Content = {" + getContentAsString() + "}"; - return logString; - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpStatusListener.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpStatusListener.java deleted file mode 100644 index 0b1d1d6b8d..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/HttpStatusListener.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.http; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link HttpStatusListener} is an interface for reporting HTTP request states - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public interface HttpStatusListener { - /** - * report an error - * - * @param message optional error message - */ - void onHttpError(@Nullable String message); - - /** - * report a successful request - */ - void onHttpSuccess(); -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RateLimitedHttpClient.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RateLimitedHttpClient.java deleted file mode 100644 index cde895d2b0..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RateLimitedHttpClient.java +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.http; - -import java.net.URI; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.Authentication; -import org.eclipse.jetty.client.api.AuthenticationStore; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.util.StringContentProvider; -import org.eclipse.jetty.http.HttpMethod; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link RateLimitedHttpClient} is a wrapper for a Jetty HTTP client that limits the number of requests by delaying - * the request creation - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class RateLimitedHttpClient { - private static final int MAX_QUEUE_SIZE = 1000; // maximum queue size - private final Logger logger = LoggerFactory.getLogger(RateLimitedHttpClient.class); - - private HttpClient httpClient; - private int delay = 0; // in ms - private final ScheduledExecutorService scheduler; - private final LinkedBlockingQueue requestQueue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE); - private final LinkedBlockingQueue priorityRequestQueue = new LinkedBlockingQueue<>( - MAX_QUEUE_SIZE); - - private @Nullable ScheduledFuture processJob; - - public RateLimitedHttpClient(HttpClient httpClient, ScheduledExecutorService scheduler) { - this.httpClient = httpClient; - this.scheduler = scheduler; - } - - /** - * Stop processing the queue and clear it - */ - public void shutdown() { - stopProcessJob(); - requestQueue.forEach(RequestQueueEntry::cancel); - } - - /** - * Set a new delay - * - * @param delay in ms between to requests - */ - public void setDelay(int delay) { - if (delay < 0) { - throw new IllegalArgumentException("Delay needs to be larger or equal to zero"); - } - this.delay = delay; - stopProcessJob(); - if (delay != 0) { - processJob = scheduler.scheduleWithFixedDelay(this::processQueue, 0, delay, TimeUnit.MILLISECONDS); - } - } - - /** - * Set the HTTP client - * - * @param httpClient secure or insecure {@link HttpClient} - */ - public void setHttpClient(HttpClient httpClient) { - this.httpClient = httpClient; - } - - /** - * Create a new request to the given URL respecting rate-limits - * - * @param finalUrl the request URL - * @param method http request method GET/PUT/POST - * @param content the content (if method PUT/POST) - * @return a {@link CompletableFuture} that completes with the request - */ - public CompletableFuture newRequest(URI finalUrl, HttpMethod method, String content, - @Nullable String contentType) { - return queueRequest(finalUrl, method, content, contentType, requestQueue); - } - - /** - * Create a new priority request (executed as next request) to the given URL respecting rate-limits - * - * @param finalUrl the request URL - * @param method http request method GET/PUT/POST - * @param content the content (if method PUT/POST) - * @return a {@link CompletableFuture} that completes with the request - */ - public CompletableFuture newPriorityRequest(URI finalUrl, HttpMethod method, String content, - @Nullable String contentType) { - return queueRequest(finalUrl, method, content, contentType, priorityRequestQueue); - } - - private CompletableFuture queueRequest(URI finalUrl, HttpMethod method, String content, - @Nullable String contentType, LinkedBlockingQueue queue) { - // if no delay is set, return a completed CompletableFuture - CompletableFuture future = new CompletableFuture<>(); - RequestQueueEntry queueEntry = new RequestQueueEntry(finalUrl, method, content, contentType, future); - if (delay == 0) { - queueEntry.completeFuture(httpClient); - } else { - if (!queue.offer(queueEntry)) { - future.completeExceptionally(new RejectedExecutionException("Maximum queue size exceeded.")); - } - - } - return future; - } - - /** - * Get the {@link AuthenticationStore} from the wrapped {@link HttpClient} - * - * @return the AuthenticationStore of the client - */ - public AuthenticationStore getAuthenticationStore() { - return httpClient.getAuthenticationStore(); - } - - /** - * Remove authentication result from the wrapped {@link HttpClient} and force re-auth - * - * @param uri the {@link URI} associated with the authentication result - * @return true if a result was found and cleared, false if not authenticated at all - */ - public boolean reAuth(URI uri) { - AuthenticationStore authStore = httpClient.getAuthenticationStore(); - Authentication.Result authResult = authStore.findAuthenticationResult(uri); - if (authResult != null) { - authStore.removeAuthenticationResult(authResult); - logger.debug("Cleared authentication result for '{}', retrying immediately", uri); - return true; - } else { - logger.warn("Could not find authentication result for '{}', failing here", uri); - return false; - } - } - - private void stopProcessJob() { - ScheduledFuture processJob = this.processJob; - if (processJob != null) { - processJob.cancel(false); - this.processJob = null; - } - } - - /** - * Gets an request from either the priority queue or tge regular queue and creates the request - */ - private void processQueue() { - RequestQueueEntry queueEntry = priorityRequestQueue.poll(); - if (queueEntry == null) { - // no entry in priorityRequestQueue, try the regular queue - queueEntry = requestQueue.poll(); - } - if (queueEntry != null) { - queueEntry.completeFuture(httpClient); - } - } - - private static class RequestQueueEntry { - private final URI finalUrl; - private final HttpMethod method; - private final String content; - private final @Nullable String contentType; - private final CompletableFuture future; - - public RequestQueueEntry(URI finalUrl, HttpMethod method, String content, @Nullable String contentType, - CompletableFuture future) { - this.finalUrl = finalUrl; - this.method = method; - this.content = content; - this.contentType = contentType; - this.future = future; - } - - /** - * complete the future with a request - * - * @param httpClient the client to create the request - */ - public void completeFuture(HttpClient httpClient) { - Request request = httpClient.newRequest(finalUrl).method(method); - if ((method == HttpMethod.POST || method == HttpMethod.PUT) && !content.isEmpty()) { - if (contentType == null) { - request.content(new StringContentProvider(content)); - } else { - request.content(new StringContentProvider(content), contentType); - } - } - future.complete(request); - } - - /** - * cancel this request and complete the future with a {@link CancellationException} - */ - public void cancel() { - future.completeExceptionally(new CancellationException()); - } - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RefreshingUrlCache.java b/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RefreshingUrlCache.java deleted file mode 100644 index 91f7fa3fac..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/java/org/smarthomej/binding/http/internal/http/RefreshingUrlCache.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http.internal.http; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Date; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.http.HttpMethod; -import org.openhab.core.thing.binding.generic.ChannelHandlerContent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.http.internal.Util; -import org.smarthomej.binding.http.internal.config.HttpThingConfig; - -/** - * The {@link RefreshingUrlCache} is responsible for requesting from a single URL and passing the content to the - * channels - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class RefreshingUrlCache { - private final Logger logger = LoggerFactory.getLogger(RefreshingUrlCache.class); - - private final String url; - private final RateLimitedHttpClient httpClient; - private final boolean strictErrorHandling; - private final int timeout; - private final int bufferSize; - private final @Nullable String fallbackEncoding; - private final Set> consumers = ConcurrentHashMap.newKeySet(); - private final Map headers; - private final HttpMethod httpMethod; - private final String httpContent; - private final @Nullable String httpContentType; - private final HttpStatusListener httpStatusListener; - - private final ScheduledFuture future; - private @Nullable ChannelHandlerContent lastContent; - - public RefreshingUrlCache(ScheduledExecutorService executor, RateLimitedHttpClient httpClient, String url, - HttpThingConfig thingConfig, String httpContent, @Nullable String httpContentType, - HttpStatusListener httpStatusListener) { - this.httpClient = httpClient; - this.url = url; - this.strictErrorHandling = thingConfig.strictErrorHandling; - this.timeout = thingConfig.timeout; - this.bufferSize = thingConfig.bufferSize; - this.httpMethod = thingConfig.stateMethod; - this.headers = thingConfig.getHeaders(); - this.httpContent = httpContent; - this.httpContentType = httpContentType; - this.httpStatusListener = httpStatusListener; - fallbackEncoding = thingConfig.encoding; - - future = executor.scheduleWithFixedDelay(this::refresh, 1, thingConfig.refresh, TimeUnit.SECONDS); - logger.trace("Started refresh task for URL '{}' with interval {}s", url, thingConfig.refresh); - } - - private void refresh() { - refresh(false); - } - - private void refresh(boolean isRetry) { - if (consumers.isEmpty()) { - // do not refresh if we don't have listeners - return; - } - - // format URL - try { - URI uri = Util.uriFromString(String.format(this.url, new Date())); - logger.trace("Requesting refresh (retry={}) from '{}' with timeout {}ms", isRetry, uri, timeout); - - httpClient.newRequest(uri, httpMethod, httpContent, httpContentType).thenAccept(request -> { - request.timeout(timeout, TimeUnit.MILLISECONDS); - headers.forEach(request::header); - - CompletableFuture<@Nullable ChannelHandlerContent> responseContentFuture = new CompletableFuture<>(); - responseContentFuture.exceptionally(t -> { - if (t instanceof HttpAuthException) { - if (isRetry || !httpClient.reAuth(uri)) { - logger.debug("Authentication failed for '{}', retry={}", uri, isRetry); - httpStatusListener.onHttpError("Authorization failed"); - } else { - refresh(true); - } - } - return null; - }).thenAccept(this::processResult); - - if (logger.isTraceEnabled()) { - logger.trace("Sending to '{}': {}", uri, Util.requestToLogString(request)); - } - - request.send(new HttpResponseListener(responseContentFuture, fallbackEncoding, bufferSize, - httpStatusListener)); - }).exceptionally(e -> { - if (e instanceof CancellationException) { - logger.debug("Request to URL {} was cancelled by thing handler.", uri); - } else { - logger.warn("Request to URL {} failed: {}", uri, e.getMessage()); - } - return null; - }); - } catch (IllegalArgumentException | URISyntaxException | MalformedURLException e) { - logger.warn("Creating request for '{}' failed: {}", url, e.getMessage()); - } - } - - public void stop() { - // clearing all listeners to prevent further updates - consumers.clear(); - future.cancel(false); - logger.trace("Stopped refresh task for URL '{}'", url); - } - - public void addConsumer(Consumer<@Nullable ChannelHandlerContent> consumer) { - consumers.add(consumer); - } - - public Optional get() { - return Optional.ofNullable(lastContent); - } - - private void processResult(@Nullable ChannelHandlerContent content) { - if (content != null || strictErrorHandling) { - for (Consumer<@Nullable ChannelHandlerContent> consumer : consumers) { - try { - consumer.accept(content); - } catch (IllegalArgumentException | IllegalStateException e) { - logger.warn("Failed processing result for URL {}: {}", url, e.getMessage()); - } - } - } - lastContent = content; - } -} diff --git a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/addon/addon.xml deleted file mode 100644 index a2c9d6cf1d..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/addon/addon.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - binding - HTTP Binding - This is the binding for retrieving and processing HTTP resources. - - diff --git a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/config/config.xml b/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/config/config.xml deleted file mode 100644 index 82cd4456a9..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/config/config.xml +++ /dev/null @@ -1,431 +0,0 @@ - - - - - - - Transformation pattern used when receiving values. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending values. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - - - - - - true - true - READWRITE - - - - - - - Transformation pattern used when receiving values. - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - The value that represents ON - - - - The value that represents OFF - - - - The value that represents INCREASE - - - - The value that represents DECREASE - - - - The value by which the current brightness is increased/decreased if the corresponding command is - received - 1 - - - - Color mode for parsing incoming and sending outgoing values - - - - - true - RGB - - - - - - - - - true - true - READWRITE - - - - - - - Transformation pattern used when receiving value. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - The value that represents OPEN - - - - The value that represents CLOSED - - - - - - - - - true - true - READWRITE - - - - - - - Transformation pattern used when receiving value. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - The value that represents ON - - - - The value that represents OFF - - - - The value that represents INCREASE - - - - The value that represents DECREASE - - - - The value by which the current brightness is increased/decreased if the corresponding command is - received - 1 - - - - - - - - - true - true - READWRITE - - - - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - - - - Transformation pattern used when receiving value. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - - - - - - true - true - READWRITE - - - - Unit to append to the (transformed) value. - true - - - - - - - Transformation pattern used when receiving value. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - The value that represents PLAY - - - - The value that represents PAUSE - - - - The value that represents NEXT - - - - The value that represents PREVIOUS - - - - The value that represents REWIND - - - - The value that represents FASTFORWARD - - - - - - - - - true - true - READWRITE - - - - - - - Transformation pattern used when receiving value. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - The value that represents UP - - - - The value that represents DOWN - - - - The value that represents STOP - - - - The value that represents MOVE - - - - - - - - - true - true - READWRITE - - - - - - - Transformation pattern used when receiving value. Multiple transformation can be chained using "∩". - - - - Transformation pattern used when sending value. Multiple transformation can be chained using "∩". - - - - This value is added to the base URL configured in the thing for retrieving values. - true - - - - This value is added to the base URL configured in the thing for sending values. - true - - - - Content for state request (only used if method is POST/PUT) - true - - - - The value that represents ON - - - - The value that represents OFF - - - - - - - - - true - true - READWRITE - - - - diff --git a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/thing/thing-types.xml deleted file mode 100644 index 2cc7be53ef..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/thing/thing-types.xml +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - Represents a base URL and all associated requests. - - - - - - - - - - - - 1 - - - - - - The URL set here can be extended in the channel configuration. - url - - - - Time between two refreshes of all channels - 30 - - - - The timeout in ms for each request - 3000 - - - - Delay between to requests - 0 - true - - - - Size of the response buffer (default 2048 kB) - 2048 - true - - - - Basic Authentication username - true - - - - Authentication password or token - password - true - - - - - - - - - - BASIC - true - true - - - - HTTP method (GET,POST, PUT) for retrieving a status. - - - - - - true - GET - true - - - - HTTP method (GET,POST, PUT) for sending commands. - - - - - - true - GET - true - - - - The MIME content type. Only used for `POST` and `PUT`. - - - - - - - - true - - - - Fallback Encoding text received by this thing's channels. - true - - - - Additional headers send along with the request - false - true - - - - If set to true ignores invalid SSL certificate errors. This is potentially dangerous. - false - true - - - - Sets a custom user agent (default is "Jetty/version", e.g. "Jetty/9.4.20.v20190813"). - true - - - - - - DateTime - - - - - - Color - - - - - - Contact - - - - - - DateTime - - - - - - Dimmer - - - - - - Image - - - - - - Location - - - - - - Number - - - - - - Player - - - - - - Rollershutter - - - - - - String - - - - - - Switch - - - - - diff --git a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/update/instructions.xml deleted file mode 100644 index 7343854b90..0000000000 --- a/bundles/org.smarthomej.binding.http/src/main/resources/OH-INF/update/instructions.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - http:requestDateTime - - - - http:requestDateTime - - - - - - diff --git a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/AbstractWireMockTest.java b/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/AbstractWireMockTest.java deleted file mode 100644 index 7a40b19d9d..0000000000 --- a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/AbstractWireMockTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http; - -import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; -import static com.github.tomakehurst.wiremock.client.WireMock.removeAllMappings; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; - -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jetty.client.HttpClient; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.openhab.core.test.TestPortUtil; -import org.openhab.core.test.java.JavaTest; -import org.smarthomej.binding.http.internal.http.RateLimitedHttpClient; - -import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; - -/** - * The {@link AbstractWireMockTest} implements tests for the {@link RateLimitedHttpClient} - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public abstract class AbstractWireMockTest extends JavaTest { - protected int port = 0; - protected @NonNullByDefault({}) WireMockServer wireMockServer; - protected @NonNullByDefault({}) HttpClient httpClient; - protected ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(4); - - @BeforeAll - public void initAll() throws Exception { - port = TestPortUtil.findFreePort(); - - wireMockServer = new WireMockServer(options().port(port).extensions(new ResponseTemplateTransformer(false))); - wireMockServer.start(); - - httpClient = new HttpClient(); - httpClient.start(); - - configureFor("localhost", port); - } - - @AfterEach - public void cleanUpTest() { - removeAllMappings(); - } - - @AfterAll - public void cleanUpAll() throws Exception { - wireMockServer.shutdown(); - scheduler.shutdown(); - httpClient.stop(); - } -} diff --git a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RateLimitedHttpClientTest.java b/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RateLimitedHttpClientTest.java deleted file mode 100644 index 84ba72d509..0000000000 --- a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RateLimitedHttpClientTest.java +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.anyOf; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.lessThan; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -import java.net.URI; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.http.HttpMethod; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.smarthomej.binding.http.internal.http.RateLimitedHttpClient; - -/** - * The {@link RateLimitedHttpClientTest} implements tests for the {@link RateLimitedHttpClient} - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class RateLimitedHttpClientTest extends AbstractWireMockTest { - private static final String TEST_LOCATION = "/testlocation"; - private static final String TEST_CONTENT = "TESTCONTENT"; - - private List responses = new CopyOnWriteArrayList<>(); - - @AfterEach - public void cleanUpTest() { - responses.clear(); - super.cleanUpTest(); - } - - @Test - public void testWithoutLimit() { - doLimitTest(0, List.of(false, false)); - - // we except to receive the responses in the correct order - assertEquals(0, responses.get(0).seqNumber); - assertEquals(1, responses.get(1).seqNumber); - - // we expect a short delay between both requests, but less than 100ms - long msBetween = responses.get(1).time - responses.get(0).time; - assertThat((int) msBetween, allOf(greaterThanOrEqualTo(0), lessThan(100))); - } - - @Test - public void testWithLimit() { - doLimitTest(500, List.of(false, false)); - // we except to receive the responses in the correct order - assertEquals(0, responses.get(0).seqNumber); - assertEquals(1, responses.get(1).seqNumber); - - // we expect at least 500ms delay between both requests, but less than 500+100=600ms - long msBetween = responses.get(1).time - responses.get(0).time; - assertThat((int) msBetween, allOf(greaterThanOrEqualTo(500), lessThan(600))); - } - - @Test - public void testWithLimitAndPriority() { - doLimitTest(500, List.of(false, false, true)); - - // we expect to receive the responses of request 3 before request two, exact order of 1 and 3 depends on timing, - // so accept both - assertThat(responses.get(0).seqNumber, anyOf(equalTo(0), equalTo(2))); - assertThat(responses.get(1).seqNumber, anyOf(equalTo(0), equalTo(2))); - assertNotEquals(responses.get(1).seqNumber, responses.get(0).seqNumber); - assertEquals(1, responses.get(2).seqNumber); - - // we expect at least 2*500=1000ms delay between the first and last request, but less than 2*500+100=1100 ms - long msBetween = responses.get(2).time - responses.get(0).time; - assertThat((int) msBetween, allOf(greaterThanOrEqualTo(1000), lessThan(1100))); - } - - private List doLimitTest(int setDelay, List config) { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse().withBody(TEST_CONTENT))); - - RateLimitedHttpClient rateLimitedHttpClient = new RateLimitedHttpClient(httpClient, scheduler); - rateLimitedHttpClient.setDelay(setDelay); - - URI url = URI.create("http://localhost:" + port + TEST_LOCATION); - int seqNumber = 0; - - for (boolean priority : config) { - int nextSeqNumber = seqNumber++; - CompletableFuture requestFuture; - - if (priority) { - requestFuture = rateLimitedHttpClient.newPriorityRequest(url, HttpMethod.GET, "", null); - } else { - requestFuture = rateLimitedHttpClient.newRequest(url, HttpMethod.GET, "", null); - } - - requestFuture.thenAccept(request -> { - try { - responses.add(new Response(nextSeqNumber, request.send())); - } catch (Exception e) { - } - }); - } - - // wait until we got all results - waitForAssert(() -> assertEquals(config.size(), responses.size())); - rateLimitedHttpClient.shutdown(); - - return responses; - } - - private static class Response { - public final int seqNumber; - public final long time = System.currentTimeMillis(); - public final String content; - - public Response(int seqNumber, ContentResponse contentResponse) { - this.seqNumber = seqNumber; - this.content = contentResponse.getContentAsString(); - } - } -} diff --git a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RefreshingUrlCacheTest.java b/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RefreshingUrlCacheTest.java deleted file mode 100644 index fad6c7968b..0000000000 --- a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/RefreshingUrlCacheTest.java +++ /dev/null @@ -1,261 +0,0 @@ -/** - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockingDetails; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.util.Jetty; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.openhab.core.thing.binding.generic.ChannelHandlerContent; -import org.smarthomej.binding.http.internal.config.HttpThingConfig; -import org.smarthomej.binding.http.internal.http.HttpStatusListener; -import org.smarthomej.binding.http.internal.http.RateLimitedHttpClient; -import org.smarthomej.binding.http.internal.http.RefreshingUrlCache; - -/** - * The {@link RefreshingUrlCacheTest} implements tests for the {@link RefreshingUrlCache} - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class RefreshingUrlCacheTest extends AbstractWireMockTest { - private static final String TEST_LOCATION = "/testlocation"; - private static final String TEST_CONTENT = "TESTCONTENT"; - - private @NonNullByDefault({}) RateLimitedHttpClient rateLimitedHttpClient; - private @NonNullByDefault({}) HttpThingConfig thingConfig; - private @NonNullByDefault({}) String url; - private @NonNullByDefault({}) HttpStatusListener statusListener; - - private final List<@Nullable ChannelHandlerContent> contentWrappers = new CopyOnWriteArrayList<>(); - - @BeforeEach - public void initTest() { - // this is usually done inside the HttpHandlerFactory when creating the clients - httpClient.setUserAgentField(null); - - // create a RateLimitedHttpClient - rateLimitedHttpClient = new RateLimitedHttpClient(httpClient, scheduler); - rateLimitedHttpClient.setDelay(0); - statusListener = mock(HttpStatusListener.class); - - // initialize thing config with some default values - thingConfig = new HttpThingConfig(); - thingConfig.baseURL = "http://localhost:" + port; - thingConfig.timeout = 500; - thingConfig.refresh = 1; - - url = thingConfig.baseURL + TEST_LOCATION; - } - - @AfterEach - public void cleanUpTest() { - rateLimitedHttpClient.shutdown(); - contentWrappers.clear(); - super.cleanUpTest(); - } - - @Test - public void testUpdateOnSuccessfulRequest() { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse().withBody(TEST_CONTENT))); - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // wait until we got at least four results or timeout (after 10s) - waitForAssert(() -> assertEquals(4, contentWrappers.size())); - urlCache.stop(); - - // verify we did not have errors and the number of responses matches the number of success calls - verify(statusListener, never()).onHttpError(any()); - verify(statusListener, times(contentWrappers.size())).onHttpSuccess(); - - // assert all content equals the correct value - assertTrue(contentWrappers.stream().map(Objects::requireNonNull).map(ChannelHandlerContent::getAsString) - .allMatch(TEST_CONTENT::equals)); - } - - @Test - public void testNoUpdateOn404ErrorInNormalMode() { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse().withStatus(404))); - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // verify we get at least two error reports in 3s - verify(statusListener, timeout(3000).atLeast(2)).onHttpError(any()); - verify(statusListener, never()).onHttpSuccess(); - urlCache.stop(); - - // assert all content equals the correct value - assertEquals(true, contentWrappers.isEmpty()); - } - - @Test - public void testNullUpdateOn404ErrorInStrictMode() { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse().withStatus(404))); - thingConfig.strictErrorHandling = true; - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // verify we get at least two error reports in 3s - verify(statusListener, timeout(3000).atLeast(2)).onHttpError(any()); - verify(statusListener, never()).onHttpSuccess(); - urlCache.stop(); - - int totalErrorCalls = mockingDetails(statusListener).getInvocations().size(); - - // assert we have the same number of consumer calls as error calls and all are null - assertEquals(totalErrorCalls, contentWrappers.size()); - assertEquals(true, contentWrappers.stream().allMatch(Objects::isNull)); - } - - @Test - public void testNoUpdateOnRequestTimedOutInNormalMode() { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse().withFixedDelay(1000).withStatus(200))); - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // verify we get at least two error reports in 3s - verify(statusListener, timeout(3000).atLeast(2)).onHttpError(any()); - verify(statusListener, never()).onHttpSuccess(); - urlCache.stop(); - - // assert all content equals the correct value - assertEquals(true, contentWrappers.isEmpty()); - } - - @Test - public void testNullUpdateOnRequestTimedOutInStrictMode() { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse().withFixedDelay(1000).withStatus(200))); - thingConfig.strictErrorHandling = true; - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // verify we get at least two error reports in 3s - verify(statusListener, timeout(3000).atLeast(2)).onHttpError(any()); - verify(statusListener, never()).onHttpSuccess(); - urlCache.stop(); - - int totalErrorCalls = mockingDetails(statusListener).getInvocations().size(); - - // assert we have the same number of consumer calls as error calls and all are null - assertEquals(totalErrorCalls, contentWrappers.size()); - assertEquals(true, contentWrappers.stream().allMatch(Objects::isNull)); - } - - @Test - public void testAdditionalHeaderIsSentWithRequest() { - String testHeaderKey = "X-SMARTHOME"; - String testHeaderValue = "TESTVALUE"; - - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn(aResponse() - .withBody("{{request.headers." + testHeaderKey + "}}").withTransformers("response-template"))); - thingConfig.headers = new ArrayList<>(List.of(testHeaderKey + "=" + testHeaderValue)); - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // we need only one answer - waitForAssert(() -> assertFalse(contentWrappers.isEmpty())); - urlCache.stop(); - - String returnedHeaderValue = Objects.requireNonNull(contentWrappers.get(0)).getAsString(); - assertEquals(testHeaderValue, returnedHeaderValue); - } - - @Test - public void testUserAgentIsJettyWhenNotConfigured() { - stubFor(get(urlEqualTo(TEST_LOCATION)).willReturn( - aResponse().withBody("{{request.headers.User-Agent}}").withTransformers("response-template"))); - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // we need only one answer - waitForAssert(() -> assertFalse(contentWrappers.isEmpty())); - urlCache.stop(); - - String returnedHeaderValue = Objects.requireNonNull(contentWrappers.get(0)).getAsString(); - assertEquals("Jetty/" + Jetty.VERSION, returnedHeaderValue); - } - - @Test - public void testContentSentAlongWithPost() { - stubFor(post(urlEqualTo(TEST_LOCATION)) - .willReturn(aResponse().withBody("{{request.body}}").withTransformers("response-template"))); - thingConfig.stateMethod = HttpMethod.POST; - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // we need only one answer - waitForAssert(() -> assertFalse(contentWrappers.isEmpty())); - urlCache.stop(); - - String returnedBody = Objects.requireNonNull(contentWrappers.get(0)).getAsString(); - assertEquals(TEST_CONTENT, returnedBody); - } - - @Test - public void testDateIsFormattedInURL() { - stubFor(get(urlPathEqualTo(TEST_LOCATION)) - .willReturn(aResponse().withBody("{{request.query.date}}").withTransformers("response-template"))); - url += "?date=%1$tY-%1$tm-%1$td"; - - RefreshingUrlCache urlCache = getUrlCache(TEST_CONTENT); - - // we need only one answer - waitForAssert(() -> assertFalse(contentWrappers.isEmpty())); - urlCache.stop(); - - String returnedQueryValue = Objects.requireNonNull(contentWrappers.get(0)).getAsString(); - assertTrue(returnedQueryValue.matches("\\d{4}-\\d{2}-\\d{2}")); - } - - /** - * helper method to create a {@link RefreshingUrlCache} and add a test listener - * - * @param content HTTP content - * @return the cache object - */ - private RefreshingUrlCache getUrlCache(String content) { - RefreshingUrlCache urlCache = new RefreshingUrlCache(scheduler, rateLimitedHttpClient, url, thingConfig, - content, null, statusListener); - urlCache.addConsumer(contentWrappers::add); - - return urlCache; - } -} diff --git a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/UtilTest.java b/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/UtilTest.java deleted file mode 100644 index 728bc368be..0000000000 --- a/bundles/org.smarthomej.binding.http/src/test/java/org/smarthomej/binding/http/UtilTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.http; - -import java.net.MalformedURLException; -import java.net.URISyntaxException; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.smarthomej.binding.http.internal.Util; - -/** - * The {@link UtilTest} is a test class for URL encoding - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class UtilTest { - - @Test - public void uriUTF8InHostnameEncodeTest() throws MalformedURLException, URISyntaxException { - String s = "https://foöo.bar/zhu.html?str=zin&tzz=678"; - Assertions.assertEquals("https://xn--foo-tna.bar/zhu.html?str=zin&tzz=678", Util.uriFromString(s).toString()); - } - - @Test - public void uriUTF8InPathEncodeTest() throws MalformedURLException, URISyntaxException { - String s = "https://foo.bar/zül.html?str=zin"; - Assertions.assertEquals("https://foo.bar/z%C3%BCl.html?str=zin", Util.uriFromString(s).toString()); - } - - @Test - public void uriUTF8InQueryEncodeTest() throws MalformedURLException, URISyntaxException { - String s = "https://foo.bar/zil.html?str=zän"; - Assertions.assertEquals("https://foo.bar/zil.html?str=z%C3%A4n", Util.uriFromString(s).toString()); - } - - @Test - public void uriSpaceInPathEncodeTest() throws MalformedURLException, URISyntaxException { - String s = "https://foo.bar/z l.html?str=zun"; - Assertions.assertEquals("https://foo.bar/z%20l.html?str=zun", Util.uriFromString(s).toString()); - } - - @Test - public void uriSpaceInQueryEncodeTest() throws MalformedURLException, URISyntaxException { - String s = "https://foo.bar/zzl.html?str=z n"; - Assertions.assertEquals("https://foo.bar/zzl.html?str=z%20n", Util.uriFromString(s).toString()); - } -} diff --git a/bundles/org.smarthomej.binding.http/xtend_examples.md b/bundles/org.smarthomej.binding.http/xtend_examples.md deleted file mode 100644 index 50b492fceb..0000000000 --- a/bundles/org.smarthomej.binding.http/xtend_examples.md +++ /dev/null @@ -1,63 +0,0 @@ -## Vito WIFI (https://github.com/openhab/openhab-addons/issues/9480#issuecomment-751335696) - -### .things -``` -Thing http:url:vitowifi "VitoWifi" @ "1stfloor" [baseURL="http://192.168.1.61/", commandMethod="POST", delay="1000", refresh="90", timeout="900"] { - - Channels: - Type number : Channel_Vito_ID "Vito_ID" [ stateExtension="read?DP=0x00f8&Type=CountS", stateTransformation="REGEX:(^[-+]?[0-9]+)" ] - Type number : Channel_Vito_Mode "Vito_Mode" [ stateExtension="read?DP=0xb000&Type=Mode", stateTransformation="REGEX:(^[-+]?[0-9]+)", commandExtension="write?DP=0xb000&Type=Mode&Value=%2$s" ] - Type number : Channel_Vito_OnOff "Vito_OnOff_for_ESPEasy" [ stateExtension="control?cmd=Status,GPIO,12", stateTransformation="JSONPATH(state)",commandExtension="control?cmd=GPIO,12,%2$s"] -} -``` - -### .items - -``` -Number Vito_ID "Vito_ID" (gHeating) {channel="http:url:vitowifi:Channel_Vito_ID" , expire="5m" } -Number Vito_Mode "Vito_Mode" (gHeating) {channel="http:url:vitowifi:Channel_Vito_Mode" , expire="5m" }// 0= Off 1= WW 2= WW+heat 0x42 (66)=party -Number Vito_OnOff "ONOFF Switch" (gHeating) {channel="http:url:vitowifi:Channel_Vito_OnOff" , expire="5m" } -``` - -## Feinstaubsensor (https://community.openhab.org/t/http-binding-openhab-3-version/101851/235) - -The http request http://feinstaubsensor-14255834/data.json is answering with a JSON string. -So I need some simple JSONPATH transformation and one java script transformation. -Data is polled every 10 seconds. - -### .things - -``` -Thing http:url:feinstaub "Feinstaub" [ baseURL="http://feinstaubsensor-14255834/data.json", refresh=10] { - Channels: - Type number : SDS_PM10 [ stateTransformation="JSONPATH:$.sensordatavalues[0].value" ] - Type number : SDS_PM25 [ stateTransformation="JSONPATH:$.sensordatavalues[1].value" ] - Type number : Temperatur [ stateTransformation="JSONPATH:$.sensordatavalues[2].value" ] - Type number : Pressure [ stateTransformation="JS:airpressure.js" ] - Type number : Humidity [ stateTransformation="JSONPATH:$.sensordatavalues[4].value" ] -} -``` - -### .items - -``` -/* ************************** - * Feinstaub sensor data - * ************************** */ - Number N_FS_SDS_PM10 "Partikelgröße 10µm [%.2f µg/m³]" { channel="http:url:feinstaub:SDS_PM10" } - Number N_FS_SDS_PM25 "Partikelgröße 2.5µm [%.2f µg/m³]" { channel="http:url:feinstaub:SDS_PM25" } - Number N_FS_Temperatur "Temperatur [%.2f °C]" { channel="http:url:feinstaub:Temperatur" } - Number N_FS_Pressure "Luftdruck [%.2f hPa]" { channel="http:url:feinstaub:Pressure" } - Number N_FS_Humidity "Luftfeuchte [%.2f %%]" { channel="http:url:feinstaub:Humidity" } -``` - -### airpressure.js (transformation) - -I have a BME2080 sensor connected. The Humidity must be diveded by 100 to show hPa. - -``` -(function(x) { - var json = JSON.parse(x); - return json.sensordatavalues[3].value/100; -})(input) -``` \ No newline at end of file diff --git a/bundles/pom.xml b/bundles/pom.xml index 00c033c0c8..f552b592f9 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -24,7 +24,6 @@ org.smarthomej.binding.androiddebugbridge org.smarthomej.binding.amazonechocontrol - org.smarthomej.binding.http org.smarthomej.binding.notificationsforfiretv org.smarthomej.binding.tcpudp org.smarthomej.binding.telenot