[pushover] Improved exception handling (#12023)

* Improved exception handling

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2022-01-16 18:07:36 +01:00 committed by GitHub
parent b0765271d3
commit 8b3bb313eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 210 deletions

View File

@ -31,4 +31,10 @@ public class PushoverBindingConstants {
public static final String DEFAULT_SOUND = "default"; public static final String DEFAULT_SOUND = "default";
public static final String DEFAULT_TITLE = "openHAB"; public static final String DEFAULT_TITLE = "openHAB";
public static final String TEXT_OFFLINE_COMMUNICATION_ERROR = "@text/offline.communication-error";
public static final String TEXT_OFFLINE_CONF_ERROR_MISSING_APIKEY = "@text/offline.conf-error-missing-apikey";
public static final String TEXT_OFFLINE_CONF_ERROR_MISSING_USER = "@text/offline.conf-error-missing-user";
public static final String TEXT_OFFLINE_CONF_ERROR_UNKNOWN = "@text/offline.conf-error-unknown";
public static final String TEXT_ERROR_SKIP_SENDING_MESSAGE = "@text/error.skip-sending-message";
} }

View File

@ -12,9 +12,10 @@
*/ */
package org.openhab.binding.pushover.internal.connection; package org.openhab.binding.pushover.internal.connection;
import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -32,13 +33,16 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration; import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration;
import org.openhab.binding.pushover.internal.dto.Sound; import org.openhab.binding.pushover.internal.dto.Sound;
import org.openhab.core.cache.ExpiringCacheMap; import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.i18n.CommunicationException;
import org.openhab.core.i18n.ConfigurationException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
/** /**
* The {@link PushoverAPIConnection} is responsible for handling the connections to Pushover Messages API. * The {@link PushoverAPIConnection} is responsible for handling the connections to Pushover Messages API.
@ -48,63 +52,75 @@ import com.google.gson.JsonParser;
@NonNullByDefault @NonNullByDefault
public class PushoverAPIConnection { public class PushoverAPIConnection {
private static final String JSON_VALUE_ERRORS = "errors";
private static final String JSON_VALUE_RECEIPT = "receipt";
private static final String JSON_VALUE_SOUNDS = "sounds";
private static final String JSON_VALUE_STATUS = "status";
private final Logger logger = LoggerFactory.getLogger(PushoverAPIConnection.class); private final Logger logger = LoggerFactory.getLogger(PushoverAPIConnection.class);
private static final String VALIDATE_URL = "https://api.pushover.net/1/users/validate.json"; private static final String VALIDATE_URL = "https://api.pushover.net/1/users/validate.json";
private static final String MESSAGE_URL = "https://api.pushover.net/1/messages.json"; private static final String MESSAGE_URL = "https://api.pushover.net/1/messages.json";
private static final String CANCEL_MESSAGE_URL = "https://api.pushover.net/1/receipts/{receipt}/cancel.json"; private static final String CANCEL_MESSAGE_URL = "https://api.pushover.net/1/receipts/%s/cancel.json";
private static final String SOUNDS_URL = "https://api.pushover.net/1/sounds.json"; private static final String SOUNDS_URL = "https://api.pushover.net/1/sounds.json";
private final HttpClient httpClient; private final HttpClient httpClient;
private final PushoverAccountConfiguration config; private final PushoverAccountConfiguration config;
private final ExpiringCacheMap<String, String> cache = new ExpiringCacheMap<>(TimeUnit.DAYS.toMillis(1)); private final ExpiringCache<List<Sound>> cache = new ExpiringCache<>(TimeUnit.DAYS.toMillis(1),
this::getSoundsFromSource);
public PushoverAPIConnection(HttpClient httpClient, PushoverAccountConfiguration config) { public PushoverAPIConnection(HttpClient httpClient, PushoverAccountConfiguration config) {
this.httpClient = httpClient; this.httpClient = httpClient;
this.config = config; this.config = config;
} }
public boolean validateUser() throws PushoverCommunicationException, PushoverConfigurationException { public boolean validateUser() throws CommunicationException, ConfigurationException {
return getMessageStatus( return getMessageStatus(
post(VALIDATE_URL, PushoverMessageBuilder.getInstance(config.apikey, config.user).build())); post(VALIDATE_URL, PushoverMessageBuilder.getInstance(config.apikey, config.user).build()));
} }
public boolean sendMessage(PushoverMessageBuilder message) public boolean sendMessage(PushoverMessageBuilder message) throws CommunicationException, ConfigurationException {
throws PushoverCommunicationException, PushoverConfigurationException {
return getMessageStatus(post(MESSAGE_URL, message.build())); return getMessageStatus(post(MESSAGE_URL, message.build()));
} }
public String sendPriorityMessage(PushoverMessageBuilder message) public String sendPriorityMessage(PushoverMessageBuilder message)
throws PushoverCommunicationException, PushoverConfigurationException { throws CommunicationException, ConfigurationException {
final JsonObject json = JsonParser.parseString(post(MESSAGE_URL, message.build())).getAsJsonObject(); final JsonObject json = JsonParser.parseString(post(MESSAGE_URL, message.build())).getAsJsonObject();
return getMessageStatus(json) && json.has("receipt") ? json.get("receipt").getAsString() : ""; return getMessageStatus(json) && json.has(JSON_VALUE_RECEIPT) ? json.get(JSON_VALUE_RECEIPT).getAsString() : "";
} }
public boolean cancelPriorityMessage(String receipt) public boolean cancelPriorityMessage(String receipt) throws CommunicationException, ConfigurationException {
throws PushoverCommunicationException, PushoverConfigurationException { return getMessageStatus(post(String.format(CANCEL_MESSAGE_URL, receipt),
return getMessageStatus(post(CANCEL_MESSAGE_URL.replace("{receipt}", receipt),
PushoverMessageBuilder.getInstance(config.apikey, config.user).build())); PushoverMessageBuilder.getInstance(config.apikey, config.user).build()));
} }
public List<Sound> getSounds() throws PushoverCommunicationException, PushoverConfigurationException { public @Nullable List<Sound> getSounds() {
final String localApikey = config.apikey; return cache.getValue();
if (localApikey == null || localApikey.isEmpty()) {
throw new PushoverConfigurationException("@text/offline.conf-error-missing-apikey");
} }
final Map<String, String> params = new HashMap<>(1); private List<Sound> getSoundsFromSource() throws CommunicationException, ConfigurationException {
params.put(PushoverMessageBuilder.MESSAGE_KEY_TOKEN, localApikey); final String localApikey = config.apikey;
if (localApikey == null || localApikey.isBlank()) {
throw new ConfigurationException(TEXT_OFFLINE_CONF_ERROR_MISSING_APIKEY);
}
// TODO do not cache the response, cache the parsed list of sounds try {
final String content = getFromCache(buildURL(SOUNDS_URL, params)); final String content = get(
final JsonObject json = content == null ? null : JsonParser.parseString(content).getAsJsonObject(); buildURL(SOUNDS_URL, Map.of(PushoverMessageBuilder.MESSAGE_KEY_TOKEN, localApikey)));
final JsonObject sounds = json == null || !json.has("sounds") ? null : json.get("sounds").getAsJsonObject(); final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
final JsonObject sounds = json.has(JSON_VALUE_SOUNDS) ? json.get(JSON_VALUE_SOUNDS).getAsJsonObject()
return sounds == null ? List.of() : null;
: sounds.entrySet().stream().map(entry -> new Sound(entry.getKey(), entry.getValue().getAsString())) if (sounds != null) {
return sounds.entrySet().stream()
.map(entry -> new Sound(entry.getKey(), entry.getValue().getAsString()))
.collect(Collectors.toUnmodifiableList()); .collect(Collectors.toUnmodifiableList());
} }
} catch (JsonSyntaxException e) {
// do nothing
}
return List.of();
}
private String buildURL(String url, Map<String, String> requestParams) { private String buildURL(String url, Map<String, String> requestParams) {
return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key))) return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key)))
@ -115,21 +131,16 @@ public class PushoverAPIConnection {
return value == null ? "" : URLEncoder.encode(value, StandardCharsets.UTF_8); return value == null ? "" : URLEncoder.encode(value, StandardCharsets.UTF_8);
} }
private @Nullable String getFromCache(String url) { private String get(String url) throws CommunicationException, ConfigurationException {
return cache.putIfAbsentAndGet(url, () -> get(url));
}
private String get(String url) throws PushoverCommunicationException, PushoverConfigurationException {
return executeRequest(HttpMethod.GET, url, null); return executeRequest(HttpMethod.GET, url, null);
} }
private String post(String url, ContentProvider body) private String post(String url, ContentProvider body) throws CommunicationException, ConfigurationException {
throws PushoverCommunicationException, PushoverConfigurationException {
return executeRequest(HttpMethod.POST, url, body); return executeRequest(HttpMethod.POST, url, body);
} }
private synchronized String executeRequest(HttpMethod httpMethod, String url, @Nullable ContentProvider body) private synchronized String executeRequest(HttpMethod httpMethod, String url, @Nullable ContentProvider body)
throws PushoverCommunicationException, PushoverConfigurationException { throws CommunicationException, ConfigurationException {
logger.trace("Pushover request: {} - URL = '{}'", httpMethod, url); logger.trace("Pushover request: {} - URL = '{}'", httpMethod, url);
try { try {
final Request request = httpClient.newRequest(url).method(httpMethod).timeout(config.timeout, final Request request = httpClient.newRequest(url).method(httpMethod).timeout(config.timeout,
@ -152,35 +163,43 @@ public class PushoverAPIConnection {
return content; return content;
case HttpStatus.BAD_REQUEST_400: case HttpStatus.BAD_REQUEST_400:
logger.debug("Pushover server responded with status code {}: {}", httpStatus, content); logger.debug("Pushover server responded with status code {}: {}", httpStatus, content);
throw new PushoverConfigurationException(getMessageError(content)); throw new ConfigurationException(getMessageError(content));
default: default:
logger.debug("Pushover server responded with status code {}: {}", httpStatus, content); logger.debug("Pushover server responded with status code {}: {}", httpStatus, content);
throw new PushoverCommunicationException(content); throw new CommunicationException(content);
} }
} catch (ExecutionException e) { } catch (ExecutionException e) {
logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e); String message = e.getMessage();
throw new PushoverCommunicationException(e.getLocalizedMessage(), e.getCause()); logger.debug("ExecutionException occurred during execution: {}", message, e);
} catch (InterruptedException | TimeoutException e) { throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message,
logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e); e.getCause());
throw new PushoverCommunicationException(e.getLocalizedMessage()); } catch (TimeoutException e) {
String message = e.getMessage();
logger.debug("TimeoutException occurred during execution: {}", message, e);
throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
String message = e.getMessage();
logger.debug("InterruptedException occurred during execution: {}", message, e);
throw new CommunicationException(message == null ? TEXT_OFFLINE_COMMUNICATION_ERROR : message);
} }
} }
private String getMessageError(String content) { private String getMessageError(String content) {
final JsonObject json = JsonParser.parseString(content).getAsJsonObject(); final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
final JsonElement errorsElement = json.get("errors"); final JsonElement errorsElement = json.get(JSON_VALUE_ERRORS);
if (errorsElement != null && errorsElement.isJsonArray()) { if (errorsElement != null && errorsElement.isJsonArray()) {
return errorsElement.getAsJsonArray().toString(); return errorsElement.getAsJsonArray().toString();
} }
return "@text/offline.conf-error-unknown"; return TEXT_OFFLINE_CONF_ERROR_UNKNOWN;
} }
private boolean getMessageStatus(String content) { private boolean getMessageStatus(String content) {
final JsonObject json = JsonParser.parseString(content).getAsJsonObject(); final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
return json.has("status") ? json.get("status").getAsInt() == 1 : false; return json.has(JSON_VALUE_STATUS) ? json.get(JSON_VALUE_STATUS).getAsInt() == 1 : false;
} }
private boolean getMessageStatus(JsonObject json) { private boolean getMessageStatus(JsonObject json) {
return json.has("status") ? json.get("status").getAsInt() == 1 : false; return json.has(JSON_VALUE_STATUS) ? json.get(JSON_VALUE_STATUS).getAsInt() == 1 : false;
} }
} }

View File

@ -1,62 +0,0 @@
/**
* 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.pushover.internal.connection;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link PushoverCommunicationException} is a configuration exception for the connections to Pushover Messages API.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class PushoverCommunicationException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with null as its detail message.
*/
public PushoverCommunicationException() {
super();
}
/**
* Constructs a new exception with the specified detail message.
*
* @param message Detail message
*/
public PushoverCommunicationException(@Nullable String message) {
super(message);
}
/**
* Constructs a new exception with the specified cause.
*
* @param cause The cause
*/
public PushoverCommunicationException(@Nullable Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
* @param message Detail message
* @param cause The cause
*/
public PushoverCommunicationException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
}

