[gardena] Fix null annotation issue (and compiler warning) (#12957)

* Fix compiler warning
* Add basic test coverage for DataItem deserialization
* Add full prefixes to attributes variables
* Add missing newlines at end of test payload files
* Add full prefix to attributes variable

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-06-23 09:11:20 +02:00 committed by GitHub
parent 024e15806d
commit 3465e8f30d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 206 additions and 20 deletions

View File

@ -43,10 +43,12 @@ import org.openhab.binding.gardena.internal.model.DataItemDeserializer;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.model.dto.api.CreateWebSocketRequest;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.api.Location;
import org.openhab.binding.gardena.internal.model.dto.api.LocationDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.LocationResponse;
import org.openhab.binding.gardena.internal.model.dto.api.LocationsResponse;
import org.openhab.binding.gardena.internal.model.dto.api.PostOAuth2Response;
import org.openhab.binding.gardena.internal.model.dto.api.WebSocket;
import org.openhab.binding.gardena.internal.model.dto.api.WebSocketCreatedResponse;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommand;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommandRequest;
@ -153,9 +155,14 @@ public class GardenaSmartImpl implements GardenaSmart, GardenaSmartWebSocketList
private void startWebsockets() throws Exception {
for (LocationDataItem location : locationsResponse.data) {
WebSocketCreatedResponse webSocketCreatedResponse = getWebsocketInfo(location.id);
String socketId = id + "-" + location.attributes.name;
Location locationAttributes = location.attributes;
WebSocket webSocketAttributes = webSocketCreatedResponse.data.attributes;
if (locationAttributes == null || webSocketAttributes == null) {
continue;
}
String socketId = id + "-" + locationAttributes.name;
webSockets.put(location.id, new GardenaSmartWebSocket(this, webSocketClient, scheduler,
webSocketCreatedResponse.data.attributes.url, token, socketId, location.id));
webSocketAttributes.url, token, socketId, location.id));
}
}
@ -391,7 +398,10 @@ public class GardenaSmartImpl implements GardenaSmart, GardenaSmartWebSocketList
Thread.sleep(3000);
WebSocketCreatedResponse webSocketCreatedResponse = getWebsocketInfo(socket.getLocationID());
// only restart single socket, do not restart binding
socket.restart(webSocketCreatedResponse.data.attributes.url);
WebSocket webSocketAttributes = webSocketCreatedResponse.data.attributes;
if (webSocketAttributes != null) {
socket.restart(webSocketAttributes.url);
}
} catch (Exception ex) {
// restart binding on error
logger.warn("Restarting GardenaSmart Webservice failed ({}): {}, restarting binding", socket.getSocketID(),

View File

@ -27,6 +27,7 @@ import org.openhab.binding.gardena.internal.GardenaSmartEventListener;
import org.openhab.binding.gardena.internal.exception.GardenaDeviceNotFoundException;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.dto.Device;
import org.openhab.binding.gardena.internal.model.dto.api.CommonService;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.command.GardenaCommand;
import org.openhab.binding.gardena.internal.model.dto.command.MowerCommand;
@ -284,7 +285,9 @@ public class GardenaThingHandler extends BaseThingHandler {
ThingStatus newStatus = ThingStatus.ONLINE;
ThingStatusDetail newDetail = ThingStatusDetail.NONE;
if (!CONNECTION_STATUS_ONLINE.equals(device.common.attributes.rfLinkState.value)) {
CommonService commonServiceAttributes = device.common.attributes;
if (commonServiceAttributes == null
|| !CONNECTION_STATUS_ONLINE.equals(commonServiceAttributes.rfLinkState.value)) {
newStatus = ThingStatus.OFFLINE;
newDetail = ThingStatusDetail.COMMUNICATION_ERROR;
}

View File

@ -19,9 +19,11 @@ import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.gardena.internal.exception.GardenaException;
import org.openhab.binding.gardena.internal.model.dto.api.CommonService;
import org.openhab.binding.gardena.internal.model.dto.api.CommonServiceDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.api.DeviceDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.Location;
import org.openhab.binding.gardena.internal.model.dto.api.LocationDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.MowerServiceDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.PowerSocketServiceDataItem;
@ -82,8 +84,10 @@ public class Device {
*/
public void evaluateDeviceType() {
if (deviceType == null) {
if (common.attributes.modelType.value.toLowerCase().startsWith(DEVICE_TYPE_PREFIX)) {
String modelType = common.attributes.modelType.value.toLowerCase();
CommonService commonServiceAttributes = common.attributes;
if (commonServiceAttributes != null
&& commonServiceAttributes.modelType.value.toLowerCase().startsWith(DEVICE_TYPE_PREFIX)) {
String modelType = commonServiceAttributes.modelType.value.toLowerCase();
modelType = modelType.substring(14);
deviceType = modelType.replace(" ", "_");
} else {
@ -111,8 +115,9 @@ public class Device {
// ignore
} else if (dataItem instanceof LocationDataItem) {
LocationDataItem locationDataItem = (LocationDataItem) dataItem;
if (locationDataItem.attributes != null) {
location = locationDataItem.attributes.name;
Location locationAttributes = locationDataItem.attributes;
if (locationAttributes != null) {
location = locationAttributes.name;
}
} else if (dataItem instanceof CommonServiceDataItem) {
common = (CommonServiceDataItem) dataItem;

View File

@ -12,11 +12,13 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class CommonServiceDataItem extends DataItem<CommonService> {
public class CommonServiceDataItem extends DataItem<@NonNull CommonService> {
}

View File

@ -12,11 +12,13 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class CreateWebSocketDataItem extends DataItem<CreateWebSocket> {
public class CreateWebSocketDataItem extends DataItem<@NonNull CreateWebSocket> {
}

View File

@ -12,12 +12,15 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class CreateWebSocketRequest {
public CreateWebSocketDataItem data;

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gardena.internal.util.StringUtils;
/**
@ -20,7 +22,7 @@ import org.openhab.binding.gardena.internal.util.StringUtils;
* @author Gerhard Riegler - Initial contribution
*/
public class DataItem<T> {
public class DataItem<@NonNull T> {
public String id;
public String type;
@ -28,5 +30,5 @@ public class DataItem<T> {
return StringUtils.substringBeforeLast(id, ":");
}
public T attributes;
public @Nullable T attributes;
}

View File

@ -12,11 +12,13 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class DeviceDataItem extends DataItem<Void> {
public class DeviceDataItem extends DataItem<@NonNull Void> {
}

View File

@ -12,11 +12,13 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class LocationDataItem extends DataItem<Location> {
public class LocationDataItem extends DataItem<@NonNull Location> {
}

View File

@ -12,11 +12,13 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class MowerServiceDataItem extends DataItem<MowerService> {
public class MowerServiceDataItem extends DataItem<@NonNull MowerService> {
}

View File

@ -12,10 +12,12 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class PowerSocketServiceDataItem extends DataItem<PowerSocketService> {
public class PowerSocketServiceDataItem extends DataItem<@NonNull PowerSocketService> {
}

View File

@ -12,10 +12,12 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SensorServiceDataItem extends DataItem<SensorService> {
public class SensorServiceDataItem extends DataItem<@NonNull SensorService> {
}

View File

@ -12,10 +12,12 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveServiceDataItem extends DataItem<ValveService> {
public class ValveServiceDataItem extends DataItem<@NonNull ValveService> {
}

View File

@ -12,10 +12,12 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class ValveSetServiceDataItem extends DataItem<ValveSetService> {
public class ValveSetServiceDataItem extends DataItem<@NonNull ValveSetService> {
}

View File

@ -12,10 +12,12 @@
*/
package org.openhab.binding.gardena.internal.model.dto.api;
import org.eclipse.jdt.annotation.NonNull;
/**
* Represents a Gardena object that is sent via the Gardena API.
*
* @author Gerhard Riegler - Initial contribution
*/
public class WebSocketDataItem extends DataItem<WebSocket> {
public class WebSocketDataItem extends DataItem<@NonNull WebSocket> {
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2022 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.gardena;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.gardena.internal.model.DataItemDeserializer;
import org.openhab.binding.gardena.internal.model.dto.api.DataItem;
import org.openhab.binding.gardena.internal.model.dto.api.DeviceDataItem;
import org.openhab.binding.gardena.internal.model.dto.api.SensorService;
import org.openhab.binding.gardena.internal.model.dto.api.SensorServiceDataItem;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Tests for {@link DataItem} deserialization.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class DataItemTest {
private Gson gson = new GsonBuilder().registerTypeAdapter(DataItem.class, new DataItemDeserializer()).create();
public <T> T getObjectFromJson(String filename, Class<T> clazz) throws IOException {
try (InputStream inputStream = DataItemTest.class.getResourceAsStream(filename)) {
if (inputStream == null) {
throw new IOException("inputstream is null");
}
byte[] bytes = inputStream.readAllBytes();
if (bytes == null) {
throw new IOException("Resulting byte-array empty");
}
String json = new String(bytes, StandardCharsets.UTF_8);
return Objects.requireNonNull(gson.fromJson(json, clazz));
}
}
@Test
public void sensorServiceWellformed() throws IOException {
DataItem<?> dataItem = getObjectFromJson("SensorServiceDataItem.json", DataItem.class);
assertInstanceOf(SensorServiceDataItem.class, dataItem);
assertEquals("SENSOR", dataItem.type);
SensorService attributes = ((SensorServiceDataItem) dataItem).attributes;
assertNotNull(attributes);
assertEquals(55, attributes.soilHumidity.value);
}
@Test
public void sensorServiceNoAttributes() throws IOException {
DataItem<?> dataItem = getObjectFromJson("SensorServiceDataItemNoAttributes.json", DataItem.class);
assertInstanceOf(SensorServiceDataItem.class, dataItem);
assertEquals("SENSOR", dataItem.type);
assertNull((SensorServiceDataItem) dataItem.attributes);
}
@Test
public void device() throws IOException {
DataItem<?> dataItem = getObjectFromJson("DeviceDataItem.json", DataItem.class);
assertInstanceOf(DeviceDataItem.class, dataItem);
assertEquals("DEVICE", dataItem.type);
assertNull((SensorServiceDataItem) dataItem.attributes);
}
}

View File

@ -0,0 +1,24 @@
{
"id": "0162efc2-0b62-4983-aa1d-d72442008b7c",
"type": "DEVICE",
"relationships": {
"location": {
"data": {
"id": "d01b4d00-945a-4639-bf5a-f73d2030dfe0",
"type": "LOCATION"
}
},
"services": {
"data": [
{
"id": "0162efc2-0b62-4983-aa1d-d72442008b7c",
"type": "SENSOR"
},
{
"id": "0162efc2-0b62-4983-aa1d-d72442008b7c",
"type": "COMMON"
}
]
}
}
}

View File

@ -0,0 +1,22 @@
{
"id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
"type": "SENSOR",
"relationships": {
"device": {
"data": {
"id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
"type": "DEVICE"
}
}
},
"attributes": {
"soilHumidity": {
"value": 55,
"timestamp": "2022-06-19T09:30:55.546+00:00"
},
"soilTemperature": {
"value": 18,
"timestamp": "2022-06-19T09:30:55.747+00:00"
}
}
}

View File

@ -0,0 +1,12 @@
{
"id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
"type": "SENSOR",
"relationships": {
"device": {
"data": {
"id": "638e82cd-774c-4f34-be10-2eec41d1c0a6",
"type": "DEVICE"
}
}
}
}