70: Refactor the notification framework into transport-independent notification services
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
package com.r35157.libs.notification;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface AddressedNotifier<
|
||||
D extends NotificationDestination,
|
||||
M extends NotificationMessage>
|
||||
{
|
||||
void push(D destination, M message) throws IOException;
|
||||
|
||||
default BoundNotifier<M> bind(D destination) {
|
||||
return message -> push(destination, message);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.r35157.libs.notification;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface BoundNotifier<M extends NotificationMessage> {
|
||||
void push(M message) throws IOException;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.r35157.libs.notification;
|
||||
|
||||
public interface NotificationDestination {}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.r35157.libs.notification;
|
||||
|
||||
public interface NotificationMessage {
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.discord;
|
||||
|
||||
import com.r35157.libs.notification.NotificationMessage;
|
||||
|
||||
public record DiscordMessage(String text) implements NotificationMessage {
|
||||
public DiscordMessage {
|
||||
if (text == null || text.isBlank()) {
|
||||
throw new IllegalArgumentException("Discord message cannot be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.discord;
|
||||
|
||||
import com.r35157.libs.notification.BoundNotifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.http.HttpClient;
|
||||
|
||||
public class DiscordNotifier implements BoundNotifier<DiscordMessage> {
|
||||
|
||||
public DiscordNotifier(URL discordWebhookUrl) {
|
||||
this.httpClient = HttpClient.newHttpClient();
|
||||
this.discordWebhookUrl = discordWebhookUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(DiscordMessage message) throws IOException {
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
private void test() throws IOException {
|
||||
URL discordWebhookUrl = null;
|
||||
|
||||
BoundNotifier<DiscordMessage> boundNotifier = new DiscordNotifier(discordWebhookUrl);
|
||||
|
||||
DiscordMessage message = new DiscordMessage("Hello World!");
|
||||
boundNotifier.push(message);
|
||||
}
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final URL discordWebhookUrl;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.pushover;
|
||||
|
||||
import com.r35157.libs.notification.NotificationMessage;
|
||||
|
||||
public record PushMessage(String text) implements NotificationMessage {
|
||||
public PushMessage {
|
||||
if (text == null || text.isBlank()) {
|
||||
throw new IllegalArgumentException("Push message cannot be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.smtp;
|
||||
|
||||
public record EmailBody(String text) {
|
||||
public EmailBody {
|
||||
if (text == null || text.isBlank()) {
|
||||
throw new IllegalArgumentException("Email body cannot be blank!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.smtp;
|
||||
|
||||
import com.r35157.libs.notification.NotificationMessage;
|
||||
|
||||
public record EmailMessage(
|
||||
EmailSubject subject,
|
||||
EmailBody body
|
||||
) implements NotificationMessage {
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.smtp;
|
||||
|
||||
public record EmailSubject(String text) {
|
||||
public EmailSubject {
|
||||
if (text == null || text.isBlank()) {
|
||||
throw new IllegalArgumentException("Email subject cannot be blank!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.smtp;
|
||||
|
||||
import com.r35157.libs.notification.NotificationDestination;
|
||||
|
||||
public record SMTPDestination(ΩEmailAddressΩ emailAddress) implements NotificationDestination { }
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.r35157.libs.notification.impl.smtp;
|
||||
|
||||
import com.r35157.libs.notification.AddressedNotifier;
|
||||
import com.r35157.libs.notification.BoundNotifier;
|
||||
import com.r35157.libs.valuetypes.basic.Credentials;
|
||||
import com.r35157.libs.valuetypes.basic.NetworkEndPoint;
|
||||
import com.r35157.libs.valuetypes.basic.SmtpConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SMTPNotifier implements AddressedNotifier<SMTPDestination, EmailMessage> {
|
||||
|
||||
public SMTPNotifier(SmtpConfiguration smtpConfiguration) {
|
||||
this.smtpConfiguration = smtpConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(SMTPDestination destination, EmailMessage message) throws IOException {
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
private void test() throws IOException {
|
||||
ΩHostnameΩ hostName = new ΩHostnameΩ("SomeSMTPHost");
|
||||
ΩportNumberΩ portNumber = 2525;
|
||||
NetworkEndPoint networkEndPoint = new NetworkEndPoint(hostName, portNumber);
|
||||
|
||||
ΩUserNameΩ userName = "SomeUser";
|
||||
ΩPasswordΩ password = "SomePassword";
|
||||
Credentials credentials = new Credentials(userName, password);
|
||||
|
||||
SmtpConfiguration smtpConfiguration = new SmtpConfiguration(networkEndPoint, credentials);
|
||||
AddressedNotifier<SMTPDestination, EmailMessage> notifier = new SMTPNotifier(smtpConfiguration);
|
||||
|
||||
ΩEmailAddressΩ emailAddress = "SomeReceiver";
|
||||
SMTPDestination destination = new SMTPDestination(emailAddress);
|
||||
BoundNotifier<EmailMessage> boundNotifier = notifier.bind(destination);
|
||||
|
||||
EmailSubject subject = new EmailSubject("SomeSubject");
|
||||
EmailBody body = new EmailBody("SomeBody");
|
||||
EmailMessage message = new EmailMessage(subject, body);
|
||||
boundNotifier.push(message);
|
||||
}
|
||||
|
||||
private final SmtpConfiguration smtpConfiguration;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.r35157.service.notification;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Sends notification messages to explicitly supplied destinations.
|
||||
*
|
||||
* @param <D> transport-specific destination configuration
|
||||
*/
|
||||
public interface AddressedNotificationService<
|
||||
D extends NotificationDestination>
|
||||
{
|
||||
/**
|
||||
* Sends a notification message to a destination.
|
||||
*
|
||||
* @param destination the destination configuration
|
||||
* @param message the non-blank notification text
|
||||
* @throws IllegalArgumentException if {@code message} is {@code null},
|
||||
* empty, or blank
|
||||
* @throws IOException if synchronous notification delivery fails
|
||||
*/
|
||||
void push(
|
||||
@NotNull D destination,
|
||||
@NotNull ΩNotificationMessageΩ message
|
||||
) throws IOException;
|
||||
|
||||
/**
|
||||
* Binds a destination into a transport-independent notification service.
|
||||
*
|
||||
* @param destination the destination configuration to capture
|
||||
* @return a service that requires only a notification message
|
||||
* @throws NullPointerException if {@code destination} is {@code null}
|
||||
*/
|
||||
default @NotNull BoundNotificationService bind(@NotNull D destination) {
|
||||
Objects.requireNonNull(destination, "destination");
|
||||
return message -> push(destination, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.r35157.service.notification;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Sends notification messages through a fully configured transport and
|
||||
* destination.
|
||||
*
|
||||
* <p>Consumers of this interface do not need to know which transport is used
|
||||
* or how its destination is configured.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BoundNotificationService {
|
||||
/**
|
||||
* Sends a notification message.
|
||||
*
|
||||
* @param message the non-blank notification text
|
||||
* @throws IllegalArgumentException if {@code message} is {@code null},
|
||||
* empty, or blank
|
||||
* @throws IOException if synchronous notification delivery fails
|
||||
*/
|
||||
void push(@NotNull ΩNotificationMessageΩ message) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.r35157.service.notification;
|
||||
|
||||
/**
|
||||
* Marks transport-specific destination configuration that can be bound into a
|
||||
* {@link BoundNotificationService}.
|
||||
*/
|
||||
public interface NotificationDestination {
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.r35157.service.notification.impl.discord;
|
||||
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.http.HttpClient;
|
||||
|
||||
/**
|
||||
* Placeholder for bound Discord webhook notification delivery.
|
||||
*/
|
||||
public class DiscordNotificationService implements BoundNotificationService {
|
||||
|
||||
/**
|
||||
* Creates a service bound to a Discord webhook URL.
|
||||
*
|
||||
* @param discordWebhookUrl Discord webhook URL
|
||||
*/
|
||||
public DiscordNotificationService(URL discordWebhookUrl) {
|
||||
this.httpClient = HttpClient.newHttpClient();
|
||||
this.discordWebhookUrl = discordWebhookUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(@NotNull ΩNotificationMessageΩ message) throws IOException {
|
||||
validateDiscordMessage(message);
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
private static void validateDiscordMessage(ΩNotificationMessageΩ message) {
|
||||
if (message == null || message.isBlank()) {
|
||||
throw new IllegalArgumentException("Notification message cannot be blank");
|
||||
}
|
||||
}
|
||||
|
||||
private void test() throws IOException {
|
||||
URL discordWebhookUrl = null;
|
||||
|
||||
BoundNotificationService notificationService =
|
||||
new DiscordNotificationService(discordWebhookUrl);
|
||||
|
||||
ΩNotificationMessageΩ message = "Hello World!";
|
||||
notificationService.push(message);
|
||||
}
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final URL discordWebhookUrl;
|
||||
}
|
||||
+29
-9
@@ -1,6 +1,7 @@
|
||||
package com.r35157.libs.notification.impl.pushover;
|
||||
package com.r35157.service.notification.impl.pushover;
|
||||
|
||||
import com.r35157.libs.notification.BoundNotifier;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -10,20 +11,32 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class PushOverNotifier implements BoundNotifier<PushMessage> {
|
||||
/**
|
||||
* Sends bound notification messages through the Pushover Messages API.
|
||||
*/
|
||||
public class PushoverNotificationService implements BoundNotificationService {
|
||||
|
||||
public PushOverNotifier(ΩAPIKeyΩ token, ΩUserNameΩ userName) {
|
||||
/**
|
||||
* Creates a Pushover service bound to an application token and user or
|
||||
* group key.
|
||||
*
|
||||
* @param token Pushover application token
|
||||
* @param userName Pushover user or group key
|
||||
*/
|
||||
public PushoverNotificationService(ΩAPIKeyΩ token, ΩUserNameΩ userName) {
|
||||
this.httpClient = HttpClient.newHttpClient();
|
||||
this.token = token;
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(PushMessage message) throws IOException {
|
||||
public void push(@NotNull ΩNotificationMessageΩ message) throws IOException {
|
||||
validatePushoverMessage(message);
|
||||
|
||||
String body = formEncode(
|
||||
"token", token,
|
||||
"user", userName,
|
||||
"message", message.text()
|
||||
"message", message
|
||||
);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
@@ -46,6 +59,12 @@ public class PushOverNotifier implements BoundNotifier<PushMessage> {
|
||||
}
|
||||
}
|
||||
|
||||
private static void validatePushoverMessage(ΩNotificationMessageΩ message) {
|
||||
if (message == null || message.isBlank()) {
|
||||
throw new IllegalArgumentException("Notification message cannot be blank");
|
||||
}
|
||||
}
|
||||
|
||||
private static String formEncode(String... keyValues) {
|
||||
if (keyValues.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("keyValues must contain key/value pairs");
|
||||
@@ -74,10 +93,11 @@ public class PushOverNotifier implements BoundNotifier<PushMessage> {
|
||||
ΩAPIKeyΩ apiKey = "SomeKey";
|
||||
ΩUserNameΩ userName = "userNmae";
|
||||
|
||||
BoundNotifier<PushMessage> boundNotifier = new PushOverNotifier(apiKey, userName);
|
||||
BoundNotificationService notificationService =
|
||||
new PushoverNotificationService(apiKey, userName);
|
||||
|
||||
PushMessage message = new PushMessage("Hello World!");
|
||||
boundNotifier.push(message);
|
||||
ΩNotificationMessageΩ message = "Hello World!";
|
||||
notificationService.push(message);
|
||||
}
|
||||
|
||||
private static final URI PUSHOVER_MESSAGES_URI =
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.r35157.service.notification.impl.smtp;
|
||||
|
||||
import com.r35157.service.notification.NotificationDestination;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Complete SMTP destination configuration for a bound plain-text
|
||||
* notification service.
|
||||
*
|
||||
* @param emailAddress destination email address
|
||||
* @param subject fixed subject for every notification sent to the destination
|
||||
*/
|
||||
public record SMTPDestination(
|
||||
@NotNull ΩEmailAddressΩ emailAddress,
|
||||
@NotNull ΩEmailSubjectΩ subject
|
||||
) implements NotificationDestination {
|
||||
/**
|
||||
* Validates the complete SMTP destination binding.
|
||||
*
|
||||
* @throws IllegalArgumentException if the email address or subject is
|
||||
* {@code null}, empty, or blank
|
||||
*/
|
||||
public SMTPDestination {
|
||||
if (emailAddress == null || emailAddress.isBlank()) {
|
||||
throw new IllegalArgumentException("Email address cannot be blank");
|
||||
}
|
||||
if (subject == null || subject.isBlank()) {
|
||||
throw new IllegalArgumentException("Email subject cannot be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.r35157.service.notification.impl.smtp;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.Credentials;
|
||||
import com.r35157.libs.valuetypes.basic.NetworkEndPoint;
|
||||
import com.r35157.libs.valuetypes.basic.SmtpConfiguration;
|
||||
import com.r35157.service.notification.AddressedNotificationService;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Placeholder for SMTP notification delivery with destination and subject
|
||||
* binding.
|
||||
*/
|
||||
public class SMTPNotificationService
|
||||
implements AddressedNotificationService<SMTPDestination>
|
||||
{
|
||||
/**
|
||||
* Creates an SMTP notification service.
|
||||
*
|
||||
* @param smtpConfiguration SMTP transport configuration
|
||||
*/
|
||||
public SMTPNotificationService(SmtpConfiguration smtpConfiguration) {
|
||||
this.smtpConfiguration = smtpConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(
|
||||
@NotNull SMTPDestination destination,
|
||||
@NotNull ΩNotificationMessageΩ message
|
||||
) throws IOException {
|
||||
validateSmtpMessage(message);
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds an email address and fixed subject into a transport-independent
|
||||
* notification service.
|
||||
*
|
||||
* @param emailAddress destination email address
|
||||
* @param subject fixed subject for every notification
|
||||
* @return a service requiring only a notification message
|
||||
* @throws IllegalArgumentException if the address or subject is
|
||||
* {@code null}, empty, or blank
|
||||
*/
|
||||
public @NotNull BoundNotificationService bind(
|
||||
@NotNull ΩEmailAddressΩ emailAddress,
|
||||
@NotNull ΩEmailSubjectΩ subject
|
||||
) {
|
||||
return bind(new SMTPDestination(emailAddress, subject));
|
||||
}
|
||||
|
||||
private static void validateSmtpMessage(ΩNotificationMessageΩ message) {
|
||||
if (message == null || message.isBlank()) {
|
||||
throw new IllegalArgumentException("Notification message cannot be blank");
|
||||
}
|
||||
}
|
||||
|
||||
private void test() throws IOException {
|
||||
ΩHostnameΩ hostName = new ΩHostnameΩ("SomeSMTPHost");
|
||||
ΩportNumberΩ portNumber = 2525;
|
||||
NetworkEndPoint networkEndPoint = new NetworkEndPoint(hostName, portNumber);
|
||||
|
||||
ΩUserNameΩ userName = "SomeUser";
|
||||
ΩPasswordΩ password = "SomePassword";
|
||||
Credentials credentials = new Credentials(userName, password);
|
||||
|
||||
SmtpConfiguration smtpConfiguration = new SmtpConfiguration(networkEndPoint, credentials);
|
||||
SMTPNotificationService smtpNotificationService =
|
||||
new SMTPNotificationService(smtpConfiguration);
|
||||
|
||||
ΩEmailAddressΩ emailAddress = "SomeReceiver";
|
||||
ΩEmailSubjectΩ subject = "SomeSubject";
|
||||
BoundNotificationService notificationService =
|
||||
smtpNotificationService.bind(emailAddress, subject);
|
||||
|
||||
ΩNotificationMessageΩ message = "SomeBody";
|
||||
notificationService.push(message);
|
||||
}
|
||||
|
||||
private final SmtpConfiguration smtpConfiguration;
|
||||
}
|
||||
Reference in New Issue
Block a user