From 51af453f8f0194a9824910750fc9e3f340337924 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sat, 26 Sep 2020 17:45:21 +0200 Subject: [PATCH 1/5] Migration of Pushover OH1 action to OH3 binding Signed-off-by: Christoph Weitkamp --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.pushover/NOTICE | 13 + .../org.openhab.binding.pushover/README.md | 64 +++++ bundles/org.openhab.binding.pushover/pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + .../internal/PushoverBindingConstants.java | 36 +++ .../internal/actions/PushoverActions.java | 216 ++++++++++++++ .../config/PushoverAccountConfiguration.java | 34 +++ .../connection/PushoverAPIConnection.java | 229 +++++++++++++++ .../PushoverCommunicationException.java | 61 ++++ .../PushoverConfigurationException.java | 61 ++++ .../connection/PushoverMessageBuilder.java | 267 ++++++++++++++++++ .../binding/pushover/internal/dto/Sound.java | 28 ++ .../factory/PushoverHandlerFactory.java | 69 +++++ .../handler/PushoverAccountHandler.java | 182 ++++++++++++ .../main/resources/OH-INF/binding/binding.xml | 10 + .../main/resources/OH-INF/config/config.xml | 79 ++++++ .../resources/OH-INF/i18n/pushover.properties | 52 ++++ .../OH-INF/i18n/pushover_de.properties | 75 +++++ .../resources/OH-INF/thing/thing-types.xml | 16 ++ .../internal/actions/PushoverActionsTest.java | 235 +++++++++++++++ bundles/pom.xml | 1 + 23 files changed, 1760 insertions(+) create mode 100644 bundles/org.openhab.binding.pushover/NOTICE create mode 100644 bundles/org.openhab.binding.pushover/README.md create mode 100644 bundles/org.openhab.binding.pushover/pom.xml create mode 100644 bundles/org.openhab.binding.pushover/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverAccountConfiguration.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverConfigurationException.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java create mode 100644 bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties create mode 100644 bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties create mode 100644 bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java diff --git a/CODEOWNERS b/CODEOWNERS index 45dfce187bfa5..e678c1e00fa3d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -199,6 +199,7 @@ /bundles/org.openhab.binding.powermax/ @lolodomo /bundles/org.openhab.binding.pulseaudio/ @peuter /bundles/org.openhab.binding.pushbullet/ @hakan42 +/bundles/org.openhab.binding.pushover/ @cweitkamp /bundles/org.openhab.binding.radiothermostat/ @mlobstein /bundles/org.openhab.binding.regoheatpump/ @crnjan /bundles/org.openhab.binding.revogi/ @andibraeu diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index a64b92e1ca5c5..c5527e028dfd9 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -986,6 +986,11 @@ org.openhab.binding.pushbullet ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.pushover + ${project.version} + org.openhab.addons.bundles org.openhab.binding.radiothermostat diff --git a/bundles/org.openhab.binding.pushover/NOTICE b/bundles/org.openhab.binding.pushover/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.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/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.pushover/README.md b/bundles/org.openhab.binding.pushover/README.md new file mode 100644 index 0000000000000..0fed63b051e08 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/README.md @@ -0,0 +1,64 @@ +# Pushover Binding + +_Give some details about what this binding is meant for - a protocol, system, specific device._ + +_If possible, provide some resources like pictures, a YouTube video, etc. to give an impression of what can be done with this binding. You can place such resources into a `doc` folder next to this README.md._ + +## Supported Things + +_Please describe the different supported things / devices within this section._ +_Which different types are supported, which models were tested etc.?_ +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/ESH-INF/thing``` of your binding._ + +## Discovery + +_Describe the available auto-discovery features here. Mention for what it works and what needs to be kept in mind when using it._ + +## Binding Configuration + +_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it. In this section, you should link to this file and provide some information about the options. The file could e.g. look like:_ + +``` +# Configuration for the Philips Hue Binding +# +# Default secret key for the pairing of the Philips Hue Bridge. +# It has to be between 10-40 (alphanumeric) characters +# This may be changed by the user for security reasons. +secret=openHABSecret +``` + +_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/ESH-INF/binding``` of your binding._ + +_If your binding does not offer any generic configurations, you can remove this section completely._ + +## Thing Configuration + +_Describe what is needed to manually configure a thing, either through the (Paper) UI or via a thing-file. This should be mainly about its mandatory and optional configuration parameters. A short example entry for a thing file can help!_ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/ESH-INF/thing``` of your binding._ + +## Channels + +_Here you should provide information about available channel types, what their meaning is and how they can be used._ + +_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/ESH-INF/thing``` of your binding._ + +| channel | type | description | +|----------|--------|------------------------------| +| control | Switch | This is the control channel | + +## Full Example + + + +### Actions + +TODO + +demo.rules: + +```java +val actions = getActions("pushover", "pushover:pushover-account:account") +// send HTML message +actions.sendHtmlMessage("Hello World!", "openHAB") +``` diff --git a/bundles/org.openhab.binding.pushover/pom.xml b/bundles/org.openhab.binding.pushover/pom.xml new file mode 100644 index 0000000000000..fbaa79eb07ec2 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.0.0-SNAPSHOT + + + org.openhab.binding.pushover + + openHAB Add-ons :: Bundles :: Pushover Binding + + diff --git a/bundles/org.openhab.binding.pushover/src/main/feature/feature.xml b/bundles/org.openhab.binding.pushover/src/main/feature/feature.xml new file mode 100644 index 0000000000000..e3d87f0545b51 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.pushover/${project.version} + + diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java new file mode 100644 index 0000000000000..2b4f252f63d16 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2020 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link PushoverBindingConstants} class defines common constants, which are used across the whole binding. + * + * @author Christoph Weitkamp - Initial contribution + */ +@NonNullByDefault +public class PushoverBindingConstants { + + private static final String BINDING_ID = "pushover"; + + public static final ThingTypeUID PUSHOVER_ACCOUNT = new ThingTypeUID(BINDING_ID, "pushover-account"); + public static final ThingTypeUID PUSHOVER_GROUP = new ThingTypeUID(BINDING_ID, "pushover-group"); + public static final ThingTypeUID PUSHOVER_USER = new ThingTypeUID(BINDING_ID, "pushover-user"); + + public static final String CONFIG_SOUND = "sound"; + + public static final String DEFAULT_SOUND = "default"; + public static final String DEFAULT_TITLE = "openHAB"; +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java new file mode 100644 index 0000000000000..0414a07819d45 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2010-2020 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.actions; + +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.DEFAULT_TITLE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pushover.internal.connection.PushoverMessageBuilder; +import org.openhab.binding.pushover.internal.handler.PushoverAccountHandler; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.ActionOutput; +import org.openhab.core.automation.annotation.RuleAction; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingActionsScope; +import org.openhab.core.thing.binding.ThingHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Some automation actions to be used with a {@link PushoverAccountHandler}. + * + * @author Christoph Weitkamp - Initial contribution + */ +@ThingActionsScope(name = "pushover") +@NonNullByDefault +public class PushoverActions implements ThingActions { + + private final Logger logger = LoggerFactory.getLogger(PushoverActions.class); + + private @NonNullByDefault({}) PushoverAccountHandler accountHandler; + + @RuleAction(label = "@text/sendMessageActionLabel", description = "@text/sendMessageActionDescription") + public @ActionOutput(name = "sent", label = "@text/sendMessageActionOutputLabel", description = "@text/sendMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendMessage( + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) { + logger.trace("ThingAction 'sendMessage' called with value(s): message='{}', title='{}'", message, title); + return send(getDefaultPushoverMessageBuilder(message), title); + } + + public static Boolean sendMessage(ThingActions actions, String message, @Nullable String title) { + return ((PushoverActions) actions).sendMessage(message, title); + } + + @RuleAction(label = "@text/sendURLMessageActionLabel", description = "@text/sendURLMessageActionDescription") + public @ActionOutput(name = "sent", label = "@text/sendMessageActionOutputLabel", description = "@text/sendMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendURLMessage( + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title, + @ActionInput(name = "url", label = "@text/sendMessageActionInputURLLabel", description = "@text/sendMessageActionInputURLDescription", type = "java.lang.String", required = true) String url, + @ActionInput(name = "urlTitle", label = "@text/sendMessageActionInputURLTitleLabel", description = "@text/sendMessageActionInputURLTitleDescription", type = "java.lang.String") @Nullable String urlTitle) { + logger.trace( + "ThingAction 'sendURLMessage' called with value(s): message='{}', url='{}', title='{}', urlTitle='{}'", + message, url, title, urlTitle); + if (url == null) { + throw new IllegalArgumentException("Skip sending message as 'url' is null."); + } + + PushoverMessageBuilder builder = getDefaultPushoverMessageBuilder(message).withUrl(url); + if (urlTitle != null) { + builder.withUrl(urlTitle); + } + return send(builder, title); + } + + public static Boolean sendURLMessage(ThingActions actions, String message, @Nullable String title, String url, + @Nullable String urlTitle) { + return ((PushoverActions) actions).sendURLMessage(message, title, url, urlTitle); + } + + @RuleAction(label = "@text/sendHTMLMessageActionLabel", description = "@text/sendHTMLMessageActionDescription") + public @ActionOutput(name = "sent", label = "@text/sendMessageActionOutputLabel", description = "@text/sendMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendHtmlMessage( + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) { + logger.trace("ThingAction 'sendHtmlMessage' called with value(s): message='{}', title='{}'", message, title); + return send(getDefaultPushoverMessageBuilder(message).withHtmlFormatting(), title); + } + + public static Boolean sendHtmlMessage(ThingActions actions, String message, @Nullable String title) { + return ((PushoverActions) actions).sendHtmlMessage(message, title); + } + + @RuleAction(label = "@text/sendMonospaceMessageActionLabel", description = "@text/sendMonospaceMessageActionDescription") + public @ActionOutput(name = "sent", label = "@text/sendMessageActionOutputLabel", description = "@text/sendMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendMonospaceMessage( + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) { + logger.trace("ThingAction 'sendMonospaceMessage' called with value(s): message='{}', title='{}'", message, + title); + return send(getDefaultPushoverMessageBuilder(message).withMonospaceFormatting(), title); + } + + public static Boolean sendMonospaceMessage(ThingActions actions, String message, @Nullable String title) { + return ((PushoverActions) actions).sendMonospaceMessage(message, title); + } + + @RuleAction(label = "@text/sendAttachmentMessageActionLabel", description = "@text/sendAttachmentMessageActionDescription") + public @ActionOutput(name = "sent", label = "@text/sendMessageActionOutputLabel", description = "@text/sendMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendAttachmentMessage( + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title, + @ActionInput(name = "attachment", label = "@text/sendMessageActionInputAttachmentLabel", description = "@text/sendMessageActionInputAttachmentDescription", type = "java.lang.String", required = true) String attachment, + @ActionInput(name = "contentType", label = "@text/sendMessageActionInputContentTypeLabel", description = "@text/sendMessageActionInputContentTypeDescription", type = "java.lang.String") @Nullable String contentType) { + logger.trace( + "ThingAction 'sendAttachmentMessage' called with value(s): message='{}', title='{}', attachment='{}', contentType='{}'", + message, title, attachment, contentType); + if (attachment == null) { + throw new IllegalArgumentException("Skip sending message as 'attachment' is null."); + } + + PushoverMessageBuilder builder = getDefaultPushoverMessageBuilder(message).withAttachment(attachment); + if (contentType != null) { + builder.withContentType(contentType); + } + return send(builder, title); + } + + public static Boolean sendAttachmentMessage(ThingActions actions, String message, @Nullable String title, + String attachment, @Nullable String contentType) { + return ((PushoverActions) actions).sendAttachmentMessage(message, title, attachment, contentType); + } + + @RuleAction(label = "@text/sendPriorityMessageActionLabel", description = "@text/sendPriorityMessageActionDescription") + public @ActionOutput(name = "receipt", label = "@text/sendPriorityMessageActionOutputLabel", description = "@text/sendPriorityMessageActionOutputDescription", type = "java.lang.String") String sendPriorityMessage( + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title, + @ActionInput(name = "priority", label = "@text/sendMessageActionInputPriorityLabel", description = "@text/sendMessageActionInputPriorityDescription", required = true) int priority) { + logger.trace("ThingAction 'sendPriorityMessage' called with value(s): message='{}', title='{}', priority='{}'", + message, title, priority); + PushoverMessageBuilder builder = getDefaultPushoverMessageBuilder(message).withPriority(priority); + + if (title != null) { + builder.withTitle(title); + } + return accountHandler.sendPriorityMessage(builder); + } + + public static String sendPriorityMessage(ThingActions actions, String message, @Nullable String title, + int priority) { + return ((PushoverActions) actions).sendPriorityMessage(message, title, priority); + } + + @RuleAction(label = "@text/cancelPriorityMessageActionLabel", description = "@text/cancelPriorityMessageActionDescription") + public @ActionOutput(name = "canceled", label = "@text/cancelPriorityMessageActionOutputLabel", description = "@text/cancelPriorityMessageActionOutputDescription", type = "java.lang.Boolean") Boolean cancelPriorityMessage( + @ActionInput(name = "receipt", label = "@text/cancelPriorityMessageActionInputReceiptLabel", description = "@text/cancelPriorityMessageActionInputReceiptDescription", type = "java.lang.String", required = true) String receipt) { + logger.trace("ThingAction 'cancelPriorityMessage' called with value(s): '{}'", receipt); + if (accountHandler == null) { + throw new RuntimeException("PushoverAccountHandler is null!"); + } + + if (receipt == null) { + throw new IllegalArgumentException("Skip canceling message as 'receipt' is null."); + } + + return accountHandler.cancelPriorityMessage(receipt); + } + + public static Boolean cancelPriorityMessage(ThingActions actions, String receipt) { + return ((PushoverActions) actions).cancelPriorityMessage(receipt); + } + + @RuleAction(label = "@text/sendMessageToDeviceActionLabel", description = "@text/sendMessageToDeviceActionDescription") + public @ActionOutput(name = "sent", label = "@text/sendMessageActionOutputLabel", description = "@text/sendMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendMessageToDevice( + @ActionInput(name = "device", label = "@text/sendMessageActionInputDeviceLabel", description = "@text/sendMessageActionInputDeviceDescription", type = "java.lang.String", required = true) String device, + @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, + @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) { + logger.trace("ThingAction 'sendMessageToDevice' called with value(s): device='{}', message='{}', title='{}'", + device, message, title); + if (device == null) { + throw new IllegalArgumentException("Skip sending message as 'device' is null."); + } + + return send(getDefaultPushoverMessageBuilder(message).withDevice(device), title); + } + + public static Boolean sendMessageToDevice(ThingActions actions, String device, String message, + @Nullable String title) { + return ((PushoverActions) actions).sendMessageToDevice(device, message, title); + } + + private PushoverMessageBuilder getDefaultPushoverMessageBuilder(String message) { + if (accountHandler == null) { + throw new RuntimeException("PushoverAccountHandler is null!"); + } + + if (message == null) { + throw new IllegalArgumentException("Skip sending message as 'message' is null."); + } + + return accountHandler.getDefaultPushoverMessageBuilder(message); + } + + private Boolean send(PushoverMessageBuilder builder, @Nullable String title) { + if (title != null) { + builder.withTitle(title); + } + return accountHandler.sendMessage(builder); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + this.accountHandler = (PushoverAccountHandler) handler; + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return accountHandler; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverAccountConfiguration.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverAccountConfiguration.java new file mode 100644 index 0000000000000..2767e5d5be23b --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverAccountConfiguration.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2020 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.config; + +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link PushoverAccountConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Christoph Weitkamp - Initial contribution + */ +@NonNullByDefault +public class PushoverAccountConfiguration { + public @Nullable String apikey; + public @Nullable String user; + public String title = DEFAULT_TITLE; + public String format = "none"; + public String sound = DEFAULT_SOUND; + public int retry = 300; + public int expire = 3600; +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java new file mode 100644 index 0000000000000..968ffeed50380 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2010-2020 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 java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration; +import org.openhab.binding.pushover.internal.dto.Sound; +import org.openhab.core.cache.ExpiringCacheMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * The {@link PushoverAPIConnection} is responsible for handling the connections to Pushover Messages API. + * + * @author Christoph Weitkamp - Initial contribution + */ +@NonNullByDefault +public class PushoverAPIConnection { + + 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 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 SOUNDS_URL = "https://api.pushover.net/1/sounds.json"; + + private final HttpClient httpClient; + private final PushoverAccountConfiguration config; + + private final ExpiringCacheMap cache = new ExpiringCacheMap<>(TimeUnit.DAYS.toMillis(1)); + + private final JsonParser parser = new JsonParser(); + + public PushoverAPIConnection(HttpClient httpClient, PushoverAccountConfiguration config) { + this.httpClient = httpClient; + this.config = config; + } + + /** + * + * @return + * @throws PushoverCommunicationException + * @throws PushoverConfigurationException + */ + public boolean validateUser() throws PushoverCommunicationException, PushoverConfigurationException { + return getMessageStatus( + post(VALIDATE_URL, PushoverMessageBuilder.getInstance(config.apikey, config.user).build())); + } + + /** + * + * @param message + * @return + * @throws PushoverCommunicationException + * @throws PushoverConfigurationException + */ + public boolean sendMessage(PushoverMessageBuilder message) + throws PushoverCommunicationException, PushoverConfigurationException { + return getMessageStatus(post(MESSAGE_URL, message.build())); + } + + /** + * + * @param message + * @return + * @throws PushoverCommunicationException + * @throws PushoverConfigurationException + */ + public String sendPriorityMessage(PushoverMessageBuilder message) + throws PushoverCommunicationException, PushoverConfigurationException { + final JsonObject json = parser.parse(post(MESSAGE_URL, message.build())).getAsJsonObject(); + return getMessageStatus(json) && json.has("receipt") ? json.get("receipt").getAsString() : ""; + } + + /** + * + * @param receipt + * @return + * @throws PushoverCommunicationException + * @throws PushoverConfigurationException + */ + public boolean cancelPriorityMessage(String receipt) + throws PushoverCommunicationException, PushoverConfigurationException { + return getMessageStatus(post(CANCEL_MESSAGE_URL.replace("{receipt}", receipt), + PushoverMessageBuilder.getInstance(config.apikey, config.user).build())); + } + + /** + * + * @return + * @throws PushoverCommunicationException + * @throws PushoverConfigurationException + */ + public List getSounds() throws PushoverCommunicationException, PushoverConfigurationException { + final String localApikey = config.apikey; + if (localApikey == null || localApikey.isEmpty()) { + throw new PushoverConfigurationException("@text/offline.conf-error-missing-apikey"); + } + + final Map params = new HashMap<>(1); + params.put(PushoverMessageBuilder.MESSAGE_KEY_TOKEN, localApikey); + + // TODO do not cache the response, cache the parsed list of sounds + final JsonObject json = parser.parse(getFromCache(buildURL(SOUNDS_URL, params))).getAsJsonObject(); + if (json.has("sounds")) { + final JsonObject sounds = json.get("sounds").getAsJsonObject(); + if (sounds != null) { + return Collections.unmodifiableList(sounds.entrySet().stream() + .map(entry -> new Sound(entry.getKey(), entry.getValue().getAsString())) + .collect(Collectors.toList())); + } + } + return Collections.emptyList(); + } + + private String buildURL(String url, Map requestParams) { + return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key))) + .collect(Collectors.joining("&", url + "?", "")); + } + + private String encodeParam(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + logger.debug("UnsupportedEncodingException occurred during execution: {}", e.getLocalizedMessage(), e); + return ""; + } + } + + private @Nullable String getFromCache(String url) { + return cache.putIfAbsentAndGet(url, () -> get(url)); + } + + private String get(String url) throws PushoverCommunicationException, PushoverConfigurationException { + return executeRequest(HttpMethod.GET, url, null); + } + + private String post(String url, ContentProvider body) + throws PushoverCommunicationException, PushoverConfigurationException { + return executeRequest(HttpMethod.POST, url, body); + } + + private String executeRequest(HttpMethod httpMethod, String url, @Nullable ContentProvider body) + throws PushoverCommunicationException, PushoverConfigurationException { + logger.trace("Pushover request: {} - URL = '{}'", httpMethod, url); + try { + final Request request = httpClient.newRequest(url).method(httpMethod).timeout(10, TimeUnit.SECONDS); + + if (body != null) { + if (logger.isTraceEnabled()) { + logger.trace("Pushover request body: '{}'", body); + } + request.content(body); + } + + final ContentResponse contentResponse = request.send(); + + final int httpStatus = contentResponse.getStatus(); + final String content = contentResponse.getContentAsString(); + logger.trace("Pushover response: status = {}, content = '{}'", httpStatus, content); + switch (httpStatus) { + case HttpStatus.OK_200: + return content; + case HttpStatus.BAD_REQUEST_400: + logger.debug("Pushover server responded with status code {}: {}", httpStatus, content); + throw new PushoverConfigurationException(getMessageError(content)); + default: + logger.debug("Pushover server responded with status code {}: {}", httpStatus, content); + throw new PushoverCommunicationException(content); + } + } catch (ExecutionException | InterruptedException | TimeoutException e) { + logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e); + throw new PushoverCommunicationException(e.getLocalizedMessage(), e.getCause()); + } + } + + private String getMessageError(String content) { + final JsonObject json = parser.parse(content).getAsJsonObject(); + if (json.has("errors")) { + final JsonArray errors = json.get("errors").getAsJsonArray(); + if (errors != null) { + return errors.toString(); + } + } + return "Unknown error occured."; + } + + private boolean getMessageStatus(String content) { + final JsonObject json = parser.parse(content).getAsJsonObject(); + return json.has("status") ? json.get("status").getAsInt() == 1 : false; + } + + private boolean getMessageStatus(JsonObject json) { + return json.has("status") ? json.get("status").getAsInt() == 1 : false; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java new file mode 100644 index 0000000000000..968f5c28b6c08 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2020 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 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(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified cause. + * + * @param cause The cause + */ + public PushoverCommunicationException(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(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverConfigurationException.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverConfigurationException.java new file mode 100644 index 0000000000000..cc15122fd8db4 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverConfigurationException.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2020 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 RuntimeException { + + 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); + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java new file mode 100644 index 0000000000000..005ce2c05c028 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java @@ -0,0 +1,267 @@ +/** + * Copyright (c) 2010-2020 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 java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.util.MultiPartContentProvider; +import org.eclipse.jetty.client.util.PathContentProvider; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PushoverMessageBuilder} builds the body for Pushover Messages API requests. + * + * @author Christoph Weitkamp - Initial contribution + */ +@NonNullByDefault +public class PushoverMessageBuilder { + + private final Logger logger = LoggerFactory.getLogger(PushoverMessageBuilder.class); + + public static final String MESSAGE_KEY_TOKEN = "token"; + private static final String MESSAGE_KEY_USER = "user"; + private static final String MESSAGE_KEY_MESSAGE = "message"; + private static final String MESSAGE_KEY_TITLE = "title"; + private static final String MESSAGE_KEY_DEVICE = "device"; + private static final String MESSAGE_KEY_PRIORITY = "priority"; + private static final String MESSAGE_KEY_RETRY = "retry"; + private static final String MESSAGE_KEY_EXPIRE = "expire"; + private static final String MESSAGE_KEY_URL = "url"; + private static final String MESSAGE_KEY_URL_TITLE = "url_title"; + private static final String MESSAGE_KEY_SOUND = "sound"; + private static final String MESSAGE_KEY_ATTACHMENT = "attachment"; + public static final String MESSAGE_KEY_HTML = "html"; + public static final String MESSAGE_KEY_MONOSPACE = "monospace"; + + private static final int MAX_MESSAGE_LENGTH = 1024; + private static final int MAX_TITLE_LENGTH = 250; + private static final int MAX_DEVICE_LENGTH = 25; + private static final List VALID_PRIORITY_LIST = Arrays.asList(-2, -1, 0, 1, 2); + private static final int DEFAULT_PRIORITY = 0; + public static final int EMERGENCY_PRIORITY = 2; + private static final int MIN_RETRY_SECONDS = 30; + private static final int MAX_EXPIRE_SECONDS = 10800; + private static final int MAX_URL_LENGTH = 512; + private static final int MAX_URL_TITLE_LENGTH = 100; + private static final String DEFAULT_CONTENT_TYPE = "image/jpeg"; + + private final MultiPartContentProvider body = new MultiPartContentProvider(); + + private @Nullable String message; + private @Nullable String title; + private @Nullable String device; + private int priority = DEFAULT_PRIORITY; + private int retry = 300; + private int expire = 3600; + private @Nullable String url; + private @Nullable String urlTitle; + private @Nullable String sound; + private @Nullable String attachment; + private String contentType = DEFAULT_CONTENT_TYPE; + private boolean html = false; + private boolean monospace = false; + + private PushoverMessageBuilder(String apikey, String user) throws PushoverConfigurationException { + body.addFieldPart(MESSAGE_KEY_TOKEN, new StringContentProvider(apikey), null); + body.addFieldPart(MESSAGE_KEY_USER, new StringContentProvider(user), null); + } + + public static PushoverMessageBuilder getInstance(@Nullable String apikey, @Nullable String user) + throws PushoverConfigurationException { + if (apikey == null || apikey.isEmpty()) { + throw new PushoverConfigurationException("@text/offline.conf-error-missing-apikey"); + } + + if (user == null || user.isEmpty()) { + throw new PushoverConfigurationException("@text/offline.conf-error-missing-user"); + } + + return new PushoverMessageBuilder(apikey, user); + } + + public PushoverMessageBuilder withMessage(String message) { + this.message = message; + return this; + } + + public PushoverMessageBuilder withTitle(String title) { + this.title = title; + return this; + } + + public PushoverMessageBuilder withDevice(String device) { + this.device = device; + return this; + } + + public PushoverMessageBuilder withPriority(int priority) { + this.priority = priority; + return this; + } + + public PushoverMessageBuilder withRetry(int retry) { + this.retry = retry; + return this; + } + + public PushoverMessageBuilder withExpire(int expire) { + this.expire = expire; + return this; + } + + public PushoverMessageBuilder withUrl(String url) { + this.url = url; + return this; + } + + public PushoverMessageBuilder withUrlTitle(String urlTitle) { + this.urlTitle = urlTitle; + return this; + } + + public PushoverMessageBuilder withSound(String sound) { + this.sound = sound; + return this; + } + + public PushoverMessageBuilder withAttachment(String attachment) { + this.attachment = attachment; + return this; + } + + public PushoverMessageBuilder withContentType(String contentType) { + this.contentType = contentType; + return this; + } + + public PushoverMessageBuilder withHtmlFormatting() { + this.html = true; + return this; + } + + public PushoverMessageBuilder withMonospaceFormatting() { + this.monospace = true; + return this; + } + + public ContentProvider build() { + if (message != null) { + if (message.length() > MAX_MESSAGE_LENGTH) { + throw new IllegalArgumentException(String.format( + "Skip sending the message as 'message' is longer than %d characters.", MAX_MESSAGE_LENGTH)); + } + body.addFieldPart(MESSAGE_KEY_MESSAGE, new StringContentProvider(message), null); + } + + if (title != null) { + if (title.length() > MAX_TITLE_LENGTH) { + throw new IllegalArgumentException(String + .format("Skip sending the message as 'title' is longer than %d characters.", MAX_TITLE_LENGTH)); + } + body.addFieldPart(MESSAGE_KEY_TITLE, new StringContentProvider(title), null); + } + + if (device != null) { + if (device.length() > MAX_DEVICE_LENGTH) { + logger.warn("Skip 'device' as it is longer than {} characters. Got: {}.", MAX_DEVICE_LENGTH, device); + } else { + // TODO [A-Za-z0-9_-] + // TODO Messages may be addressed to multiple specific devices by joining them with a comma (such as + // device=iphone,nexus5) + body.addFieldPart(MESSAGE_KEY_DEVICE, new StringContentProvider(device), null); + } + } + + if (priority != DEFAULT_PRIORITY) { + if (VALID_PRIORITY_LIST.contains(priority)) { + body.addFieldPart(MESSAGE_KEY_PRIORITY, new StringContentProvider(String.valueOf(priority)), null); + + if (priority == EMERGENCY_PRIORITY) { + if (retry < MIN_RETRY_SECONDS) { + logger.warn("Retry value of {} is too small. Using default value of {}.", retry, + MIN_RETRY_SECONDS); + body.addFieldPart(MESSAGE_KEY_RETRY, + new StringContentProvider(String.valueOf(MIN_RETRY_SECONDS)), null); + } else { + body.addFieldPart(MESSAGE_KEY_RETRY, new StringContentProvider(String.valueOf(retry)), null); + } + + if (0 < expire && expire <= MAX_EXPIRE_SECONDS) { + body.addFieldPart(MESSAGE_KEY_EXPIRE, new StringContentProvider(String.valueOf(expire)), null); + } else { + logger.warn("Expire value of {} is invalid. Using default value of {}.", expire, + MAX_EXPIRE_SECONDS); + body.addFieldPart(MESSAGE_KEY_EXPIRE, + new StringContentProvider(String.valueOf(MAX_EXPIRE_SECONDS)), null); + } + } + } else { + logger.warn("Invalid 'priority', skipping. Expected: {}. Got: {}.", + VALID_PRIORITY_LIST.stream().map(i -> i.toString()).collect(Collectors.joining(",")), priority); + } + } + + if (url != null) { + if (url.length() > MAX_URL_LENGTH) { + throw new IllegalArgumentException(String + .format("Skip sending the message as 'url' is longer than %d characters.", MAX_URL_LENGTH)); + } + body.addFieldPart(MESSAGE_KEY_URL, new StringContentProvider(url), null); + + if (urlTitle != null) { + if (urlTitle.length() > MAX_URL_TITLE_LENGTH) { + throw new IllegalArgumentException( + String.format("Skip sending the message as 'urlTitle' is longer than %d characters.", + MAX_URL_TITLE_LENGTH)); + } + body.addFieldPart(MESSAGE_KEY_URL_TITLE, new StringContentProvider(urlTitle), null); + } + } + + if (sound != null) { + // TODO validate sound + body.addFieldPart(MESSAGE_KEY_SOUND, new StringContentProvider(sound), null); + } + + if (attachment != null) { + File file = new File(attachment); + if (!file.exists()) { + throw new IllegalArgumentException( + String.format("Skip sending the message as file '%s' does not exist.", attachment)); + } + try { + body.addFilePart(MESSAGE_KEY_ATTACHMENT, file.getName(), + new PathContentProvider(contentType, file.toPath()), null); + } catch (IOException e) { + throw new IllegalArgumentException(String.format("Skip sending the message: %s", e.getMessage())); + } + } + + if (html) { + body.addFieldPart(MESSAGE_KEY_HTML, new StringContentProvider("1"), null); + } else if (monospace) { + body.addFieldPart(MESSAGE_KEY_MONOSPACE, new StringContentProvider("1"), null); + } + + return body; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java new file mode 100644 index 0000000000000..2a6c02a9e7616 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 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.dto; + +/** + * The {@link Sound} is the Java class used to map the JSON response to an Pushover API request.. + * + * @author Christoph Weitkamp - Initial contribution + */ +public class Sound { + public String sound; + public String label; + + public Sound(String sound, String label) { + this.sound = sound; + this.label = label; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java new file mode 100644 index 0000000000000..83bc7ec2979d5 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2020 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.factory; + +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.pushover.internal.handler.PushoverAccountHandler; +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.Reference; + +/** + * The {@link PushoverHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Christoph Weitkamp - Initial contribution + */ +@Component(configurationPid = "binding.pushover", service = ThingHandlerFactory.class) +@NonNullByDefault +public class PushoverHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(PUSHOVER_ACCOUNT, PUSHOVER_USER); + + private final HttpClient httpClient; + + @Activate + public PushoverHandlerFactory(final @Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + final ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (PUSHOVER_ACCOUNT.equals(thingTypeUID)) { + return new PushoverAccountHandler(thing, httpClient); + } else if (PUSHOVER_USER.equals(thingTypeUID)) { + return null; + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java new file mode 100644 index 0000000000000..42ff984e8df53 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*; + +import java.net.URI; +import java.util.Collection; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.pushover.internal.actions.PushoverActions; +import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration; +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.core.config.core.ConfigOptionProvider; +import org.openhab.core.config.core.ParameterOption; +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.ThingHandlerService; +import org.openhab.core.types.Command; + +/** + * The {@link PushoverAccountHandler} is responsible for handling commands, which are sent to one of the channels. + * + * @author Christoph Weitkamp - Initial contribution + */ +@NonNullByDefault +public class PushoverAccountHandler extends BaseThingHandler implements ConfigOptionProvider { + + private static final Collection> SUPPORTED_THING_ACTIONS = Set + .of(PushoverActions.class); + private static final String URI_STR = "thing-type:pushover:pushover-account"; + + private final HttpClient httpClient; + + private @NonNullByDefault({}) PushoverAccountConfiguration config; + private @Nullable PushoverAPIConnection connection; + + public PushoverAccountHandler(Thing thing, HttpClient httpClient) { + super(thing); + this.httpClient = httpClient; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // nothing + } + + @Override + public void initialize() { + config = getConfigAs(PushoverAccountConfiguration.class); + + boolean configValid = true; + final String apikey = config.apikey; + if (apikey == null || apikey.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-missing-apikey"); + configValid = false; + } + final String user = config.user; + if (user == null || user.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-missing-user"); + configValid = false; + } + + if (configValid) { + updateStatus(ThingStatus.UNKNOWN); + + connection = new PushoverAPIConnection(httpClient, config); + try { + connection.validateUser(); + updateStatus(ThingStatus.ONLINE); + } catch (PushoverCommunicationException | PushoverConfigurationException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + } + } + } + + @Override + public Collection> getServices() { + return SUPPORTED_THING_ACTIONS; + } + + /** + * Returns a preconfigured {@link PushoverMessageBuilder}. + * + * @param message the message + * @return a {@link PushoverMessageBuilder} instance + */ + public PushoverMessageBuilder getDefaultPushoverMessageBuilder(String message) { + PushoverMessageBuilder builder = PushoverMessageBuilder.getInstance(config.apikey, config.user) + .withMessage(message) // + .withTitle(config.title) // + .withRetry(config.retry) // + .withExpire(config.expire); + // specify format if defined + switch (config.format) { + case PushoverMessageBuilder.MESSAGE_KEY_HTML: + builder.withHtmlFormatting(); + break; + case PushoverMessageBuilder.MESSAGE_KEY_MONOSPACE: + builder.withMonospaceFormatting(); + default: + break; + } + // add sound if defined + if (!DEFAULT_SOUND.equals(config.sound)) { + builder.withSound(config.sound); + } + return builder; + } + + /** + * + * @param messageBuilder + * @return + */ + public boolean sendMessage(PushoverMessageBuilder messageBuilder) { + if (connection != null) { + return connection.sendMessage(messageBuilder); + } else { + throw new IllegalArgumentException("PushoverAPIConnection is null!"); + } + } + + /** + * + * @param messageBuilder + * @return + */ + public String sendPriorityMessage(PushoverMessageBuilder messageBuilder) { + if (connection != null) { + return connection.sendPriorityMessage(messageBuilder); + } else { + throw new IllegalArgumentException("PushoverAPIConnection is null!"); + } + } + + /** + * + * @param receipt + * @return + */ + public boolean cancelPriorityMessage(String receipt) { + if (connection != null) { + return connection.cancelPriorityMessage(receipt); + } else { + throw new IllegalArgumentException("PushoverAPIConnection is null!"); + } + } + + @Override + public @Nullable Collection getParameterOptions(URI uri, String param, @Nullable String context, + @Nullable Locale locale) { + if (URI_STR.equals(uri.toString()) && CONFIG_SOUND.equals(param) && connection != null) { + return connection.getSounds().stream().map(s -> new ParameterOption(s.sound, s.label)) + .collect(Collectors.toUnmodifiableSet()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..58dd8ca7239f5 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + Pushover Binding + Pushover - Simple Notifications. + Christoph Weitkamp + + diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..fe2d3fb0b2e8d --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,79 @@ + + + + + + password + + Your API token / key (APP_TOKEN) to access the Pushover Message API. + + + password + + Your user key or group key (USER_KEY) to which you want to push notifications. + + + + The default title of the message. + openHAB + + + + The default format of the message. + none + + + + + + + + + The default notification sound on target device. + default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + The retry parameter specifies how often the Pushover servers will send the same notification to the + user. + 300 + + + true + + The expire parameter specifies how long your notification will continue to be retried for. + 3600 + + + + diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties new file mode 100644 index 0000000000000..17db7cd05371e --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties @@ -0,0 +1,52 @@ +# user defined messages +offline.conf-error-missing-apikey = The 'apikey' parameter must be configured. +offline.conf-error-missing-user = The 'user' parameter must be configured. + +# actions +sendMessageActionLabel = send a plain text message +sendMessageActionDescription = This method is used to send a plain text message. +sendMessageActionOutputLabel = Sent +sendMessageActionOutputDescription = true, if message has been sent successfully +sendMessageActionInputMessageLabel = Message +sendMessageActionInputMessageDescription = Message to be sent. +sendMessageActionInputTitleLabel = Title +sendMessageActionInputTitleDescription = The title of the message. + +sendURLMessageActionLabel = send a plain text message with an URL +sendURLMessageActionDescription = This method is used to send a message with an URL. +sendMessageActionInputURLLabel = URL +sendMessageActionInputURLDescription = A supplementary URL to show with the message. +sendMessageActionInputURLTitleLabel = URL Title +sendMessageActionInputURLTitleDescription = A title for the URL, otherwise just the URL is shown. + +sendHTMLMessageActionLabel = send a HTML message +sendHTMLMessageActionDescription = This method is used to send a HTML message. + +sendMonospaceMessageActionLabel = send a monospace message +sendMonospaceMessageActionDescription = This method is used to send a monospace message. + +sendAttachmentMessageActionLabel = send a plain text message with an attachment +sendAttachmentMessageActionDescription = This method is used to send a message with an attachment. +sendMessageActionInputAttachmentLabel = Attachment +sendMessageActionInputAttachmentDescription = A (local) path to the attachment. +sendMessageActionInputContentTypeLabel = Content Type +sendMessageActionInputContentTypeDescription = The content type of the attachment. Defaults to "image/jpeg". + +sendPriorityMessageActionLabel = send a priority message +sendPriorityMessageActionDescription = This method is used to send a priority message. +sendPriorityMessageActionOutputLabel = Receipt +sendPriorityMessageActionOutputDescription = Receipt, if priority message sent successfully. +sendMessageActionInputPriorityLabel = Priority +sendMessageActionInputPriorityDescription = Priority to be used. + +cancelPriorityMessageActionLabel = cancel a priority message +cancelPriorityMessageActionDescription = This method is used to cancel a priority message. +cancelPriorityMessageActionOnputLabel = Cancelled +cancelPriorityMessageActionOnputDescription = true, if message has been cancelled successfully. +cancelPriorityMessageActionInputReceiptLabel = Receipt +cancelPriorityMessageActionInputReceiptDescription = Receipt of the message to be canceled. + +sendMessageToDeviceActionLabel = send a plain text message to a specific device +sendMessageToDeviceActionDescription = This method is used to send a message to a specific device. +sendMessageActionInputDeviceLabel = Device +sendMessageActionInputDeviceDescription = The name of a specific device (multiple devices may be separated by a comma). diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties new file mode 100644 index 0000000000000..8a3f17bd48da3 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties @@ -0,0 +1,75 @@ +# binding +binding.pushover.description = Pushover - Einfache Benachrichtigungen. + +# thing types +thing-type.pushover.pushover-account.label = Pushover Konto +thing-type.pushover.pushover-account.description = Ermöglicht den Zugriff auf die Pushover Message API. + +# thing type config description +thing-type.config.pushover.pushover-account.apikey.label = API Token / Key +thing-type.config.pushover.pushover-account.apikey.description = API Token / Schlüssel für den Zugriff auf die Pushover Message API. +thing-type.config.pushover.pushover-account.user.label = User / Group Key +thing-type.config.pushover.pushover-account.user.description = User / Group Key (USER_KEY) an den / die Nachrichten gesendet werden sollen. +thing-type.config.pushover.pushover-account.title.label = Titel +thing-type.config.pushover.pushover-account.title.description = Standardtitel der Nachricht. +thing-type.config.pushover.pushover-account.format.label = Format +thing-type.config.pushover.pushover-account.format.description = Standardformat der Nachricht. +thing-type.config.pushover.pushover-account.sound.label = Benachrichtigungston +thing-type.config.pushover.pushover-account.sound.description = Standardbenachrichtigungston auf dem Endgerät. +thing-type.config.pushover.pushover-account.retry.label = Wiederholungen +thing-type.config.pushover.pushover-account.retry.description = Dieser Parameter gibt an, in welchen Abständen eine Prioritätsnachricht wiederholt an den Benutzer gesendet werden soll. +thing-type.config.pushover.pushover-account.expire.label = Verfall +thing-type.config.pushover.pushover-account.expire.description = Dieser Parameter gibt an, wie lange eine Prioritätsnachricht wiederholt wird. + +# user defined messages +offline.conf-error-missing-apikey = Der Parameter 'apikey' muss konfiguriert werden. +offline.conf-error-missing-user = Der Parameter 'user' muss konfiguriert werden. + +# actions +sendMessageActionLabel = eine Textnachricht senden +sendMessageActionDescription = Action zum Versenden einer Textnachricht. +sendMessageActionOutputLabel = Gesendet +sendMessageActionOutputDescription = true, wenn die Nachricht erfolgreich versendet wurde. +sendMessageActionInputMessageLabel = Nachricht +sendMessageActionInputMessageDescription = Die Nachricht. +sendMessageActionInputTitleLabel = Titel +sendMessageActionInputTitleDescription = Titel der Nachricht. + +sendURLMessageActionLabel = eine Textnachricht mit URL senden +sendURLMessageActionDescription = Action zum Versenden einer Textnachricht mit einer URL. +sendMessageActionInputURLLabel = URL +sendMessageActionInputURLDescription = Eine zusätzliche URL, die mit der Nachricht angezeigt werden soll. +sendMessageActionInputURLTitleLabel = URL Title +sendMessageActionInputURLTitleDescription = Ein Titel für die URL, andernfalls wird nur die URL angezeigt. + +sendHTMLMessageActionLabel = eine HTML-Nachricht senden +sendHTMLMessageActionDescription = Action zum Versenden einer HTML-Nachricht. + +sendMonospaceMessageActionLabel = eine monospace-Nachricht senden +sendMonospaceMessageActionDescription = Action zum Versenden einer monospace-Nachricht. + +sendAttachmentMessageActionLabel = eine Textnachricht mit Anhang senden +sendAttachmentMessageActionDescription = Action zum Versenden einer Textnachricht mit Anhang. +sendMessageActionInputAttachmentLabel = Anhang +sendMessageActionInputAttachmentDescription = Lokaler Pfad zum Anhang. +sendMessageActionInputContentTypeLabel = Content-Type +sendMessageActionInputContentTypeDescription = Der Content-Type für den Anhang. Default: "image/jpeg". + +sendPriorityMessageActionLabel = eine Prioritätsnachricht senden +sendPriorityMessageActionDescription = Action zum Versenden einer Prioritätsnachricht. +sendPriorityMessageActionOutputLabel = Receipt +sendPriorityMessageActionOutputDescription = ID der Prioritätsnachricht, wenn diese erfolgreich versendet wurde. +sendMessageActionInputPriorityLabel = Priorität +sendMessageActionInputPriorityDescription = Die Priorität. + +cancelPriorityMessageActionLabel = eine Prioritätsnachricht annullieren +cancelPriorityMessageActionDescription = Action zum Annullieren einer Prioritätsnachricht. +cancelPriorityMessageActionOnputLabel = Annulliert +cancelPriorityMessageActionOnputDescription = true, wenn die Prioritätsnachricht erfolgreich annulliert wurde. +cancelPriorityMessageActionInputReceiptLabel = Receipt +cancelPriorityMessageActionInputReceiptDescription = Die ID der Prioritätsnachricht. + +sendMessageToDeviceActionLabel = eine Nachricht an ein Endgerät +sendMessageToDeviceActionDescription = Action zum Versenden einer Nachricht an ein Endgerät. +sendMessageActionInputDeviceLabel = Endgerät +sendMessageActionInputDeviceDescription = Der Name des Endgeräts (mehrere Geräte können durch ein Komma getrennt werden). diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..0bb228b5e4a45 --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,16 @@ + + + + + + Provides access to the Pushover Messages API. + + apikey + + + + + diff --git a/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java b/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java new file mode 100644 index 0000000000000..47e4185bed20d --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2010-2020 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.actions; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.binding.pushover.internal.connection.PushoverMessageBuilder; +import org.openhab.binding.pushover.internal.handler.PushoverAccountHandler; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingHandler; + +/** + * Unit tests for {@link PushoverActions}. + * + * @author Christoph Weitkamp - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.WARN) +public class PushoverActionsTest { + + private static final String MESSAGE = "My Message"; + private static final String TITLE = "My Title"; + private static final String URL = "https://www.test.com"; + private static final String URL_TITLE = "Some Link"; + private static final String RECEIPT = "12345"; + private static final String SUCCESSFULL_RESPONSE = "{\"status\":1,\"request\":\"647d2300-702c-4b38-8b2f-d56326ae460b\"}"; + private static final String FAILED_RESPONSE = "{\"user\":\"invalid\",\"errors\":[\"user identifier is invalid\"],\"status\":0,\"request\":\"5042853c-402d-4a18-abcb-168734a801de\"}"; + + @NonNullByDefault + private final ThingActions thingActionsStub = new ThingActions() { + @Override + public void setThingHandler(ThingHandler handler) { + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return null; + } + }; + + private @Mock Thing mockThing; + private @Mock HttpClient mockHttpClient; + private @Mock PushoverAccountHandler mockPushoverAccountHandler; + + private PushoverActions pushoverThingActions; + + @BeforeEach + public void setUp() { + pushoverThingActions = new PushoverActions(); + + when(mockPushoverAccountHandler.getDefaultPushoverMessageBuilder(any())) + .thenReturn(PushoverMessageBuilder.getInstance("key", "user")); + when(mockPushoverAccountHandler.sendMessage(any())).thenReturn(Boolean.TRUE); + when(mockPushoverAccountHandler.sendPriorityMessage(any())).thenReturn(RECEIPT); + when(mockPushoverAccountHandler.cancelPriorityMessage(RECEIPT)).thenReturn(Boolean.TRUE); + } + + // sendMessage + @Test + public void testSendMessageThingActionsIsNotPushoverThingActions() { + assertThrows(ClassCastException.class, () -> PushoverActions.sendMessage(thingActionsStub, MESSAGE, TITLE)); + } + + @Test + public void testSendMessageThingHandlerIsNull() { + assertThrows(RuntimeException.class, () -> PushoverActions.sendMessage(pushoverThingActions, MESSAGE, TITLE)); + } + + // @Test + // public void testSendMessageWithoutMessage() { + // Optional message = Optional.of(null); + // assertThrows(IllegalArgumentException.class, + // () -> PushoverActions.sendMessage(pushoverThingActions, message.get(), TITLE)); + // } + + @Test + public void testSendMessageWithoutTitle() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean sent = PushoverActions.sendMessage(pushoverThingActions, MESSAGE, null); + assertThat(sent, is(true)); + } + + @Test + public void testSendMessage() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean sent = PushoverActions.sendMessage(pushoverThingActions, MESSAGE, TITLE); + assertThat(sent, is(true)); + } + + // sendURLMessage + @Test + public void testSendURLMessageThingActionsIsNotPushoverThingActions() { + assertThrows(ClassCastException.class, + () -> PushoverActions.sendURLMessage(thingActionsStub, MESSAGE, TITLE, URL, URL_TITLE)); + } + + @Test + public void testSendURLMessageThingHandlerIsNull() { + assertThrows(RuntimeException.class, + () -> PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, TITLE, URL, URL_TITLE)); + } + + // @Test + // public void testSendURLMessageWithoutMessage() { + // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + // Optional message = Optional.of(null); + // assertThrows(IllegalArgumentException.class, + // () -> PushoverActions.sendURLMessage(pushoverThingActions, message.get(), TITLE, URL, URL_TITLE)); + // } + + // @Test + // public void testSendURLMessageWithoutURL() { + // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + // Optional url = Optional.of(null); + // assertThrows(IllegalArgumentException.class, + // () -> PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, TITLE, url.get(), URL_TITLE)); + // } + + @Test + public void testSendURLMessageWithoutTitle() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean sent = PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, null, URL, URL_TITLE); + assertThat(sent, is(true)); + } + + @Test + public void testSendURLMessageWithoutURLTitle() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean sent = PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, TITLE, URL, null); + assertThat(sent, is(true)); + } + + @Test + public void testSendURLMessage() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean sent = PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, TITLE, URL, URL_TITLE); + assertThat(sent, is(true)); + } + + // sendPriorityMessage + @Test + public void testSendPriorityMessageThingActionsIsNotPushoverThingActions() { + assertThrows(ClassCastException.class, () -> PushoverActions.sendPriorityMessage(thingActionsStub, MESSAGE, + TITLE, PushoverMessageBuilder.EMERGENCY_PRIORITY)); + } + + @Test + public void testSendPriorityMessageThingHandlerIsNull() { + assertThrows(RuntimeException.class, () -> PushoverActions.sendPriorityMessage(pushoverThingActions, MESSAGE, + TITLE, PushoverMessageBuilder.EMERGENCY_PRIORITY)); + } + + // @Test + // public void testSendPriorityMessageWithoutMessage() { + // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + // Optional message = Optional.of(null); + // assertThrows(IllegalArgumentException.class, () -> PushoverActions.sendPriorityMessage(pushoverThingActions, + // message.get(), TITLE, PushoverMessageBuilder.EMERGENCY_PRIORITY)); + // } + + @Test + public void testSendPriorityMessageWithoutTitle() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + String receipt = PushoverActions.sendPriorityMessage(pushoverThingActions, MESSAGE, null, + PushoverMessageBuilder.EMERGENCY_PRIORITY); + assertThat(receipt, is(RECEIPT)); + } + + @Test + public void testSendPriorityMessage() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + String receipt = PushoverActions.sendPriorityMessage(pushoverThingActions, MESSAGE, TITLE, + PushoverMessageBuilder.EMERGENCY_PRIORITY); + assertThat(receipt, is(RECEIPT)); + } + + // cancelPriorityMessage + @Test + public void testCancelPriorityMessageThingActionsIsNotPushoverThingActions() { + assertThrows(ClassCastException.class, () -> PushoverActions.cancelPriorityMessage(thingActionsStub, RECEIPT)); + } + + @Test + public void testCancelPriorityMessageThingHandlerIsNull() { + assertThrows(RuntimeException.class, + () -> PushoverActions.cancelPriorityMessage(pushoverThingActions, RECEIPT)); + } + + // @Test + // public void testCancelPriorityMessageWithoutReceipt() { + // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + // Optional receipt = Optional.of(null); + // assertThrows(IllegalArgumentException.class, + // () -> PushoverActions.cancelPriorityMessage(pushoverThingActions, receipt.get())); + // } + + @Test + public void testCancelPriorityMessageWithValidReceipt() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean cancelled = PushoverActions.cancelPriorityMessage(pushoverThingActions, RECEIPT); + assertThat(cancelled, is(true)); + } + + @Test + public void testCancelPriorityMessageWithInvalidReceipt() { + pushoverThingActions.setThingHandler(mockPushoverAccountHandler); + boolean cancelled = PushoverActions.cancelPriorityMessage(pushoverThingActions, "invalid"); + assertThat(cancelled, is(false)); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 8e46d1fc4618c..e02af935e7d8a 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -231,6 +231,7 @@ org.openhab.binding.powermax org.openhab.binding.pulseaudio org.openhab.binding.pushbullet + org.openhab.binding.pushover org.openhab.binding.radiothermostat org.openhab.binding.regoheatpump org.openhab.binding.revogi From 9a11f2c94a69353a49357f2b4b750ae2d6731a92 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sat, 21 Nov 2020 16:10:09 +0100 Subject: [PATCH 2/5] Finalized documentation Signed-off-by: Christoph Weitkamp --- .../org.openhab.binding.pushover/README.md | 84 +++++++++++-------- .../internal/actions/PushoverActions.java | 12 ++- .../connection/PushoverAPIConnection.java | 10 +-- .../PushoverCommunicationException.java | 7 +- .../connection/PushoverMessageBuilder.java | 2 +- .../main/resources/OH-INF/config/config.xml | 6 +- .../resources/OH-INF/i18n/pushover.properties | 2 +- .../OH-INF/i18n/pushover_de.properties | 2 +- 8 files changed, 69 insertions(+), 56 deletions(-) diff --git a/bundles/org.openhab.binding.pushover/README.md b/bundles/org.openhab.binding.pushover/README.md index 0fed63b051e08..9d18d8b4da61f 100644 --- a/bundles/org.openhab.binding.pushover/README.md +++ b/bundles/org.openhab.binding.pushover/README.md @@ -1,59 +1,60 @@ # Pushover Binding -_Give some details about what this binding is meant for - a protocol, system, specific device._ - -_If possible, provide some resources like pictures, a YouTube video, etc. to give an impression of what can be done with this binding. You can place such resources into a `doc` folder next to this README.md._ +The Pushover binding allows you to notify mobile devices of a message using the [Pushover REST API](https://pushover.net/api). +To get started you first need to register (a free process) to get an API token. +Initially you have to create an application, set its name and optionally upload an icon, and get the API token in return. +Once you have the token, you need a user key (or group key) and optionally a device name for each user to which you want to push notifications. ## Supported Things -_Please describe the different supported things / devices within this section._ -_Which different types are supported, which models were tested etc.?_ -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/ESH-INF/thing``` of your binding._ - -## Discovery - -_Describe the available auto-discovery features here. Mention for what it works and what needs to be kept in mind when using it._ +There is only one Thing available - the `pushover-account`. +You are able to create multiple instances of this Thing to broadcast to different users, groups or devices. -## Binding Configuration +## Thing Configuration -_If your binding requires or supports general configuration settings, please create a folder ```cfg``` and place the configuration file ```.cfg``` inside it. In this section, you should link to this file and provide some information about the options. The file could e.g. look like:_ +| Configuration Parameter | Type | Description | +|-------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| `apikey` | text | Your API token / key (APP_TOKEN) to access the Pushover Message API. **mandatory** | +| `user` | text | Your user key or group key (USER_KEY) to which you want to push notifications. **mandatory** | +| `title` | text | The default title of a message (default: `openHAB`). | +| `format` | text | The default format (`none`, `HTML` or `monospace`) of a message (default: `none`). | +| `sound` | text | The default notification sound on target device (default: `default`) (see [supported notification sounds](https://pushover.net/api#sounds)). | +| `retry` | integer | The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user (default: `300`). **advanced** | +| `expire` | integer | The expire parameter specifies how long (in seconds) your notification will continue to be retried (default: `3600`). **advanced** | -``` -# Configuration for the Philips Hue Binding -# -# Default secret key for the pairing of the Philips Hue Bridge. -# It has to be between 10-40 (alphanumeric) characters -# This may be changed by the user for security reasons. -secret=openHABSecret -``` +The `retry` and `expire` parameters are only used for emergency-priority notifications. -_Note that it is planned to generate some part of this based on the information that is available within ```src/main/resources/ESH-INF/binding``` of your binding._ +## Channels -_If your binding does not offer any generic configurations, you can remove this section completely._ +Currently the binding does not support any Channels. -## Thing Configuration +## Thing Actions -_Describe what is needed to manually configure a thing, either through the (Paper) UI or via a thing-file. This should be mainly about its mandatory and optional configuration parameters. A short example entry for a thing file can help!_ +All actions return a `Boolean` value to indicate if the message - parameter `message` **mandatory** - was sent successfully or not. +The `title` parameter defaults to whatever value you defined in the `title` related configuration parameter. -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/ESH-INF/thing``` of your binding._ +`sendMessage(String message, @Nullable String title)` - This method is used to send a plain text message. +`sendHtmlMessage(String message, @Nullable String title)` - This method is used to send a HTML message. +`sendMonospaceMessage(String message, @Nullable String title)` - This method is used to send a monospace message. +`sendAttachmentMessage(String message, @Nullable String title, String attachment, @Nullable String contentType)` - This method is used to send a message with an attachment. It takes a (local) path (`attachment` **mandatory**) to the attachment and an optional parameter `contentType` to define the content-type of the attachment (default: `image/jpeg`). +`sendURLMessage(String message, @Nullable String title, String url, @Nullable String urlTitle)` - This method is used to send a message with an URL. A supplementary `url` to show with the message and a `urlTitle` for the URL, otherwise just the URL is shown. +`sendMessageToDevice(String device, String message, @Nullable String title)` - This method is used to send a message to a specific device. Parameter `device` **mandatory** is the name of a specific device (multiple devices may be separated by a comma). -## Channels +The `sendPriorityMessage` action returns a `String` value (the `receipt`) if the message was sent successfully, otherwise `null`. -_Here you should provide information about available channel types, what their meaning is and how they can be used._ +`sendPriorityMessage(String message, @Nullable String title, @Nullable Integer priority)` - This method is used to send a priority message. Parameter `priority` is the priority (`-2`, `-1`, `0`, `1`, `2`) to be used (default: `2`). -_Note that it is planned to generate some part of this based on the XML files within ```src/main/resources/ESH-INF/thing``` of your binding._ +`cancelPriorityMessage` returns a `Boolean` value to indicate if the message was cancelled successfully or not. -| channel | type | description | -|----------|--------|------------------------------| -| control | Switch | This is the control channel | +`cancelPriorityMessage(String receipt)` - This method is used to cancel a priority message. ## Full Example +demo.things: - -### Actions - -TODO +```java +Thing pushover:pushover-account:account [ apikey="APP_TOKEN", user="USER_KEY" ] +``` demo.rules: @@ -62,3 +63,16 @@ val actions = getActions("pushover", "pushover:pushover-account:account") // send HTML message actions.sendHtmlMessage("Hello World!", "openHAB") ``` + +```java +val actions = getActions("pushover", "pushover:pushover-account:account") +// send priority message +var receipt = actions.sendPriorityMessage("Hello World!", "openHAB", 3) + +// wait for your cancel condition + +if( receipt !== null ) { + actions.cancelPriorityMessage(receipt) + receipt = null +} +``` diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java index 0414a07819d45..8511316493283 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/actions/PushoverActions.java @@ -13,6 +13,7 @@ package org.openhab.binding.pushover.internal.actions; import static org.openhab.binding.pushover.internal.PushoverBindingConstants.DEFAULT_TITLE; +import static org.openhab.binding.pushover.internal.connection.PushoverMessageBuilder.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -36,6 +37,8 @@ @NonNullByDefault public class PushoverActions implements ThingActions { + private static final String DEFAULT_EMERGENCY_PRIORITY = "2"; + private final Logger logger = LoggerFactory.getLogger(PushoverActions.class); private @NonNullByDefault({}) PushoverAccountHandler accountHandler; @@ -107,7 +110,7 @@ public static Boolean sendMonospaceMessage(ThingActions actions, String message, @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title, @ActionInput(name = "attachment", label = "@text/sendMessageActionInputAttachmentLabel", description = "@text/sendMessageActionInputAttachmentDescription", type = "java.lang.String", required = true) String attachment, - @ActionInput(name = "contentType", label = "@text/sendMessageActionInputContentTypeLabel", description = "@text/sendMessageActionInputContentTypeDescription", type = "java.lang.String") @Nullable String contentType) { + @ActionInput(name = "contentType", label = "@text/sendMessageActionInputContentTypeLabel", description = "@text/sendMessageActionInputContentTypeDescription", type = "java.lang.String", defaultValue = DEFAULT_CONTENT_TYPE) @Nullable String contentType) { logger.trace( "ThingAction 'sendAttachmentMessage' called with value(s): message='{}', title='{}', attachment='{}', contentType='{}'", message, title, attachment, contentType); @@ -131,10 +134,11 @@ public static Boolean sendAttachmentMessage(ThingActions actions, String message public @ActionOutput(name = "receipt", label = "@text/sendPriorityMessageActionOutputLabel", description = "@text/sendPriorityMessageActionOutputDescription", type = "java.lang.String") String sendPriorityMessage( @ActionInput(name = "message", label = "@text/sendMessageActionInputMessageLabel", description = "@text/sendMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message, @ActionInput(name = "title", label = "@text/sendMessageActionInputTitleLabel", description = "@text/sendMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title, - @ActionInput(name = "priority", label = "@text/sendMessageActionInputPriorityLabel", description = "@text/sendMessageActionInputPriorityDescription", required = true) int priority) { + @ActionInput(name = "priority", label = "@text/sendMessageActionInputPriorityLabel", description = "@text/sendMessageActionInputPriorityDescription", type = "java.lang.Integer", defaultValue = DEFAULT_EMERGENCY_PRIORITY) @Nullable Integer priority) { logger.trace("ThingAction 'sendPriorityMessage' called with value(s): message='{}', title='{}', priority='{}'", message, title, priority); - PushoverMessageBuilder builder = getDefaultPushoverMessageBuilder(message).withPriority(priority); + PushoverMessageBuilder builder = getDefaultPushoverMessageBuilder(message) + .withPriority(priority == null ? EMERGENCY_PRIORITY : priority.intValue()); if (title != null) { builder.withTitle(title); @@ -143,7 +147,7 @@ public static Boolean sendAttachmentMessage(ThingActions actions, String message } public static String sendPriorityMessage(ThingActions actions, String message, @Nullable String title, - int priority) { + @Nullable Integer priority) { return ((PushoverActions) actions).sendPriorityMessage(message, title, priority); } diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java index 968ffeed50380..83f7eaf6e90ea 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.pushover.internal.connection; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -151,13 +150,8 @@ private String buildURL(String url, Map requestParams) { .collect(Collectors.joining("&", url + "?", "")); } - private String encodeParam(String value) { - try { - return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); - } catch (UnsupportedEncodingException e) { - logger.debug("UnsupportedEncodingException occurred during execution: {}", e.getLocalizedMessage(), e); - return ""; - } + private String encodeParam(@Nullable String value) { + return value == null ? "" : URLEncoder.encode(value, StandardCharsets.UTF_8); } private @Nullable String getFromCache(String url) { diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java index 968f5c28b6c08..44d26aa5a1439 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverCommunicationException.java @@ -13,6 +13,7 @@ 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. @@ -36,7 +37,7 @@ public PushoverCommunicationException() { * * @param message Detail message */ - public PushoverCommunicationException(String message) { + public PushoverCommunicationException(@Nullable String message) { super(message); } @@ -45,7 +46,7 @@ public PushoverCommunicationException(String message) { * * @param cause The cause */ - public PushoverCommunicationException(Throwable cause) { + public PushoverCommunicationException(@Nullable Throwable cause) { super(cause); } @@ -55,7 +56,7 @@ public PushoverCommunicationException(Throwable cause) { * @param message Detail message * @param cause The cause */ - public PushoverCommunicationException(String message, Throwable cause) { + public PushoverCommunicationException(@Nullable String message, @Nullable Throwable cause) { super(message, cause); } } diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java index 005ce2c05c028..f0d456356efed 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java @@ -62,7 +62,7 @@ public class PushoverMessageBuilder { private static final int MAX_EXPIRE_SECONDS = 10800; private static final int MAX_URL_LENGTH = 512; private static final int MAX_URL_TITLE_LENGTH = 100; - private static final String DEFAULT_CONTENT_TYPE = "image/jpeg"; + public static final String DEFAULT_CONTENT_TYPE = "image/jpeg"; private final MultiPartContentProvider body = new MultiPartContentProvider(); diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml index fe2d3fb0b2e8d..d3a2697e1445a 100644 --- a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml @@ -17,12 +17,12 @@ - The default title of the message. + The default title of a message. openHAB - The default format of the message. + The default format of a message. none @@ -71,7 +71,7 @@ true - The expire parameter specifies how long your notification will continue to be retried for. + The expire parameter specifies how long your notification will continue to be retried. 3600 diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties index 17db7cd05371e..c6a3bf9a84703 100644 --- a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover.properties @@ -37,7 +37,7 @@ sendPriorityMessageActionDescription = This method is used to send a priority me sendPriorityMessageActionOutputLabel = Receipt sendPriorityMessageActionOutputDescription = Receipt, if priority message sent successfully. sendMessageActionInputPriorityLabel = Priority -sendMessageActionInputPriorityDescription = Priority to be used. +sendMessageActionInputPriorityDescription = Priority to be used. Defaults to 2. cancelPriorityMessageActionLabel = cancel a priority message cancelPriorityMessageActionDescription = This method is used to cancel a priority message. diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties index 8a3f17bd48da3..bcc86ce12c8a0 100644 --- a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/i18n/pushover_de.properties @@ -60,7 +60,7 @@ sendPriorityMessageActionDescription = Action zum Versenden einer Priorit sendPriorityMessageActionOutputLabel = Receipt sendPriorityMessageActionOutputDescription = ID der Prioritätsnachricht, wenn diese erfolgreich versendet wurde. sendMessageActionInputPriorityLabel = Priorität -sendMessageActionInputPriorityDescription = Die Priorität. +sendMessageActionInputPriorityDescription = Die Priorität. Default: 2. cancelPriorityMessageActionLabel = eine Prioritätsnachricht annullieren cancelPriorityMessageActionDescription = Action zum Annullieren einer Prioritätsnachricht. From ace20af50e86750ad0209089578f2350e058ce75 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sat, 21 Nov 2020 17:21:16 +0100 Subject: [PATCH 3/5] Fixed ConfigOptionProvider for sounds Signed-off-by: Christoph Weitkamp --- .../config/PushoverConfigOptionProvider.java | 69 +++++++++++++++++++ .../binding/pushover/internal/dto/Sound.java | 8 +++ .../handler/PushoverAccountHandler.java | 34 ++++----- .../main/resources/OH-INF/config/config.xml | 26 ------- 4 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverConfigOptionProvider.java diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverConfigOptionProvider.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverConfigOptionProvider.java new file mode 100644 index 0000000000000..fc76eab23553b --- /dev/null +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/config/PushoverConfigOptionProvider.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2020 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.config; + +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*; + +import java.net.URI; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pushover.internal.dto.Sound; +import org.openhab.binding.pushover.internal.handler.PushoverAccountHandler; +import org.openhab.core.config.core.ConfigOptionProvider; +import org.openhab.core.config.core.ParameterOption; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link PushoverConfigOptionProvider} class contains fields mapping thing configuration parameters. + * + * @author Christoph Weitkamp - Initial contribution + */ +@NonNullByDefault +@Component(service = ConfigOptionProvider.class) +public class PushoverConfigOptionProvider implements ConfigOptionProvider, ThingHandlerService { + + private @Nullable PushoverAccountHandler accountHandler; + + @Override + public @Nullable Collection getParameterOptions(URI uri, String param, @Nullable String context, + @Nullable Locale locale) { + if (accountHandler != null && PUSHOVER_ACCOUNT.getAsString().equals(uri.getSchemeSpecificPart()) + && CONFIG_SOUND.equals(param)) { + List sounds = accountHandler.getSounds(); + if (!sounds.isEmpty()) { + return sounds.stream().map(Sound::getAsParameterOption) + .sorted(Comparator.comparing(ParameterOption::getLabel)) + .collect(Collectors.toUnmodifiableList()); + } + } + return null; + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + this.accountHandler = (PushoverAccountHandler) handler; + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return accountHandler; + } +} diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java index 2a6c02a9e7616..fb326c690bbe6 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/dto/Sound.java @@ -12,11 +12,15 @@ */ package org.openhab.binding.pushover.internal.dto; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.core.ParameterOption; + /** * The {@link Sound} is the Java class used to map the JSON response to an Pushover API request.. * * @author Christoph Weitkamp - Initial contribution */ +@NonNullByDefault public class Sound { public String sound; public String label; @@ -25,4 +29,8 @@ public Sound(String sound, String label) { this.sound = sound; this.label = label; } + + public ParameterOption getAsParameterOption() { + return new ParameterOption(sound, label); + } } diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java index 42ff984e8df53..7ddd3e5588df6 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java @@ -12,25 +12,23 @@ */ package org.openhab.binding.pushover.internal.handler; -import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*; +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.DEFAULT_SOUND; -import java.net.URI; import java.util.Collection; -import java.util.Locale; +import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.pushover.internal.actions.PushoverActions; import org.openhab.binding.pushover.internal.config.PushoverAccountConfiguration; +import org.openhab.binding.pushover.internal.config.PushoverConfigOptionProvider; 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.core.config.core.ConfigOptionProvider; -import org.openhab.core.config.core.ParameterOption; +import org.openhab.binding.pushover.internal.dto.Sound; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -45,11 +43,10 @@ * @author Christoph Weitkamp - Initial contribution */ @NonNullByDefault -public class PushoverAccountHandler extends BaseThingHandler implements ConfigOptionProvider { +public class PushoverAccountHandler extends BaseThingHandler { private static final Collection> SUPPORTED_THING_ACTIONS = Set - .of(PushoverActions.class); - private static final String URI_STR = "thing-type:pushover:pushover-account"; + .of(PushoverActions.class, PushoverConfigOptionProvider.class); private final HttpClient httpClient; @@ -102,6 +99,15 @@ public Collection> getServices() { return SUPPORTED_THING_ACTIONS; } + /** + * Retrieves the list of current sounds and their descriptions from the Pushover API. + * + * @return a list of {@link Sound}s + */ + public List getSounds() { + return connection != null ? connection.getSounds() : List.of(); + } + /** * Returns a preconfigured {@link PushoverMessageBuilder}. * @@ -169,14 +175,4 @@ public boolean cancelPriorityMessage(String receipt) { throw new IllegalArgumentException("PushoverAPIConnection is null!"); } } - - @Override - public @Nullable Collection getParameterOptions(URI uri, String param, @Nullable String context, - @Nullable Locale locale) { - if (URI_STR.equals(uri.toString()) && CONFIG_SOUND.equals(param) && connection != null) { - return connection.getSounds().stream().map(s -> new ParameterOption(s.sound, s.label)) - .collect(Collectors.toUnmodifiableSet()); - } - return null; - } } diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml index d3a2697e1445a..8e74c72613cf8 100644 --- a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/config/config.xml @@ -34,32 +34,6 @@ The default notification sound on target device. default - - - - - - - - - - - - - - - - - - - - - - - - - - true From bb6ecd2d461d1c78bb2798dc4326ce72f09cb51f Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Tue, 24 Nov 2020 16:44:17 +0100 Subject: [PATCH 4/5] Incorporated comments from review Signed-off-by: Christoph Weitkamp --- .../internal/PushoverBindingConstants.java | 2 - .../connection/PushoverAPIConnection.java | 38 ++----------------- .../connection/PushoverMessageBuilder.java | 4 -- .../factory/PushoverHandlerFactory.java | 6 +-- .../handler/PushoverAccountHandler.java | 15 -------- .../main/resources/OH-INF/binding/binding.xml | 1 - .../internal/actions/PushoverActionsTest.java | 6 --- 7 files changed, 6 insertions(+), 66 deletions(-) diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java index 2b4f252f63d16..0ad29960ea25b 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/PushoverBindingConstants.java @@ -26,8 +26,6 @@ public class PushoverBindingConstants { private static final String BINDING_ID = "pushover"; public static final ThingTypeUID PUSHOVER_ACCOUNT = new ThingTypeUID(BINDING_ID, "pushover-account"); - public static final ThingTypeUID PUSHOVER_GROUP = new ThingTypeUID(BINDING_ID, "pushover-group"); - public static final ThingTypeUID PUSHOVER_USER = new ThingTypeUID(BINDING_ID, "pushover-user"); public static final String CONFIG_SOUND = "sound"; diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java index 83f7eaf6e90ea..4d75a60f7681a 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverAPIConnection.java @@ -68,61 +68,28 @@ public PushoverAPIConnection(HttpClient httpClient, PushoverAccountConfiguration this.config = config; } - /** - * - * @return - * @throws PushoverCommunicationException - * @throws PushoverConfigurationException - */ public boolean validateUser() throws PushoverCommunicationException, PushoverConfigurationException { return getMessageStatus( post(VALIDATE_URL, PushoverMessageBuilder.getInstance(config.apikey, config.user).build())); } - /** - * - * @param message - * @return - * @throws PushoverCommunicationException - * @throws PushoverConfigurationException - */ public boolean sendMessage(PushoverMessageBuilder message) throws PushoverCommunicationException, PushoverConfigurationException { return getMessageStatus(post(MESSAGE_URL, message.build())); } - /** - * - * @param message - * @return - * @throws PushoverCommunicationException - * @throws PushoverConfigurationException - */ public String sendPriorityMessage(PushoverMessageBuilder message) throws PushoverCommunicationException, PushoverConfigurationException { final JsonObject json = parser.parse(post(MESSAGE_URL, message.build())).getAsJsonObject(); return getMessageStatus(json) && json.has("receipt") ? json.get("receipt").getAsString() : ""; } - /** - * - * @param receipt - * @return - * @throws PushoverCommunicationException - * @throws PushoverConfigurationException - */ public boolean cancelPriorityMessage(String receipt) throws PushoverCommunicationException, PushoverConfigurationException { return getMessageStatus(post(CANCEL_MESSAGE_URL.replace("{receipt}", receipt), PushoverMessageBuilder.getInstance(config.apikey, config.user).build())); } - /** - * - * @return - * @throws PushoverCommunicationException - * @throws PushoverConfigurationException - */ public List getSounds() throws PushoverCommunicationException, PushoverConfigurationException { final String localApikey = config.apikey; if (localApikey == null || localApikey.isEmpty()) { @@ -195,9 +162,12 @@ private String executeRequest(HttpMethod httpMethod, String url, @Nullable Conte logger.debug("Pushover server responded with status code {}: {}", httpStatus, content); throw new PushoverCommunicationException(content); } - } catch (ExecutionException | InterruptedException | TimeoutException e) { + } catch (ExecutionException e) { logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e); throw new PushoverCommunicationException(e.getLocalizedMessage(), e.getCause()); + } catch (InterruptedException | TimeoutException e) { + logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e); + throw new PushoverCommunicationException(e.getLocalizedMessage()); } } diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java index f0d456356efed..878b12ab97049 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/connection/PushoverMessageBuilder.java @@ -184,9 +184,6 @@ public ContentProvider build() { if (device.length() > MAX_DEVICE_LENGTH) { logger.warn("Skip 'device' as it is longer than {} characters. Got: {}.", MAX_DEVICE_LENGTH, device); } else { - // TODO [A-Za-z0-9_-] - // TODO Messages may be addressed to multiple specific devices by joining them with a comma (such as - // device=iphone,nexus5) body.addFieldPart(MESSAGE_KEY_DEVICE, new StringContentProvider(device), null); } } @@ -238,7 +235,6 @@ public ContentProvider build() { } if (sound != null) { - // TODO validate sound body.addFieldPart(MESSAGE_KEY_SOUND, new StringContentProvider(sound), null); } diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java index 83bc7ec2979d5..dfdd4b95a5b04 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/factory/PushoverHandlerFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.pushover.internal.factory; -import static org.openhab.binding.pushover.internal.PushoverBindingConstants.*; +import static org.openhab.binding.pushover.internal.PushoverBindingConstants.PUSHOVER_ACCOUNT; import java.util.Set; @@ -40,7 +40,7 @@ @NonNullByDefault public class PushoverHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(PUSHOVER_ACCOUNT, PUSHOVER_USER); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(PUSHOVER_ACCOUNT); private final HttpClient httpClient; @@ -60,8 +60,6 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { if (PUSHOVER_ACCOUNT.equals(thingTypeUID)) { return new PushoverAccountHandler(thing, httpClient); - } else if (PUSHOVER_USER.equals(thingTypeUID)) { - return null; } return null; diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java index 7ddd3e5588df6..b54e81917ba86 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java @@ -137,11 +137,6 @@ public PushoverMessageBuilder getDefaultPushoverMessageBuilder(String message) { return builder; } - /** - * - * @param messageBuilder - * @return - */ public boolean sendMessage(PushoverMessageBuilder messageBuilder) { if (connection != null) { return connection.sendMessage(messageBuilder); @@ -150,11 +145,6 @@ public boolean sendMessage(PushoverMessageBuilder messageBuilder) { } } - /** - * - * @param messageBuilder - * @return - */ public String sendPriorityMessage(PushoverMessageBuilder messageBuilder) { if (connection != null) { return connection.sendPriorityMessage(messageBuilder); @@ -163,11 +153,6 @@ public String sendPriorityMessage(PushoverMessageBuilder messageBuilder) { } } - /** - * - * @param receipt - * @return - */ public boolean cancelPriorityMessage(String receipt) { if (connection != null) { return connection.cancelPriorityMessage(receipt); diff --git a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml index 58dd8ca7239f5..6ed0642794f46 100644 --- a/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.pushover/src/main/resources/OH-INF/binding/binding.xml @@ -5,6 +5,5 @@ Pushover Binding Pushover - Simple Notifications. - Christoph Weitkamp diff --git a/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java b/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java index 47e4185bed20d..ba432207cc336 100644 --- a/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java +++ b/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java @@ -20,7 +20,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -30,7 +29,6 @@ import org.mockito.quality.Strictness; import org.openhab.binding.pushover.internal.connection.PushoverMessageBuilder; import org.openhab.binding.pushover.internal.handler.PushoverAccountHandler; -import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.ThingActions; import org.openhab.core.thing.binding.ThingHandler; @@ -48,8 +46,6 @@ public class PushoverActionsTest { private static final String URL = "https://www.test.com"; private static final String URL_TITLE = "Some Link"; private static final String RECEIPT = "12345"; - private static final String SUCCESSFULL_RESPONSE = "{\"status\":1,\"request\":\"647d2300-702c-4b38-8b2f-d56326ae460b\"}"; - private static final String FAILED_RESPONSE = "{\"user\":\"invalid\",\"errors\":[\"user identifier is invalid\"],\"status\":0,\"request\":\"5042853c-402d-4a18-abcb-168734a801de\"}"; @NonNullByDefault private final ThingActions thingActionsStub = new ThingActions() { @@ -63,8 +59,6 @@ public void setThingHandler(ThingHandler handler) { } }; - private @Mock Thing mockThing; - private @Mock HttpClient mockHttpClient; private @Mock PushoverAccountHandler mockPushoverAccountHandler; private PushoverActions pushoverThingActions; From 128709ef9aec022a9a11ded8f14f88c29ec85fbc Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Thu, 26 Nov 2020 20:12:59 +0100 Subject: [PATCH 5/5] Incorporated comments from review Signed-off-by: Christoph Weitkamp --- .../handler/PushoverAccountHandler.java | 16 +++++--- .../internal/actions/PushoverActionsTest.java | 39 ------------------- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java index b54e81917ba86..1fa8e075a0b7d 100644 --- a/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java +++ b/bundles/org.openhab.binding.pushover/src/main/java/org/openhab/binding/pushover/internal/handler/PushoverAccountHandler.java @@ -85,12 +85,7 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); connection = new PushoverAPIConnection(httpClient, config); - try { - connection.validateUser(); - updateStatus(ThingStatus.ONLINE); - } catch (PushoverCommunicationException | PushoverConfigurationException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); - } + scheduler.submit(this::asyncValidateUser); } } @@ -160,4 +155,13 @@ public boolean cancelPriorityMessage(String receipt) { throw new IllegalArgumentException("PushoverAPIConnection is null!"); } } + + private void asyncValidateUser() { + try { + connection.validateUser(); + updateStatus(ThingStatus.ONLINE); + } catch (PushoverCommunicationException | PushoverConfigurationException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + } + } } diff --git a/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java b/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java index ba432207cc336..423087bc5d6dd 100644 --- a/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java +++ b/bundles/org.openhab.binding.pushover/src/test/java/org/openhab/binding/pushover/internal/actions/PushoverActionsTest.java @@ -85,13 +85,6 @@ public void testSendMessageThingHandlerIsNull() { assertThrows(RuntimeException.class, () -> PushoverActions.sendMessage(pushoverThingActions, MESSAGE, TITLE)); } - // @Test - // public void testSendMessageWithoutMessage() { - // Optional message = Optional.of(null); - // assertThrows(IllegalArgumentException.class, - // () -> PushoverActions.sendMessage(pushoverThingActions, message.get(), TITLE)); - // } - @Test public void testSendMessageWithoutTitle() { pushoverThingActions.setThingHandler(mockPushoverAccountHandler); @@ -119,22 +112,6 @@ public void testSendURLMessageThingHandlerIsNull() { () -> PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, TITLE, URL, URL_TITLE)); } - // @Test - // public void testSendURLMessageWithoutMessage() { - // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); - // Optional message = Optional.of(null); - // assertThrows(IllegalArgumentException.class, - // () -> PushoverActions.sendURLMessage(pushoverThingActions, message.get(), TITLE, URL, URL_TITLE)); - // } - - // @Test - // public void testSendURLMessageWithoutURL() { - // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); - // Optional url = Optional.of(null); - // assertThrows(IllegalArgumentException.class, - // () -> PushoverActions.sendURLMessage(pushoverThingActions, MESSAGE, TITLE, url.get(), URL_TITLE)); - // } - @Test public void testSendURLMessageWithoutTitle() { pushoverThingActions.setThingHandler(mockPushoverAccountHandler); @@ -169,14 +146,6 @@ public void testSendPriorityMessageThingHandlerIsNull() { TITLE, PushoverMessageBuilder.EMERGENCY_PRIORITY)); } - // @Test - // public void testSendPriorityMessageWithoutMessage() { - // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); - // Optional message = Optional.of(null); - // assertThrows(IllegalArgumentException.class, () -> PushoverActions.sendPriorityMessage(pushoverThingActions, - // message.get(), TITLE, PushoverMessageBuilder.EMERGENCY_PRIORITY)); - // } - @Test public void testSendPriorityMessageWithoutTitle() { pushoverThingActions.setThingHandler(mockPushoverAccountHandler); @@ -205,14 +174,6 @@ public void testCancelPriorityMessageThingHandlerIsNull() { () -> PushoverActions.cancelPriorityMessage(pushoverThingActions, RECEIPT)); } - // @Test - // public void testCancelPriorityMessageWithoutReceipt() { - // pushoverThingActions.setThingHandler(mockPushoverAccountHandler); - // Optional receipt = Optional.of(null); - // assertThrows(IllegalArgumentException.class, - // () -> PushoverActions.cancelPriorityMessage(pushoverThingActions, receipt.get())); - // } - @Test public void testCancelPriorityMessageWithValidReceipt() { pushoverThingActions.setThingHandler(mockPushoverAccountHandler);