diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java index 2168e1223..21d8d2ad4 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCDeviceHandler.java @@ -21,6 +21,8 @@ import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Handler for physical Bosch devices with configurable IDs (as opposed to system services, which have static IDs). @@ -42,12 +44,14 @@ import org.openhab.core.thing.ThingStatusDetail; @NonNullByDefault public abstract class BoschSHCDeviceHandler extends BoschSHCHandler { + private final Logger logger = LoggerFactory.getLogger(getClass()); + /** * Bosch SHC configuration loaded from openHAB configuration. */ private @Nullable BoschSHCConfiguration config; - public BoschSHCDeviceHandler(Thing thing) { + protected BoschSHCDeviceHandler(Thing thing) { super(thing); } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java index 13f764f93..a6e974765 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/BoschSHCHandler.java @@ -40,7 +40,6 @@ import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonElement; /** @@ -80,12 +79,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler { public final Collection affectedChannels; } - /** - * Reusable gson instance to convert a class to json string and back in derived classes. - */ - protected static final Gson GSON = new Gson(); - - protected final Logger logger = LoggerFactory.getLogger(getClass()); + private final Logger logger = LoggerFactory.getLogger(getClass()); /** * Services of the device. @@ -450,7 +444,7 @@ public abstract class BoschSHCHandler extends BaseThingHandler { */ private void registerService(BoschSHCService service, Collection affectedChannels) { - this.services.add(new DeviceService(service, affectedChannels)); + this.services.add(new DeviceService<>(service, affectedChannels)); } /** diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java index a07f9f5be..1e85509b7 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BoschHttpClient.java @@ -37,10 +37,10 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; +import org.openhab.binding.boschshc.internal.serialization.GsonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; /** @@ -51,9 +51,8 @@ import com.google.gson.JsonSyntaxException; */ @NonNullByDefault public class BoschHttpClient extends HttpClient { - private static final Gson GSON = new Gson(); - private final Logger logger = LoggerFactory.getLogger(BoschHttpClient.class); + private final Logger logger = LoggerFactory.getLogger(getClass()); private final String ipAddress; private final String systemPassword; @@ -177,8 +176,10 @@ public class BoschHttpClient extends HttpClient { logger.debug("Online check failed with status code: {}", contentResponse.getStatus()); return false; } - } catch (TimeoutException | ExecutionException | NullPointerException e) { - logger.debug("Online check failed because of {}!", e.getMessage()); + } catch (InterruptedException e) { + throw e; + } catch (Exception e) { + logger.debug("Online check failed because of {}!", e.getMessage(), e); return false; } } @@ -203,8 +204,10 @@ public class BoschHttpClient extends HttpClient { logger.debug("Access check failed with status code: {}", contentResponse.getStatus()); return false; } - } catch (TimeoutException | ExecutionException | NullPointerException e) { - logger.debug("Access check failed because of {}!", e.getMessage()); + } catch (InterruptedException e) { + throw e; + } catch (Exception e) { + logger.debug("Access check failed because of {}!", e.getMessage(), e); return false; } } @@ -249,8 +252,8 @@ public class BoschHttpClient extends HttpClient { logger.info("Pairing failed with response status {}.", contentResponse.getStatus()); return false; } - } catch (TimeoutException | CertificateEncodingException | KeyStoreException | NullPointerException e) { - logger.warn("Pairing failed with exception {}", e.getMessage()); + } catch (TimeoutException | CertificateEncodingException | KeyStoreException | RuntimeException e) { + logger.warn("Pairing failed with exception {}", e.getMessage(), e); return false; } catch (ExecutionException e) { // javax.net.ssl.SSLHandshakeException: General SSLEngine problem @@ -281,15 +284,15 @@ public class BoschHttpClient extends HttpClient { * @return created HTTP request instance */ public Request createRequest(String url, HttpMethod method, @Nullable Object content) { - logger.trace("Create request for http client {}", this.toString()); + logger.trace("Create request for http client {}", this); Request request = this.newRequest(url).method(method).header("Content-Type", "application/json") .header("api-version", "2.1") // see https://github.com/BoschSmartHome/bosch-shc-api-docs/issues/46 .timeout(10, TimeUnit.SECONDS); // Set default timeout if (content != null) { - String body = GSON.toJson(content); - logger.trace("create request for {} and content {}", url, content.toString()); + String body = GsonUtils.DEFAULT_GSON_INSTANCE.toJson(content); + logger.trace("create request for {} and content {}", url, content); request = request.content(new StringContentProvider(body)); } else { logger.trace("create request for {}", url); @@ -314,7 +317,7 @@ public class BoschHttpClient extends HttpClient { Predicate contentValidator, @Nullable BiFunction errorResponseHandler) throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { - logger.trace("Send request: {}", request.toString()); + logger.trace("Send request: {}", request); ContentResponse contentResponse = request.send(); @@ -334,7 +337,7 @@ public class BoschHttpClient extends HttpClient { try { @Nullable - TContent content = GSON.fromJson(textContent, responseContentClass); + TContent content = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(textContent, responseContentClass); if (content == null) { throw new ExecutionException(String.format("Received no content in response, expected type %s", responseContentClass.getName()), null); diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java index 6c0752c78..926c45063 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandler.java @@ -42,6 +42,7 @@ import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException; import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException; +import org.openhab.binding.boschshc.internal.serialization.GsonUtils; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; import org.openhab.binding.boschshc.internal.services.dto.JsonRestExceptionResponse; import org.openhab.core.thing.Bridge; @@ -58,7 +59,6 @@ import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; @@ -76,11 +76,6 @@ public class BridgeHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class); - /** - * gson instance to convert a class to json string and back. - */ - private final Gson gson = new Gson(); - /** * Handler to do long polling. */ @@ -143,6 +138,7 @@ public class BridgeHandler extends BaseBridgeHandler { // prepare SSL key and certificates factory = new BoschSslUtil(ipAddress).getSslContextFactory(); } catch (PairingFailedException e) { + logger.debug("Error while obtaining SSL context factory.", e); this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "@text/offline.conf-error-ssl"); return; @@ -187,7 +183,7 @@ public class BridgeHandler extends BaseBridgeHandler { try { httpClient.stop(); } catch (Exception e) { - logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage()); + logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage(), e); } this.httpClient = null; } @@ -197,6 +193,7 @@ public class BridgeHandler extends BaseBridgeHandler { @Override public void handleCommand(ChannelUID channelUID, Command command) { + // commands are handled by individual device handlers } /** @@ -262,11 +259,7 @@ public class BridgeHandler extends BaseBridgeHandler { // start long polling loop this.updateStatus(ThingStatus.ONLINE); - try { - this.longPolling.start(httpClient); - } catch (LongPollingFailedException e) { - this.handleLongPollFailure(e); - } + startLongPolling(httpClient); } catch (InterruptedException e) { this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE, "@text/offline.interrupted"); @@ -274,6 +267,14 @@ public class BridgeHandler extends BaseBridgeHandler { } } + private void startLongPolling(BoschHttpClient httpClient) { + try { + this.longPolling.start(httpClient); + } catch (LongPollingFailedException e) { + this.handleLongPollFailure(e); + } + } + /** * Check the bridge access by sending an HTTP request. * Does not throw any exception in case the request fails. @@ -334,11 +335,10 @@ public class BridgeHandler extends BaseBridgeHandler { Type collectionType = new TypeToken>() { }.getType(); - @Nullable - List nullableDevices = gson.fromJson(content, collectionType); + List nullableDevices = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, collectionType); return Optional.ofNullable(nullableDevices).orElse(Collections.emptyList()); } catch (TimeoutException | ExecutionException e) { - logger.debug("Request devices failed because of {}!", e.getMessage()); + logger.debug("Request devices failed because of {}!", e.getMessage(), e); return Collections.emptyList(); } } @@ -371,7 +371,7 @@ public class BridgeHandler extends BaseBridgeHandler { Type collectionType = new TypeToken>() { }.getType(); - ArrayList rooms = gson.fromJson(content, collectionType); + ArrayList rooms = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, collectionType); return Objects.requireNonNullElse(rooms, emptyRooms); } catch (TimeoutException | ExecutionException e) { logger.debug("Request rooms failed because of {}!", e.getMessage()); @@ -452,7 +452,7 @@ public class BridgeHandler extends BaseBridgeHandler { // the battery level service receives no individual state object but rather requires the DeviceServiceData // structure if ("BatteryLevel".equals(deviceServiceData.id)) { - return gson.toJsonTree(deviceServiceData); + return GsonUtils.DEFAULT_GSON_INSTANCE.toJsonTree(deviceServiceData); } return deviceServiceData.state; @@ -473,8 +473,7 @@ public class BridgeHandler extends BaseBridgeHandler { // All children of this should implement BoschSHCHandler @Nullable ThingHandler baseHandler = childThing.getHandler(); - if (baseHandler != null && baseHandler instanceof BoschSHCHandler) { - BoschSHCHandler handler = (BoschSHCHandler) baseHandler; + if (baseHandler instanceof BoschSHCHandler handler) { @Nullable String deviceId = handler.getBoschID(); @@ -530,7 +529,8 @@ public class BridgeHandler extends BaseBridgeHandler { Request request = httpClient.createRequest(url, GET); return httpClient.sendRequest(request, Device.class, Device::isValid, (Integer statusCode, String content) -> { - JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class); + JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, + JsonRestExceptionResponse.class); if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) { if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) { return new BoschSHCException("@text/offline.conf-error.invalid-device-id"); @@ -628,7 +628,8 @@ public class BridgeHandler extends BaseBridgeHandler { int statusCode = contentResponse.getStatus(); if (statusCode != 200) { - JsonRestExceptionResponse errorResponse = gson.fromJson(content, JsonRestExceptionResponse.class); + JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, + JsonRestExceptionResponse.class); if (errorResponse != null) { throw new BoschSHCException( String.format("State request with URL %s failed with status code %d and error code %s", url, diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java index e5fd1842d..f1a843af6 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/bridge/LongPolling.java @@ -30,11 +30,10 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult; import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException; +import org.openhab.binding.boschshc.internal.serialization.GsonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; - /** * Handles the long polling to the Smart Home Controller. * @@ -45,11 +44,6 @@ public class LongPolling { private final Logger logger = LoggerFactory.getLogger(LongPolling.class); - /** - * gson instance to convert a class to json string and back. - */ - private final Gson gson = new Gson(); - /** * Executor to schedule long polls. */ @@ -107,17 +101,15 @@ public class LongPolling { private String subscribe(BoschHttpClient httpClient) throws LongPollingFailedException { try { String url = httpClient.getBoschShcUrl("remote/json-rpc"); - JsonRpcRequest request = new JsonRpcRequest("2.0", "RE/subscribe", + JsonRpcRequest subscriptionRequest = new JsonRpcRequest("2.0", "RE/subscribe", new String[] { "com/bosch/sh/remote/*", null }); - logger.debug("Subscribe: Sending request: {} - using httpClient {}", request.toString(), - httpClient.toString()); - Request httpRequest = httpClient.createRequest(url, POST, request); + logger.debug("Subscribe: Sending request: {} - using httpClient {}", subscriptionRequest, httpClient); + Request httpRequest = httpClient.createRequest(url, POST, subscriptionRequest); SubscribeResult response = httpClient.sendRequest(httpRequest, SubscribeResult.class, SubscribeResult::isValid, null); logger.debug("Subscribe: Got subscription ID: {} {}", response.getResult(), response.getJsonrpc()); - String subscriptionId = response.getResult(); - return subscriptionId; + return response.getResult(); } catch (TimeoutException | ExecutionException | BoschSHCException e) { throw new LongPollingFailedException( String.format("Error on subscribe (Http client: %s): %s", httpClient.toString(), e.getMessage()), @@ -141,7 +133,6 @@ public class LongPolling { this.executeLongPoll(httpClient, subscriptionId); } catch (LongPollingFailedException e) { this.handleFailure.accept(e); - return; } } @@ -157,15 +148,15 @@ public class LongPolling { JsonRpcRequest requestContent = new JsonRpcRequest("2.0", "RE/longPoll", new String[] { subscriptionId, "20" }); String url = httpClient.getBoschShcUrl("remote/json-rpc"); - Request request = httpClient.createRequest(url, POST, requestContent); + Request longPollRequest = httpClient.createRequest(url, POST, requestContent); // Long polling responds after 20 seconds with an empty response if no update has happened. // 10 second threshold was added to not time out if response from controller takes a bit longer than 20 seconds. - request.timeout(30, TimeUnit.SECONDS); + longPollRequest.timeout(30, TimeUnit.SECONDS); - this.request = request; + this.request = longPollRequest; LongPolling longPolling = this; - request.send(new BufferingResponseListener() { + longPollRequest.send(new BufferingResponseListener() { @Override public void onComplete(@Nullable Result result) { // NOTE: This handler runs inside the HTTP thread, so we schedule the response handling in a new thread @@ -195,31 +186,20 @@ public class LongPolling { // Check if response was failure or success Throwable failure = result != null ? result.getFailure() : null; if (failure != null) { - if (failure instanceof ExecutionException) { - if (failure.getCause() instanceof AbortLongPolling) { - logger.debug("Canceling long polling for subscription id {} because it was aborted", - subscriptionId); - } else { - this.handleFailure.accept(new LongPollingFailedException( - "Unexpected exception during long polling request", failure)); - } - } else { - this.handleFailure.accept( - new LongPollingFailedException("Unexpected exception during long polling request", failure)); - } + handleLongPollFailure(subscriptionId, failure); } else { logger.debug("Long poll response: {}", content); String nextSubscriptionId = subscriptionId; - LongPollResult longPollResult = gson.fromJson(content, LongPollResult.class); + LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, LongPollResult.class); if (longPollResult != null && longPollResult.result != null) { this.handleResult.accept(longPollResult); } else { logger.debug("Long poll response contained no result: {}", content); // Check if we got a proper result from the SHC - LongPollError longPollError = gson.fromJson(content, LongPollError.class); + LongPollError longPollError = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content, LongPollError.class); if (longPollError != null && longPollError.error != null) { logger.debug("Got long poll error: {} (code: {})", longPollError.error.message, @@ -238,6 +218,20 @@ public class LongPolling { } } + private void handleLongPollFailure(String subscriptionId, Throwable failure) { + if (failure instanceof ExecutionException) { + if (failure.getCause() instanceof AbortLongPolling) { + logger.debug("Canceling long polling for subscription id {} because it was aborted", subscriptionId); + } else { + this.handleFailure.accept( + new LongPollingFailedException("Unexpected exception during long polling request", failure)); + } + } else { + this.handleFailure.accept( + new LongPollingFailedException("Unexpected exception during long polling request", failure)); + } + } + @SuppressWarnings("serial") private class AbortLongPolling extends BoschSHCException { } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandler.java index 1d584c9ef..af55dc182 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/climatecontrol/ClimateControlHandler.java @@ -12,8 +12,7 @@ */ package org.openhab.binding.boschshc.internal.devices.climatecontrol; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SETPOINT_TEMPERATURE; -import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_TEMPERATURE; +import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.*; import java.util.List; @@ -29,20 +28,24 @@ import org.openhab.core.library.unit.SIUnits; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A virtual device which controls up to six Bosch Smart Home radiator thermostats in a room. - * + * * @author Christian Oeing - Initial contribution */ @NonNullByDefault public final class ClimateControlHandler extends BoschSHCDeviceHandler { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private RoomClimateControlService roomClimateControlService; /** * Constructor. - * + * * @param thing The Bosch Smart Home device that should be handled. */ public ClimateControlHandler(Thing thing) { @@ -71,7 +74,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler { /** * Updates the channels which are linked to the {@link TemperatureLevelService} of the device. - * + * * @param state Current state of {@link TemperatureLevelService}. */ private void updateChannels(TemperatureLevelServiceState state) { @@ -80,7 +83,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler { /** * Updates the channels which are linked to the {@link RoomClimateControlService} of the device. - * + * * @param state Current state of {@link RoomClimateControlService}. */ private void updateChannels(RoomClimateControlServiceState state) { @@ -89,7 +92,7 @@ public final class ClimateControlHandler extends BoschSHCDeviceHandler { /** * Sets the desired temperature for the device. - * + * * @param quantityType Command which contains the new desired temperature. */ private void updateSetpointTemperature(QuantityType quantityType) { diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java index 3ad8b502e..a1875aecc 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/devices/shuttercontrol/ShutterControlHandler.java @@ -28,10 +28,12 @@ import org.openhab.core.library.types.UpDownType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Control of your shutter to take any position you desire. - * + * * @author Christian Oeing - Initial contribution */ @NonNullByDefault @@ -49,6 +51,8 @@ public class ShutterControlHandler extends BoschSHCDeviceHandler { } } + private final Logger logger = LoggerFactory.getLogger(getClass()); + private ShutterControlService shutterControlService; public ShutterControlHandler(Thing thing) { diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java index 2759c5510..a647b1d6a 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/discovery/ThingDiscoveryService.java @@ -12,7 +12,11 @@ */ package org.openhab.binding.boschshc.internal.discovery; -import java.util.*; +import java.util.AbstractMap; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -139,9 +143,9 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T @Override public void setThingHandler(@Nullable ThingHandler handler) { - if (handler instanceof BridgeHandler) { + if (handler instanceof BridgeHandler bridgeHandler) { logger.trace("Set bridge handler {}", handler); - shcBridgeHandler = (BridgeHandler) handler; + shcBridgeHandler = bridgeHandler; } } @@ -215,8 +219,9 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T } private String getNiceName(String name, String roomName) { - if (!name.startsWith("-")) + if (!name.startsWith("-")) { return name; + } // convert "-IntrusionDetectionSystem-" into "Intrusion Detection System" // convert "-RoomClimateControl-" into "Room Climate Control myRoomName" diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java new file mode 100644 index 000000000..efa652a50 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/GsonUtils.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.serialization; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Utilities for JSON serialization and deserialization using Google Gson. + * + * @author David Pace - Initial contribution + * + */ +@NonNullByDefault +public final class GsonUtils { + private GsonUtils() { + // Utility Class + } + + /** + * The default Gson instance to be used for serialization and deserialization. + *

+ * This instance does not serialize or deserialize fields named logger. + */ + public static final Gson DEFAULT_GSON_INSTANCE = new GsonBuilder() + .addSerializationExclusionStrategy(new LoggerExclusionStrategy()) + .addDeserializationExclusionStrategy(new LoggerExclusionStrategy()).create(); +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java new file mode 100644 index 000000000..88e8824d0 --- /dev/null +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/serialization/LoggerExclusionStrategy.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.boschshc.internal.serialization; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +/** + * A GSON exclusion strategy that prevents loggers from being serialized and deserialized. + * + * @author David Pace - Initial contribution + * + */ +@NonNullByDefault +public class LoggerExclusionStrategy implements ExclusionStrategy { + + @Override + public boolean shouldSkipField(@NonNullByDefault({}) FieldAttributes f) { + return "logger".equalsIgnoreCase(f.getName()); + } + + @Override + public boolean shouldSkipClass(@NonNullByDefault({}) Class clazz) { + return false; + } +} diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java index 71a02181d..21498e6fa 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/BoschSHCSystemService.java @@ -27,23 +27,23 @@ import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; *

* The endpoints to retrieve system states are different from the ones for physical devices, i.e. they do not follow the * pattern - * + * *

  * https://{IP}:8444/smarthome/devices/{deviceId}/services/{serviceName}/state
  * 
- * + * * Instead, system services have endpoints like - * + * *
  * /intrusion/states/system
  * 
- * + * *

* The services of the devices and their official APIs can be found * here. * * @param type used for representing the service state - * + * * @author David Pace - Initial contribution */ @NonNullByDefault @@ -53,7 +53,7 @@ public abstract class BoschSHCSystemService /** * Constructs a system service instance. - * + * * @param serviceName name of the service, such as intrusionDetectionService * @param stateClass the class representing states of the system * @param endpoint the part of the URL after https://{IP}:8444/smarthome/, e.g. diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java index ffe23619d..488356b30 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceState.java @@ -13,10 +13,10 @@ package org.openhab.binding.boschshc.internal.services.dto; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.boschshc.internal.serialization.GsonUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.annotations.SerializedName; @@ -27,15 +27,7 @@ import com.google.gson.annotations.SerializedName; */ public class BoschSHCServiceState { - /** - * gson instance to convert a class to json string and back. - */ - private static final Gson GSON = new Gson(); - - /** - * Logger marked as transient to exclude the logger from JSON serialization. - */ - private final transient Logger logger = LoggerFactory.getLogger(BoschSHCServiceState.class); + private final Logger logger = LoggerFactory.getLogger(getClass()); /** * State type. Initialized when instance is created. @@ -70,7 +62,7 @@ public class BoschSHCServiceState { public static @Nullable TState fromJson(String json, Class stateClass) { - var state = GSON.fromJson(json, stateClass); + var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass); if (state == null || !state.isValid()) { return null; } @@ -80,7 +72,7 @@ public class BoschSHCServiceState { public static @Nullable TState fromJson(JsonElement json, Class stateClass) { - var state = GSON.fromJson(json, stateClass); + var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass); if (state == null || !state.isValid()) { return null; } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java index d931211f6..b79408e17 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/humiditylevel/dto/HumidityLevelServiceState.java @@ -14,7 +14,9 @@ package org.openhab.binding.boschshc.internal.services.humiditylevel.dto; import javax.measure.quantity.Dimensionless; +import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.binding.boschshc.internal.services.humiditylevel.HumidityLevelService; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.Units; import org.openhab.core.types.State; @@ -36,6 +38,6 @@ public class HumidityLevelServiceState extends BoschSHCServiceState { public double humidity; public State getHumidityState() { - return new QuantityType(this.humidity, Units.PERCENT); + return new QuantityType<@NonNull Dimensionless>(this.humidity, Units.PERCENT); } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/roomclimatecontrol/dto/RoomClimateControlServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/roomclimatecontrol/dto/RoomClimateControlServiceState.java index 525d8c7b8..f4b534493 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/roomclimatecontrol/dto/RoomClimateControlServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/roomclimatecontrol/dto/RoomClimateControlServiceState.java @@ -14,37 +14,39 @@ package org.openhab.binding.boschshc.internal.services.roomclimatecontrol.dto; import javax.measure.quantity.Temperature; +import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; +import org.openhab.binding.boschshc.internal.services.roomclimatecontrol.RoomClimateControlService; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.types.State; /** * State for {@link RoomClimateControlService} to get and set the desired temperature of a room. - * + * * @author Christian Oeing - Initial contribution */ public class RoomClimateControlServiceState extends BoschSHCServiceState { - private static final String TYPE = "climateControlState"; + private static final String CLIMATE_CONTROL_STATE_TYPE = "climateControlState"; public RoomClimateControlServiceState() { - super(TYPE); + super(CLIMATE_CONTROL_STATE_TYPE); } /** * Constructor. - * + * * @param setpointTemperature Desired temperature (in degree celsius). */ public RoomClimateControlServiceState(double setpointTemperature) { - super(TYPE); + super(CLIMATE_CONTROL_STATE_TYPE); this.setpointTemperature = setpointTemperature; } /** * Desired temperature (in degree celsius). - * + * * @apiNote Min: 5.0, Max: 30.0. * @apiNote Can be set in 0.5 steps. */ @@ -52,10 +54,10 @@ public class RoomClimateControlServiceState extends BoschSHCServiceState { /** * Desired temperature state to set for a thing. - * + * * @return Desired temperature state to set for a thing. */ public State getSetpointTemperatureState() { - return new QuantityType(this.setpointTemperature, SIUnits.CELSIUS); + return new QuantityType<@NonNull Temperature>(this.setpointTemperature, SIUnits.CELSIUS); } } diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/smokedetectorcheck/SmokeDetectorCheckState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/smokedetectorcheck/SmokeDetectorCheckState.java index a7426b909..c1f684193 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/smokedetectorcheck/SmokeDetectorCheckState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/smokedetectorcheck/SmokeDetectorCheckState.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** * Possible states for a smoke detector. - * + * * @author Christian Oeing - Initial contribution */ @NonNullByDefault diff --git a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/temperaturelevel/dto/TemperatureLevelServiceState.java b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/temperaturelevel/dto/TemperatureLevelServiceState.java index a5f5a0035..bd29d00f5 100644 --- a/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/temperaturelevel/dto/TemperatureLevelServiceState.java +++ b/bundles/org.openhab.binding.boschshc/src/main/java/org/openhab/binding/boschshc/internal/services/temperaturelevel/dto/TemperatureLevelServiceState.java @@ -14,6 +14,7 @@ package org.openhab.binding.boschshc.internal.services.temperaturelevel.dto; import javax.measure.quantity.Temperature; +import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; @@ -21,7 +22,7 @@ import org.openhab.core.types.State; /** * TemperatureLevel service state. - * + * * @author Christian Oeing - Initial contribution */ public class TemperatureLevelServiceState extends BoschSHCServiceState { @@ -37,10 +38,10 @@ public class TemperatureLevelServiceState extends BoschSHCServiceState { /** * Current temperature state to set for a thing. - * + * * @return Current temperature state to use for a thing. */ public State getTemperatureState() { - return new QuantityType(this.temperature, SIUnits.CELSIUS); + return new QuantityType<@NonNull Temperature>(this.temperature, SIUnits.CELSIUS); } } diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java index 1f77e1741..a6be12174 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/BridgeHandlerTest.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpMethod; @@ -91,7 +92,7 @@ class BridgeHandlerTest { fixture.setCallback(thingHandlerCallback); Configuration bridgeConfiguration = new Configuration(); - Map properties = new HashMap<>(); + Map<@Nullable String, @Nullable Object> properties = new HashMap<>(); properties.put("ipAddress", "localhost"); properties.put("password", "test"); bridgeConfiguration.setProperties(properties); diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResultTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResultTest.java index fb8a3e3c2..71bc6e3a2 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResultTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/devices/bridge/dto/LongPollResultTest.java @@ -16,8 +16,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; - -import com.google.gson.Gson; +import org.openhab.binding.boschshc.internal.serialization.GsonUtils; /** * Unit tests for LongPollResult @@ -26,11 +25,10 @@ import com.google.gson.Gson; */ @NonNullByDefault public class LongPollResultTest { - private final Gson gson = new Gson(); @Test void noResultsForErrorResult() { - LongPollResult longPollResult = gson.fromJson( + LongPollResult longPollResult = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson( "{\"jsonrpc\":\"2.0\", \"error\": { \"code\":-32001, \"message\":\"No subscription with id: e8fei62b0-0\" } }", LongPollResult.class); assertNotNull(longPollResult); diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelTest.java index 04b3c328c..579e8da3c 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/batterylevel/BatteryLevelTest.java @@ -15,8 +15,10 @@ package org.openhab.binding.boschshc.internal.services.batterylevel; import static org.junit.jupiter.api.Assertions.*; import java.util.ArrayList; +import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Test; import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData; import org.openhab.binding.boschshc.internal.devices.bridge.dto.Fault; @@ -58,7 +60,7 @@ class BatteryLevelTest { deviceServiceData.faults = faults; assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData)); - ArrayList entries = new ArrayList<>(); + List<@Nullable Fault> entries = new ArrayList<>(); faults.entries = entries; assertSame(BatteryLevel.OK, BatteryLevel.fromDeviceServiceData(deviceServiceData)); diff --git a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java index dd7804135..f0f1a29a5 100644 --- a/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java +++ b/bundles/org.openhab.binding.boschshc/src/test/java/org/openhab/binding/boschshc/internal/services/dto/BoschSHCServiceStateTest.java @@ -16,8 +16,8 @@ import static org.junit.jupiter.api.Assertions.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; +import org.openhab.binding.boschshc.internal.serialization.GsonUtils; -import com.google.gson.Gson; import com.google.gson.JsonObject; /** @@ -49,18 +49,19 @@ class TestState2 extends BoschSHCServiceState { */ @NonNullByDefault public class BoschSHCServiceStateTest { - private final Gson gson = new Gson(); @Test public void fromJsonNullStateForDifferentType() { - var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"differentState\"}", JsonObject.class), + var state = BoschSHCServiceState.fromJson( + GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"differentState\"}", JsonObject.class), TestState.class); assertEquals(null, state); } @Test public void fromJsonStateObjectForValidJson() { - var state = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), + var state = BoschSHCServiceState.fromJson( + GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class); assertNotEquals(null, state); } @@ -70,8 +71,11 @@ public class BoschSHCServiceStateTest { */ @Test public void fromJsonStateObjectForValidJsonAfterOtherState() { - BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState\"}", JsonObject.class), TestState.class); - var state2 = BoschSHCServiceState.fromJson(gson.fromJson("{\"@type\":\"testState2\"}", JsonObject.class), + BoschSHCServiceState.fromJson( + GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState\"}", JsonObject.class), + TestState.class); + var state2 = BoschSHCServiceState.fromJson( + GsonUtils.DEFAULT_GSON_INSTANCE.fromJson("{\"@type\":\"testState2\"}", JsonObject.class), TestState2.class); assertNotEquals(null, state2); }