View File

@ -1,61 +0,0 @@
/**
* 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.pushover.internal.connection;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PushoverConfigurationException} is a configuration exception for the connections to Pushover Messages API.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class PushoverConfigurationException extends IllegalArgumentException {
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with null as its detail message.
*/
public PushoverConfigurationException() {
super();
}
/**
* Constructs a new exception with the specified detail message.
*
* @param message Detail message
*/
public PushoverConfigurationException(String message) {
super(message);
}
/**
* Constructs a new exception with the specified cause.
*
* @param cause The cause
*/
public PushoverConfigurationException(Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
* @param message Detail message
* @param cause The cause
*/
public PushoverConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.pushover.internal.connection; package org.openhab.binding.pushover.internal.connection;
import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -25,6 +27,8 @@ import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.util.MultiPartContentProvider; import org.eclipse.jetty.client.util.MultiPartContentProvider;
import org.eclipse.jetty.client.util.PathContentProvider; import org.eclipse.jetty.client.util.PathContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider; import org.eclipse.jetty.client.util.StringContentProvider;
import org.openhab.core.i18n.CommunicationException;
import org.openhab.core.i18n.ConfigurationException;
import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.RawType; import org.openhab.core.library.types.RawType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -83,19 +87,19 @@ public class PushoverMessageBuilder {
private boolean html = false; private boolean html = false;
private boolean monospace = false; private boolean monospace = false;
private PushoverMessageBuilder(String apikey, String user) throws PushoverConfigurationException { private PushoverMessageBuilder(String apikey, String user) throws ConfigurationException {
body.addFieldPart(MESSAGE_KEY_TOKEN, new StringContentProvider(apikey), null); body.addFieldPart(MESSAGE_KEY_TOKEN, new StringContentProvider(apikey), null);
body.addFieldPart(MESSAGE_KEY_USER, new StringContentProvider(user), null); body.addFieldPart(MESSAGE_KEY_USER, new StringContentProvider(user), null);
} }
public static PushoverMessageBuilder getInstance(@Nullable String apikey, @Nullable String user) public static PushoverMessageBuilder getInstance(@Nullable String apikey, @Nullable String user)
throws PushoverConfigurationException { throws ConfigurationException {
if (apikey == null || apikey.isEmpty()) { if (apikey == null || apikey.isBlank()) {
throw new PushoverConfigurationException("@text/offline.conf-error-missing-apikey"); throw new ConfigurationException(TEXT_OFFLINE_CONF_ERROR_MISSING_APIKEY);
} }
if (user == null || user.isEmpty()) { if (user == null || user.isBlank()) {
throw new PushoverConfigurationException("@text/offline.conf-error-missing-user"); throw new ConfigurationException(TEXT_OFFLINE_CONF_ERROR_MISSING_USER);
} }
return new PushoverMessageBuilder(apikey, user); return new PushoverMessageBuilder(apikey, user);
@ -166,7 +170,7 @@ public class PushoverMessageBuilder {
return this; return this;
} }
public ContentProvider build() throws PushoverCommunicationException { public ContentProvider build() throws CommunicationException {
if (message != null) { if (message != null) {
if (message.length() > MAX_MESSAGE_LENGTH) { if (message.length() > MAX_MESSAGE_LENGTH) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
@ -279,27 +283,24 @@ public class PushoverMessageBuilder {
return body; return body;
} }
private Path createTempFile(byte[] data) throws PushoverCommunicationException { private Path createTempFile(byte[] data) throws CommunicationException {
try { try {
Path tmpFile = Files.createTempFile("pushover-", ".tmp"); Path tmpFile = Files.createTempFile("pushover-", ".tmp");
return Files.write(tmpFile, data); return Files.write(tmpFile, data);
} catch (IOException e) { } catch (IOException e) {
logger.debug("IOException occurred while creating temp file - skip sending message: {}", logger.debug("IOException occurred while creating temp file - skip sending the message: {}", e.getMessage(),
e.getLocalizedMessage(), e); e);
throw new PushoverCommunicationException( throw new CommunicationException(TEXT_ERROR_SKIP_SENDING_MESSAGE, e.getCause(), e.getLocalizedMessage());
String.format("Skip sending the message: %s", e.getLocalizedMessage()), e);
} }
} }
private void addFilePart(Path path, @Nullable String contentType) throws PushoverCommunicationException { private void addFilePart(Path path, @Nullable String contentType) throws CommunicationException {
try { try {
body.addFilePart(MESSAGE_KEY_ATTACHMENT, path.toFile().getName(), body.addFilePart(MESSAGE_KEY_ATTACHMENT, path.toFile().getName(),
new PathContentProvider(contentType == null ? DEFAULT_CONTENT_TYPE : contentType, path), null); new PathContentProvider(contentType == null ? DEFAULT_CONTENT_TYPE : contentType, path), null);
} catch (IOException e) { } catch (IOException e) {
logger.debug("IOException occurred while adding content - skip sending message: {}", logger.debug("IOException occurred while adding content - skip sending the message: {}", e.getMessage(), e);
e.getLocalizedMessage(), e); throw new CommunicationException(TEXT_ERROR_SKIP_SENDING_MESSAGE, e.getCause(), e.getLocalizedMessage());
throw new PushoverCommunicationException(
String.format("Skip sending the message: %s", e.getLocalizedMessage()), e);
} }
} }
} }

View File

@ -12,7 +12,7 @@
*/ */
package org.openhab.binding.pushover.internal.handler; package org.openhab.binding.pushover.internal.handler;
import static org.openhab.binding.pushover.internal.PushoverBindingConstants.DEFAULT_SOUND; import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -25,10 +25,10 @@ import org.openhab.binding.pushover.internal.actions.PushoverActions;
import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration; import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration;
import org.openhab.binding.pushover.internal.config.PushoverConfigOptionProvider; import org.openhab.binding.pushover.internal.config.PushoverConfigOptionProvider;
import org.openhab.binding.pushover.internal.connection.PushoverAPIConnection; import org.openhab.binding.pushover.internal.connection.PushoverAPIConnection;
import org.openhab.binding.pushover.internal.connection.PushoverCommunicationException;
import org.openhab.binding.pushover.internal.connection.PushoverConfigurationException;
import org.openhab.binding.pushover.internal.connection.PushoverMessageBuilder; import org.openhab.binding.pushover.internal.connection.PushoverMessageBuilder;
import org.openhab.binding.pushover.internal.dto.Sound; import org.openhab.binding.pushover.internal.dto.Sound;
import org.openhab.core.i18n.CommunicationException;
import org.openhab.core.i18n.ConfigurationException;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -69,15 +69,15 @@ public class PushoverAccountHandler extends BaseThingHandler {
boolean configValid = true; boolean configValid = true;
final String apikey = config.apikey; final String apikey = config.apikey;
if (apikey == null || apikey.isEmpty()) { if (apikey == null || apikey.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-missing-apikey"); TEXT_OFFLINE_CONF_ERROR_MISSING_APIKEY);
configValid = false; configValid = false;
} }
final String user = config.user; final String user = config.user;
if (user == null || user.isEmpty()) { if (user == null || user.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-missing-user"); TEXT_OFFLINE_CONF_ERROR_MISSING_USER);
configValid = false; configValid = false;
} }
@ -101,11 +101,16 @@ public class PushoverAccountHandler extends BaseThingHandler {
*/ */
public List<Sound> getSounds() { public List<Sound> getSounds() {
try { try {
return connection != null ? connection.getSounds() : PushoverAccountConfiguration.DEFAULT_SOUNDS; if (connection != null) {
} catch (PushoverCommunicationException e) { List<Sound> sounds = connection.getSounds();
if (sounds != null) {
return sounds;
}
}
} catch (CommunicationException e) {
// do nothing, causing exception is already logged // do nothing, causing exception is already logged
} catch (PushoverConfigurationException e) { } catch (ConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
} }
return PushoverAccountConfiguration.DEFAULT_SOUNDS; return PushoverAccountConfiguration.DEFAULT_SOUNDS;
} }
@ -143,10 +148,10 @@ public class PushoverAccountHandler extends BaseThingHandler {
if (connection != null) { if (connection != null) {
try { try {
return connection.sendMessage(messageBuilder); return connection.sendMessage(messageBuilder);
} catch (PushoverCommunicationException e) { } catch (CommunicationException e) {
// do nothing, causing exception is already logged // do nothing, causing exception is already logged
} catch (PushoverConfigurationException e) { } catch (ConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
} }
return false; return false;
} else { } else {
@ -158,10 +163,10 @@ public class PushoverAccountHandler extends BaseThingHandler {
if (connection != null) { if (connection != null) {
try { try {
return connection.sendPriorityMessage(messageBuilder); return connection.sendPriorityMessage(messageBuilder);
} catch (PushoverCommunicationException e) { } catch (CommunicationException e) {
// do nothing, causing exception is already logged // do nothing, causing exception is already logged
} catch (PushoverConfigurationException e) { } catch (ConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
} }
return ""; return "";
} else { } else {
@ -173,10 +178,10 @@ public class PushoverAccountHandler extends BaseThingHandler {
if (connection != null) { if (connection != null) {
try { try {
return connection.cancelPriorityMessage(receipt); return connection.cancelPriorityMessage(receipt);
} catch (PushoverCommunicationException e) { } catch (CommunicationException e) {
// do nothing, causing exception is already logged // do nothing, causing exception is already logged
} catch (PushoverConfigurationException e) { } catch (ConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
} }
return false; return false;
} else { } else {
@ -189,8 +194,8 @@ public class PushoverAccountHandler extends BaseThingHandler {
try { try {
connection.validateUser(); connection.validateUser();
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} catch (PushoverCommunicationException | PushoverConfigurationException e) { } catch (CommunicationException | ConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getRawMessage());
} }
} }
} }

View File

@ -32,9 +32,11 @@ thing-type.config.pushover.pushover-account.user.description = Your user key or
# user defined messages # user defined messages
offline.communication-error = An unexpected exception occurred during execution.
offline.conf-error-missing-apikey = The 'apikey' parameter must be configured. offline.conf-error-missing-apikey = The 'apikey' parameter must be configured.
offline.conf-error-missing-user = The 'user' parameter must be configured. offline.conf-error-missing-user = The 'user' parameter must be configured.
offline.conf-error-unknown = An unknown error occurred. offline.conf-error-unknown = An unknown error occurred.
error.skip-sending-message = Skip sending the message: {0}.
# actions # actions

View File

@ -37,6 +37,7 @@ import org.openhab.core.thing.binding.ThingHandler;
* *
* @author Christoph Weitkamp - Initial contribution * @author Christoph Weitkamp - Initial contribution
*/ */
@NonNullByDefault
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.WARN) @MockitoSettings(strictness = Strictness.WARN)
public class PushoverActionsTest { public class PushoverActionsTest {
@ -47,7 +48,6 @@ public class PushoverActionsTest {
private static final String URL_TITLE = "Some Link"; private static final String URL_TITLE = "Some Link";
private static final String RECEIPT = "12345"; private static final String RECEIPT = "12345";
@NonNullByDefault
private final ThingActions thingActionsStub = new ThingActions() { private final ThingActions thingActionsStub = new ThingActions() {
@Override @Override
public void setThingHandler(ThingHandler handler) { public void setThingHandler(ThingHandler handler) {
@ -59,9 +59,8 @@ public class PushoverActionsTest {
} }
}; };
private @Mock PushoverAccountHandler mockPushoverAccountHandler; private @NonNullByDefault({}) @Mock PushoverAccountHandler mockPushoverAccountHandler;
private @NonNullByDefault({}) PushoverActions pushoverThingActions;
private PushoverActions pushoverThingActions;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {