From 94d430a5a56c69df954a2492f2d63683e06d3cc5 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 21 Jan 2026 18:04:09 +0100 Subject: [PATCH 1/5] refaturing message system, step 1: making message abstract and add TranslatableMessage and TranslatedMessage Signed-off-by: Stephan Richter --- .../messagebus/events/ProjectEvent.java | 3 - .../umbrella/messagebus/events/TaskEvent.java | 1 - .../umbrella/core/model/Envelope.java | 12 ++-- .../umbrella/core/model/Message.java | 69 +++++++++---------- .../core/model/TranslatableMessage.java | 14 ++++ .../core/model/TranslatedMessage.java | 50 ++++++++++++++ .../umbrella/documents/DocumentApi.java | 2 +- .../umbrella/message/MessageSystem.java | 33 ++++----- .../message/model/CombinedMessage.java | 11 +-- .../umbrella/translations/Translations.java | 1 - .../srsoftware/umbrella/user/UserModule.java | 4 +- 11 files changed, 125 insertions(+), 75 deletions(-) create mode 100644 core/src/main/java/de/srsoftware/umbrella/core/model/TranslatableMessage.java create mode 100644 core/src/main/java/de/srsoftware/umbrella/core/model/TranslatedMessage.java diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ProjectEvent.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ProjectEvent.java index 702ee70a..78534b3a 100644 --- a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ProjectEvent.java +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/ProjectEvent.java @@ -1,8 +1,6 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.messagebus.events; -import static de.srsoftware.umbrella.core.ModuleRegistry.projectService; -import static de.srsoftware.umbrella.core.ModuleRegistry.taskService; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.constants.Module.PROJECT; import static de.srsoftware.umbrella.core.model.Translatable.t; @@ -11,7 +9,6 @@ import static de.srsoftware.umbrella.messagebus.events.Event.EventType.MEMBER_AD import de.srsoftware.umbrella.core.constants.Field; import de.srsoftware.umbrella.core.model.*; import java.util.Collection; -import java.util.List; import java.util.Map; diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TaskEvent.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TaskEvent.java index 222c5bab..5b5e240d 100644 --- a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TaskEvent.java +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TaskEvent.java @@ -9,7 +9,6 @@ import static de.srsoftware.umbrella.core.constants.Module.TASK; import static de.srsoftware.umbrella.core.model.Translatable.t; import static de.srsoftware.umbrella.messagebus.events.Event.EventType.MEMBER_ADDED; -import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.constants.Field; import de.srsoftware.umbrella.core.model.*; import java.util.Collection; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java index ca537c72..14d96517 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java @@ -17,16 +17,16 @@ import java.util.stream.Collectors; import org.json.JSONArray; import org.json.JSONObject; -public class Envelope { - private final Message message; +public class Envelope { + private final Message message; private final Set receivers; private final LocalDateTime time; - public Envelope(Message message, User receiver){ + public Envelope(Message message, User receiver){ this(message,new HashSet<>(Set.of(receiver))); } - public Envelope(Message message, HashSet receivers) { + public Envelope(Message message, HashSet receivers) { this.message = message; this.receivers = receivers; time = LocalDateTime.now(); @@ -40,7 +40,7 @@ public class Envelope { */ public static Envelope from(JSONObject json) throws UmbrellaException { if (!json.has(RECEIVERS)) throw missingField(RECEIVERS); - var message = Message.from(json); + var message = TranslatedMessage.from(json); var obj = json.get(RECEIVERS); if (obj instanceof JSONObject) obj = new JSONArray(List.of(obj)); if (!(obj instanceof JSONArray receiverList)) throw invalidField(RECEIVERS, t(JSONARRAY)); @@ -67,7 +67,7 @@ public class Envelope { return receivers.contains(receiver); } - public Message message(){ + public Message message(){ return message; } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Message.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Message.java index bd79cca2..f4e174cf 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Message.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Message.java @@ -1,50 +1,35 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.model; -import static de.srsoftware.tools.Optionals.isSet; -import static de.srsoftware.umbrella.core.constants.Constants.JSONOBJECT; -import static de.srsoftware.umbrella.core.constants.Field.*; -import static de.srsoftware.umbrella.core.constants.Text.STRING; -import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; -import static de.srsoftware.umbrella.core.model.Translatable.t; import static java.text.MessageFormat.format; -import de.srsoftware.tools.Mappable; -import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import java.util.*; -import org.json.JSONArray; -import org.json.JSONObject; +import java.util.Collection; +import java.util.Objects; -public record Message(UmbrellaUser sender, Translatable subject, Translatable body, List attachments) { - @Override - public boolean equals(Object o) { - if (!(o instanceof Message message)) return false; - return Objects.equals(sender, message.sender) && Objects.equals(subject, message.subject) && Objects.equals(body, message.body) && Objects.equals(attachments, message.attachments); +public abstract class Message { + private final Collection attachments; + private final T body, subject; + private final UmbrellaUser sender; + + public Message(UmbrellaUser sender, T subject, T body, Collection attachments){ + this.sender = sender; + this.subject = subject; + this.body = body; + this.attachments = attachments; } - public static Message from(JSONObject json) throws UmbrellaException { - for (var key : Set.of(SENDER, SUBJECT, BODY)) { - if (!json.has(key)) throw missingField(key); - } - if (!(json.get(SENDER) instanceof JSONObject senderObject)) throw invalidField(SENDER, t(JSONOBJECT)); - if (!(json.get(SUBJECT) instanceof String subject && isSet(subject))) throw invalidField(SUBJECT,t(STRING)); - if (!(json.get(BODY) instanceof String body && isSet(body))) throw invalidField(BODY,t(STRING)); + public Collection attachments(){ + return attachments; + } - var user = UmbrellaUser.of(senderObject); - if (!(user instanceof UmbrellaUser sender)) throw new UmbrellaException(400, t("Sender is not an umbrella user!")); - var attachments = new ArrayList(); - if (json.has(ATTACHMENTS)){ - var jsonAttachments = json.get(ATTACHMENTS); - if (jsonAttachments instanceof JSONObject obj) jsonAttachments = new JSONArray(List.of(obj)); - if (jsonAttachments instanceof JSONArray arr){ - for (var att : arr){ - if (!(att instanceof JSONObject o)) throw new UmbrellaException(400, t("Attachments contains entry that is not an object: {entry}","entry",att)); - var attachment = Attachment.of(o); - attachments.add(attachment); - } - } - } - return new Message(sender,new UnTranslatable(subject),new UnTranslatable(body),attachments); + public T body(){ + return body; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Message message)) return false; + return Objects.equals(sender, message.sender) && Objects.equals(subject, message.subject) && Objects.equals(body, message.body) && Objects.equals(attachments, message.attachments); } @Override @@ -52,6 +37,14 @@ public record Message(UmbrellaUser sender, Translatable subject, Translatable bo return Objects.hash(subject, body, attachments); } + public UmbrellaUser sender(){ + return sender; + } + + public T subject(){ + return subject; + } + @Override public String toString() { return format("{0}(from: {1}), subject: {2}",getClass().getSimpleName(),sender,subject); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/TranslatableMessage.java b/core/src/main/java/de/srsoftware/umbrella/core/model/TranslatableMessage.java new file mode 100644 index 00000000..339222aa --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/TranslatableMessage.java @@ -0,0 +1,14 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.model; + +import java.util.Collection; + +public class TranslatableMessage extends Message { + public TranslatableMessage(UmbrellaUser sender, Translatable subject, Translatable body, Collection attachments) { + super(sender, subject, body, attachments); + } + + public TranslatedMessage translate(String lang){ + return new TranslatedMessage(sender(),subject().translate(lang),body().translate(lang),attachments()); + } +} diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/TranslatedMessage.java b/core/src/main/java/de/srsoftware/umbrella/core/model/TranslatedMessage.java new file mode 100644 index 00000000..9ab6b0f5 --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/TranslatedMessage.java @@ -0,0 +1,50 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.model; + +import static de.srsoftware.tools.Optionals.isSet; +import static de.srsoftware.umbrella.core.constants.Constants.JSONOBJECT; +import static de.srsoftware.umbrella.core.constants.Field.*; +import static de.srsoftware.umbrella.core.constants.Field.ATTACHMENTS; +import static de.srsoftware.umbrella.core.constants.Text.STRING; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidField; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingField; +import static de.srsoftware.umbrella.core.model.Translatable.t; + +import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.json.JSONArray; +import org.json.JSONObject; + +public class TranslatedMessage extends Message { + public TranslatedMessage(UmbrellaUser sender, String subject, String body, Collection attachments) { + super(sender, subject, body, attachments); + } + + public static TranslatedMessage from(JSONObject json) throws UmbrellaException { + for (var key : Set.of(SENDER, SUBJECT, BODY)) { + if (!json.has(key)) throw missingField(key); + } + if (!(json.get(SENDER) instanceof JSONObject senderObject)) throw invalidField(SENDER, t(JSONOBJECT)); + if (!(json.get(SUBJECT) instanceof String subject && isSet(subject))) throw invalidField(SUBJECT,t(STRING)); + if (!(json.get(BODY) instanceof String body && isSet(body))) throw invalidField(BODY,t(STRING)); + + var user = UmbrellaUser.of(senderObject); + if (!(user instanceof UmbrellaUser sender)) throw new UmbrellaException(400, t("Sender is not an umbrella user!")); + var attachments = new ArrayList(); + if (json.has(ATTACHMENTS)){ + var jsonAttachments = json.get(ATTACHMENTS); + if (jsonAttachments instanceof JSONObject obj) jsonAttachments = new JSONArray(List.of(obj)); + if (jsonAttachments instanceof JSONArray arr){ + for (var att : arr){ + if (!(att instanceof JSONObject o)) throw new UmbrellaException(400, t("Attachments contains entry that is not an object: {entry}","entry",att)); + var attachment = Attachment.of(o); + attachments.add(attachment); + } + } + } + return new TranslatedMessage(sender,subject,body,attachments); + } +} diff --git a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java index e6d9d5b1..fbc96689 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java @@ -577,7 +577,7 @@ public class DocumentApi extends BaseHandler implements DocumentService { LOG.log(WARNING,e); } var attachment = new Attachment(doc.number()+".pdf",rendered.mimeType(),rendered.bytes()); - var message = new Message(user,new UnTranslatable(subject),new UnTranslatable(content),List.of(attachment)); + var message = new TranslatedMessage(user,subject,content,List.of(attachment)); var envelope = new Envelope(message,new User(doc.customer().shortName(),new EmailAddress(email),doc.customer().language())); postBox().send(envelope); db.save(doc.set(SENT)); diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java index 32261f32..ca8bcaec 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -25,10 +25,7 @@ import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.api.PostBox; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; -import de.srsoftware.umbrella.core.model.Envelope; -import de.srsoftware.umbrella.core.model.Token; -import de.srsoftware.umbrella.core.model.UmbrellaUser; -import de.srsoftware.umbrella.core.model.User; +import de.srsoftware.umbrella.core.model.*; import de.srsoftware.umbrella.message.model.*; import de.srsoftware.umbrella.messagebus.EventListener; import de.srsoftware.umbrella.messagebus.events.Event; @@ -44,7 +41,6 @@ import jakarta.mail.util.ByteArrayDataSource; import java.io.IOException; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; - import org.json.JSONArray; import org.json.JSONObject; @@ -52,7 +48,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener public static final System.Logger LOG = System.getLogger(MessageSystem.class.getSimpleName()); private final Timer timer = new Timer(); - private record Receiver(User user, de.srsoftware.umbrella.core.model.Message message){} + private record Receiver(User user, Message message){} private class SubmissionTask extends TimerTask{ @@ -176,7 +172,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener public void onEvent(Event event) { for (var user : event.audience()){ if (debugAddress != null && !debugAddress.equals(user.email().toString())) continue; - var message = new de.srsoftware.umbrella.core.model.Message(event.initiator(),event.subject(),event.describe(),null); + var message = new TranslatableMessage(event.initiator(),event.subject(),event.describe(),null); var envelope = new Envelope(message,user); send(envelope); } @@ -251,23 +247,24 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener if (scheduledHour != null) new SubmissionTask(scheduledHour).schedule(); } - private boolean sendMessage(HttpExchange ex, UmbrellaUser user, Envelope envelope) throws IOException { + private boolean sendMessage(HttpExchange ex, UmbrellaUser user, Envelope envelope) throws IOException { var message = envelope.message(); - var sender = message.sender().name(); - var subject = message.subject().translate(user.language()); - var body = message.body().translate(user.language()); + if (message instanceof TranslatableMessage tm) message = tm.translate(user.language()); return sendContent(ex,Map.of( - SENDER,sender, - SUBJECT,subject, - BODY,body + SENDER,message.sender(), + SUBJECT,message.subject(), + BODY,message.body() )); } - private static JSONObject summary(Envelope envelope, String lang) { - var sender = envelope.message().sender().name(); - var subject = envelope.message().subject().translate(lang); + private static JSONObject summary(Envelope envelope, String lang) { + var message = envelope.message(); + if (message instanceof TranslatableMessage tm) message = tm.translate(lang); + + var sender = message.sender().name(); + var subject = message.subject(); var time = envelope.time().format(TIME_FORMATTER); - var hash = envelope.hashCode(); + var hash = envelope.hashCode(); return new JSONObject(Map.of(SENDER,sender,SUBJECT,subject,TIMESTAMP,time,HASH,hash)); } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java index 14223f98..ef2e924c 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java @@ -15,7 +15,7 @@ public class CombinedMessage { private final StringBuilder combinedBody = new StringBuilder(); private final User receiver; private String combinedSubject = null; - private final List mergedMessages = new ArrayList<>(); + private final List> mergedMessages = new ArrayList<>(); private final Translatable subjectForCombinedMessage; private UmbrellaUser sender = null; @@ -25,11 +25,12 @@ public class CombinedMessage { this.receiver = receiver; } - public void merge(Message message) { + public void merge(Message message) { LOG.log(TRACE,"Merging {0} into combined message…",message); var lang = receiver.language(); - var body = message.body().translate(lang); - var subject = message.subject().translate(lang); + if (message instanceof TranslatableMessage tm) message = tm.translate(lang); + var body = message.body(); + var subject = message.subject().toString(); switch (mergedMessages.size()){ case 0: combinedBody.append(body); @@ -57,7 +58,7 @@ public class CombinedMessage { return combinedBody.toString(); } - public List messages() { + public List> messages() { return mergedMessages; } diff --git a/translations/src/main/java/de/srsoftware/umbrella/translations/Translations.java b/translations/src/main/java/de/srsoftware/umbrella/translations/Translations.java index 9ad5975c..893af362 100644 --- a/translations/src/main/java/de/srsoftware/umbrella/translations/Translations.java +++ b/translations/src/main/java/de/srsoftware/umbrella/translations/Translations.java @@ -18,7 +18,6 @@ import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Optional; - import org.json.JSONObject; public class Translations extends PathHandler implements Translator { diff --git a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java index 6f0af6b7..4fb900f4 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java @@ -506,8 +506,8 @@ public class UserModule extends BaseHandler implements UserService { var url = url(ex).replace("/api/user/reset_pw","/user/reset/pw")+"?token="+token; var subject = t("Your token to create a new password"); var content = t("To receive a new password, open the following link: {url}",URL,url); - var message = new Message(user,subject,content,null); - var envelope = new Envelope(message,user); + var message = new TranslatableMessage(user,subject,content,null); + var envelope = new Envelope<>(message,user); postBox().send(envelope); } catch (UmbrellaException e){ return send(ex,e); From 6d3c7cb14b45d910118429ce8816b2c6b1de3356 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 21 Jan 2026 18:32:25 +0100 Subject: [PATCH 2/5] refaturing message system, step 2: preparing new database-backed queue Signed-off-by: Stephan Richter --- .../umbrella/message/MessageQueue.java | 15 ++++++++ .../umbrella/message/MessageSystem.java | 16 ++++++--- .../umbrella/message/SqliteMessageDb.java | 34 ++++++++++++++++++- 3 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java new file mode 100644 index 00000000..1aea1c22 --- /dev/null +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java @@ -0,0 +1,15 @@ +package de.srsoftware.umbrella.message; + +import de.srsoftware.umbrella.core.model.UmbrellaUser; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public interface MessageQueue { + public Stream getEnvelopes(); + public Stream getEnvelopesFor(UmbrellaUser user); + public Optional getEnvelope(int hash); + public void markRead(int hash, UmbrellaUser user); + public void push(T message); +} diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java index ca8bcaec..7ff02f8a 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -79,7 +79,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener private final int port; private final SqliteMessageDb db; private Session session; - private final List queue = new CopyOnWriteArrayList<>(); + private final List> queue = new CopyOnWriteArrayList<>(); private String debugAddress; private final HashMap> exceptions = new HashMap<>(); @@ -140,7 +140,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener var head = path.pop(); return switch (head){ case SETTINGS -> patchSettings(ex,user.get()); - case READ -> patchState(ex,user.get(),path.pop()); + case READ -> markRead(ex,user.get(),path.pop()); default -> super.doGet(path,ex); }; } catch (NumberFormatException e){ @@ -151,6 +151,8 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } private boolean getMessage(HttpExchange ex, UmbrellaUser user, int hash) throws IOException { + var envel = db.getEnvelope(hash).filter(env -> env.isFor(user)); + if (envel.isPresent()) return sendMessage(ex, user, envel.get()); var envelope = queue.stream() .filter(msg -> msg.isFor(user)) .filter(msg -> msg.hashCode() == hash) @@ -164,6 +166,8 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } private boolean listMessages(HttpExchange ex, UmbrellaUser user) throws IOException { + var msgs = db.getEnvelopesFor(user).map(e -> summary(e, user.language())).toList(); + if (!msgs.isEmpty()) return sendContent(ex,msgs); var messages = queue.stream().filter(e -> e.isFor(user)).map(e -> summary(e, user.language())).toList(); return sendContent(ex,messages); } @@ -173,7 +177,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener for (var user : event.audience()){ if (debugAddress != null && !debugAddress.equals(user.email().toString())) continue; var message = new TranslatableMessage(event.initiator(),event.subject(),event.describe(),null); - var envelope = new Envelope(message,user); + var envelope = new Envelope<>(message,user); send(envelope); } } @@ -192,9 +196,10 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener return sendContent(ex,db.update(user,settings)); } - private boolean patchState(HttpExchange ex, UmbrellaUser user, String path) { + private boolean markRead(HttpExchange ex, UmbrellaUser user, String path) { try { var hash = Integer.parseInt(path); + db.markRead(hash, user); var envelope = queue.stream().filter(env -> env.hashCode() == hash).findFirst().orElse(null); if (envelope != null){ envelope.receivers().remove(user); @@ -306,8 +311,9 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } @Override - public void send(Envelope envelope) { + public void send(Envelope envelope) { queue.add(envelope); + db.push(envelope); new Thread(() -> processMessages(null)).start(); } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java index a22966f7..d0edff95 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java @@ -4,6 +4,7 @@ package de.srsoftware.umbrella.message; import static de.srsoftware.tools.jdbc.Condition.equal; import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.umbrella.core.Errors.*; +import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; import static de.srsoftware.umbrella.core.constants.Constants.TABLE_SETTINGS; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; @@ -13,6 +14,8 @@ import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Envelope; +import de.srsoftware.umbrella.core.model.TranslatedMessage; import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.message.model.Instantly; import de.srsoftware.umbrella.message.model.Settings; @@ -21,8 +24,11 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; -public class SqliteMessageDb implements MessageDb{ +public class SqliteMessageDb implements MessageDb, MessageQueue> { private static final System.Logger LOG = System.getLogger(SqliteMessageDb.class.getSimpleName()); private final Connection db; private static final String DB_VERSION = "message_db_version"; @@ -80,6 +86,21 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) return createSettingsTable(); } + @Override + public Optional> getEnvelope(int hash) { + throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelope({hash}) not implemented!","class",getClass().getSimpleName(),"hash",hash); // TODO + } + + @Override + public Stream> getEnvelopes() { + throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getMessages() not implemented!","class",getClass().getSimpleName()); // TODO + } + + @Override + public Stream> getEnvelopesFor(UmbrellaUser user) { + throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelopesFor({user}) not implemented!","class",getClass().getSimpleName(),"user",user.name()); // TODO + } + @Override public Settings getSettings(UmbrellaUser user) throws UmbrellaException { try { @@ -98,6 +119,17 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) var version = createTables(); } + @Override + public void markRead(int hash, UmbrellaUser user) { + throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.markRead(hash, user) not implemented!","class",getClass().getSimpleName()); // TODO + // TODO: throw exception if message not found! + } + + @Override + public void push(Envelope message) { + throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.push(message) not implemented!","class",getClass().getSimpleName()); // TODO + } + private Settings toSettings(ResultSet rs) throws SQLException { var submission = rs.getString(VALUE); if (submission.trim().equalsIgnoreCase(INSTANTLY)) return new Instantly(); From b123c30c5ba1537c508258ff5fb641e6a72732f3 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 21 Jan 2026 22:06:49 +0100 Subject: [PATCH 3/5] refaturing message system, step 3: making use of queue Signed-off-by: Stephan Richter --- .../srsoftware/umbrella/core/api/PostBox.java | 2 +- .../umbrella/core/model/Envelope.java | 16 +-- .../umbrella/message/MessageQueue.java | 17 +-- .../umbrella/message/MessageSystem.java | 111 +++++++++--------- .../umbrella/message/SqliteMessageDb.java | 11 +- 5 files changed, 82 insertions(+), 75 deletions(-) diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java b/core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java index 74e8b485..678219f4 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/PostBox.java @@ -4,5 +4,5 @@ package de.srsoftware.umbrella.core.api; import de.srsoftware.umbrella.core.model.Envelope; public interface PostBox { - public void send(Envelope envelope); + public void send(Envelope envelope); } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java index 14d96517..448297e7 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java @@ -17,16 +17,16 @@ import java.util.stream.Collectors; import org.json.JSONArray; import org.json.JSONObject; -public class Envelope { - private final Message message; +public class Envelope> { + private final T message; private final Set receivers; private final LocalDateTime time; - public Envelope(Message message, User receiver){ + public Envelope(T message, User receiver){ this(message,new HashSet<>(Set.of(receiver))); } - public Envelope(Message message, HashSet receivers) { + public Envelope(T message, HashSet receivers) { this.message = message; this.receivers = receivers; time = LocalDateTime.now(); @@ -38,7 +38,7 @@ public class Envelope { * @return * @throws UmbrellaException */ - public static Envelope from(JSONObject json) throws UmbrellaException { + public static Envelope from(JSONObject json) throws UmbrellaException { if (!json.has(RECEIVERS)) throw missingField(RECEIVERS); var message = TranslatedMessage.from(json); var obj = json.get(RECEIVERS); @@ -49,12 +49,12 @@ public class Envelope { if (!(o instanceof JSONObject receiverData)) throw invalidField("entries of "+ RECEIVERS, t(JSONOBJECT)); receivers.add(User.of(receiverData)); } - return new Envelope(message,receivers); + return new Envelope<>(message,receivers); } @Override public final boolean equals(Object o) { - if (!(o instanceof Envelope envelope)) return false; + if (!(o instanceof Envelope envelope)) return false; return message.equals(envelope.message) && time.equals(envelope.time); } @@ -67,7 +67,7 @@ public class Envelope { return receivers.contains(receiver); } - public Message message(){ + public T message(){ return message; } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java index 1aea1c22..4b892b6d 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java @@ -1,15 +1,18 @@ +/* © SRSoftware 2025 */ package de.srsoftware.umbrella.message; +import de.srsoftware.umbrella.core.model.Envelope; +import de.srsoftware.umbrella.core.model.Message; import de.srsoftware.umbrella.core.model.UmbrellaUser; - import java.util.List; import java.util.Optional; import java.util.stream.Stream; -public interface MessageQueue { - public Stream getEnvelopes(); - public Stream getEnvelopesFor(UmbrellaUser user); - public Optional getEnvelope(int hash); - public void markRead(int hash, UmbrellaUser user); - public void push(T message); +public interface MessageQueue> { + public Stream> getEnvelopes(); + public List> getEnvelopesFor(UmbrellaUser user); + public Optional> getEnvelope(int hash); + Stream getReceivers(); + public Optional> markRead(int hash, UmbrellaUser user); + public void push(Envelope message); } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java index 7ff02f8a..d3d94bee 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -40,7 +40,6 @@ import jakarta.mail.internet.MimeMultipart; import jakarta.mail.util.ByteArrayDataSource; import java.io.IOException; import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; import org.json.JSONArray; import org.json.JSONObject; @@ -77,15 +76,18 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } private final String from,host,user,pass; private final int port; - private final SqliteMessageDb db; + private final MessageDb db; + private final MessageQueue queue; private Session session; - private final List> queue = new CopyOnWriteArrayList<>(); - private String debugAddress; + private final String debugAddress; private final HashMap> exceptions = new HashMap<>(); public MessageSystem(Configuration config) throws UmbrellaException { var dbFile = config.get(CONFIG_DB).orElseThrow(() -> missingConfig(CONFIG_DB)); - db = new SqliteMessageDb(connect(dbFile)); + var sqlite = new SqliteMessageDb(connect(dbFile)); + db = sqlite; + queue = sqlite; + debugAddress = config.get(DEBUG_ADDREESS).map(Object::toString).orElse(null); port = config.get(CONFIG_SMTP_PORT,587); host = config.get(CONFIG_SMTP_HOST).map(Object::toString).orElseThrow(() -> new RuntimeException("umbrella.modules.message.smtp.host not configured!")); @@ -151,12 +153,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } private boolean getMessage(HttpExchange ex, UmbrellaUser user, int hash) throws IOException { - var envel = db.getEnvelope(hash).filter(env -> env.isFor(user)); - if (envel.isPresent()) return sendMessage(ex, user, envel.get()); - var envelope = queue.stream() - .filter(msg -> msg.isFor(user)) - .filter(msg -> msg.hashCode() == hash) - .findFirst(); + var envelope = queue.getEnvelope(hash).filter(env -> env.isFor(user)); if (envelope.isPresent()) return sendMessage(ex, user, envelope.get()); return notFound(ex); } @@ -166,9 +163,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } private boolean listMessages(HttpExchange ex, UmbrellaUser user) throws IOException { - var msgs = db.getEnvelopesFor(user).map(e -> summary(e, user.language())).toList(); - if (!msgs.isEmpty()) return sendContent(ex,msgs); - var messages = queue.stream().filter(e -> e.isFor(user)).map(e -> summary(e, user.language())).toList(); + var messages = queue.getEnvelopesFor(user).stream().map(e -> summary(e, user.language())); return sendContent(ex,messages); } @@ -184,7 +179,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener private boolean patchSettings(HttpExchange ex, UmbrellaUser user) throws IOException { var json = json(ex); - Settings settings = null; + Settings settings; if (json.has(INSTANTLY) && json.get(INSTANTLY) instanceof Boolean b && b){ settings = new Instantly(); } else { @@ -199,49 +194,37 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener private boolean markRead(HttpExchange ex, UmbrellaUser user, String path) { try { var hash = Integer.parseInt(path); - db.markRead(hash, user); - var envelope = queue.stream().filter(env -> env.hashCode() == hash).findFirst().orElse(null); - if (envelope != null){ - envelope.receivers().remove(user); - return sendMessage(ex,user,envelope); - } + var envelope = queue.markRead(hash, user); + if (envelope.isPresent()) return sendMessage(ex,user,envelope.get()); return notFound(ex); } catch (NumberFormatException | IOException e) { throw invalidField(HASH,LONG); } } + private boolean sendAt(UmbrellaUser user, Integer scheduledHour){ + try { + return db.getSettings(user).sendAt(scheduledHour); + } catch (UmbrellaException ignored) { + return true; + } + } + private synchronized void processMessages(Integer scheduledHour) { LOG.log(INFO,"Running {0}…",scheduledHour == null ? "instantly" : "scheduled at "+scheduledHour); - var queue = new ArrayList<>(this.queue); - var dueRecipients = new ArrayList(); - List recipients = queue.stream().map(Envelope::receivers).flatMap(Set::stream).filter(Objects::nonNull).distinct().toList(); - { // for known users: get notification preferences, fallback to _immediately_ for unknown users - for (User recv : recipients) { - if (recv instanceof UmbrellaUser uu) { - try { - if (!db.getSettings(uu).sendAt(scheduledHour)) continue; - } catch (UmbrellaException ignored) {} - } - dueRecipients.add(recv); - } - } + var dueRecipients = queue.getReceivers().filter(uu -> sendAt(uu,scheduledHour)).toList(); var date = new Date(); for (var receiver : dueRecipients){ var combined = new CombinedMessage(t("Collected messages"),receiver); - var envelopes = queue.stream().filter(env -> env.isFor(receiver)).toList(); - for (var envelope : envelopes) combined.merge(envelope.message()); try { + var envelopes = queue.getEnvelopesFor(receiver); + envelopes.stream().map(Envelope::message).forEach(combined::merge); send(combined,date); - for (var envelope : envelopes){ - var audience = envelope.receivers(); - audience.remove(receiver); - if (audience.isEmpty()) queue.remove(envelope); - } + envelopes.forEach(env -> queue.markRead(env.hashCode(),receiver)); } catch (Exception ex){ LOG.log(WARNING,"Failed to deliver mail ({0}) to {1}.",combined.subject(),receiver,ex); for (var message : combined.messages()) exceptions.computeIfAbsent(new Receiver(receiver,message), k -> new ArrayList<>()).add(ex); @@ -262,17 +245,6 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener )); } - private static JSONObject summary(Envelope envelope, String lang) { - var message = envelope.message(); - if (message instanceof TranslatableMessage tm) message = tm.translate(lang); - - var sender = message.sender().name(); - var subject = message.subject(); - var time = envelope.time().format(TIME_FORMATTER); - var hash = envelope.hashCode(); - return new JSONObject(Map.of(SENDER,sender,SUBJECT,subject,TIMESTAMP,time,HASH,hash)); - } - private void send(CombinedMessage message, Date date) throws MessagingException { var receiver = message.receiver(); @@ -311,9 +283,29 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } @Override + @SuppressWarnings("unchecked") public void send(Envelope envelope) { - queue.add(envelope); - db.push(envelope); + switch (envelope.message()){ + case TranslatedMessage ignored: + queue.push((Envelope) envelope); + break; + case TranslatableMessage tm: + Map> map = new HashMap<>(); + for (var receiver : envelope.receivers()){ + var lang = receiver.language(); + var env = map.get(lang); + if (env == null){ + TranslatedMessage translated = tm.translate(lang); + env = new Envelope<>(translated,new HashSet<>()); + map.put(lang,env); + } + env.receivers().add(receiver); + } + map.values().forEach(queue::push); + default: + return; + } + new Thread(() -> processMessages(null)).start(); } @@ -330,8 +322,15 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener return session; } - public void setDebugAddress(String newVal) { - this.debugAddress = newVal; + private static JSONObject summary(Envelope envelope, String lang) { + var message = envelope.message(); + if (message instanceof TranslatableMessage tm) message = tm.translate(lang); + + var sender = message.sender().name(); + var subject = message.subject(); + var time = envelope.time().format(TIME_FORMATTER); + var hash = envelope.hashCode(); + return new JSONObject(Map.of(SENDER,sender,SUBJECT,subject,TIMESTAMP,time,HASH,hash)); } } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java index d0edff95..3c3918f7 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; -public class SqliteMessageDb implements MessageDb, MessageQueue> { +public class SqliteMessageDb implements MessageDb, MessageQueue { private static final System.Logger LOG = System.getLogger(SqliteMessageDb.class.getSimpleName()); private final Connection db; private static final String DB_VERSION = "message_db_version"; @@ -97,10 +97,15 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) } @Override - public Stream> getEnvelopesFor(UmbrellaUser user) { + public List> getEnvelopesFor(UmbrellaUser user) { throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelopesFor({user}) not implemented!","class",getClass().getSimpleName(),"user",user.name()); // TODO } + @Override + public Stream getReceivers() { + return Stream.empty(); + } + @Override public Settings getSettings(UmbrellaUser user) throws UmbrellaException { try { @@ -120,7 +125,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) } @Override - public void markRead(int hash, UmbrellaUser user) { + public Optional> markRead(int hash, UmbrellaUser user) { throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.markRead(hash, user) not implemented!","class",getClass().getSimpleName()); // TODO // TODO: throw exception if message not found! } From d6d6aabe5173367d8acbbf3156edfe8377974e45 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 21 Jan 2026 22:32:45 +0100 Subject: [PATCH 4/5] refaturing message system, step 4: implementing in-memory queue Signed-off-by: Stephan Richter --- .../umbrella/message/InMemoryQueue.java | 60 +++++++++++++++++++ .../umbrella/message/MessageQueue.java | 10 ++-- .../umbrella/message/MessageSystem.java | 14 ++--- .../umbrella/message/SqliteMessageDb.java | 7 ++- 4 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java b/messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java new file mode 100644 index 00000000..c7133c1e --- /dev/null +++ b/messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java @@ -0,0 +1,60 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.message; +import static java.lang.System.Logger.Level.DEBUG; + +import de.srsoftware.umbrella.core.model.Envelope; +import de.srsoftware.umbrella.core.model.TranslatedMessage; +import de.srsoftware.umbrella.core.model.User; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class InMemoryQueue implements MessageQueue { + public static final System.Logger LOG = System.getLogger(InMemoryQueue.class.getSimpleName()); + private LinkedList> queue = new LinkedList<>(); + + @Override + public Stream> getEnvelopes() { + return queue.stream(); + } + + @Override + public List> getEnvelopesFor(User user) { + return getEnvelopes().filter(env -> env.isFor(user)).toList(); + } + + @Override + public Optional> getEnvelope(int hash) { + return getEnvelopes().filter(env -> env.hashCode() == hash).findAny(); + } + + @Override + public Stream getReceivers() { + return getEnvelopes().map(Envelope::receivers).flatMap(Set::stream).distinct(); + } + + @Override + public Optional> markRead(int hash, User user) { + for (var env : queue){ + if (env.hashCode() == hash) { + LOG.log(DEBUG,"Removing {0} from receiver list of {1}…",user.name(),env.message().subject()); + env.receivers().remove(user); + if (env.receivers().isEmpty()) { + LOG.log(DEBUG,"No more due receiers, removing {0} from queue…",env.message().subject()); + queue.remove(env); + } + return Optional.of(env); + } + } + return Optional.empty(); + } + + @Override + public void push(Envelope envelope) { + queue.add(envelope); + LOG.log(DEBUG,"{0} for {1} pushed to queue",envelope.message().subject(),envelope.receivers().stream().map(User::name).collect(Collectors.joining(", "))); + } +} diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java index 4b892b6d..e9c342b6 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java @@ -3,16 +3,16 @@ package de.srsoftware.umbrella.message; import de.srsoftware.umbrella.core.model.Envelope; import de.srsoftware.umbrella.core.model.Message; -import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.core.model.User; import java.util.List; import java.util.Optional; import java.util.stream.Stream; public interface MessageQueue> { public Stream> getEnvelopes(); - public List> getEnvelopesFor(UmbrellaUser user); + public List> getEnvelopesFor(User user); public Optional> getEnvelope(int hash); - Stream getReceivers(); - public Optional> markRead(int hash, UmbrellaUser user); - public void push(Envelope message); + Stream getReceivers(); + public Optional> markRead(int hash, User user); + public void push(Envelope envelope); } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java index d3d94bee..6a2853f4 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -84,9 +84,8 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener public MessageSystem(Configuration config) throws UmbrellaException { var dbFile = config.get(CONFIG_DB).orElseThrow(() -> missingConfig(CONFIG_DB)); - var sqlite = new SqliteMessageDb(connect(dbFile)); - db = sqlite; - queue = sqlite; + db = new SqliteMessageDb(connect(dbFile)); + queue = new InMemoryQueue(); debugAddress = config.get(DEBUG_ADDREESS).map(Object::toString).orElse(null); port = config.get(CONFIG_SMTP_PORT,587); @@ -202,12 +201,11 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener } } - private boolean sendAt(UmbrellaUser user, Integer scheduledHour){ + private boolean sendAt(User user, Integer scheduledHour){ try { - return db.getSettings(user).sendAt(scheduledHour); - } catch (UmbrellaException ignored) { - return true; - } + if (user instanceof UmbrellaUser uu) return db.getSettings(uu).sendAt(scheduledHour); + } catch (UmbrellaException ignored) {} + return true; } private synchronized void processMessages(Integer scheduledHour) { diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java index 3c3918f7..6b9d0f16 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java @@ -17,6 +17,7 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Envelope; import de.srsoftware.umbrella.core.model.TranslatedMessage; import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.core.model.User; import de.srsoftware.umbrella.message.model.Instantly; import de.srsoftware.umbrella.message.model.Settings; import de.srsoftware.umbrella.message.model.Silent; @@ -97,12 +98,12 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) } @Override - public List> getEnvelopesFor(UmbrellaUser user) { + public List> getEnvelopesFor(User user) { throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelopesFor({user}) not implemented!","class",getClass().getSimpleName(),"user",user.name()); // TODO } @Override - public Stream getReceivers() { + public Stream getReceivers() { return Stream.empty(); } @@ -125,7 +126,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) } @Override - public Optional> markRead(int hash, UmbrellaUser user) { + public Optional> markRead(int hash, User user) { throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.markRead(hash, user) not implemented!","class",getClass().getSimpleName()); // TODO // TODO: throw exception if message not found! } From 109b17028966bedf6fe7df71cab9d785aeb73623 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 21 Jan 2026 23:43:39 +0100 Subject: [PATCH 5/5] bugfixes and emprovements on message creation from events Signed-off-by: Stephan Richter --- .../de/srsoftware/umbrella/core/model/Envelope.java | 5 +++-- .../srsoftware/umbrella/message/MessageSystem.java | 13 +++++++------ .../umbrella/message/model/CombinedMessage.java | 7 +++---- .../umbrella/message/model/Instantly.java | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java index 448297e7..979be7a9 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Envelope.java @@ -10,6 +10,7 @@ import static java.text.MessageFormat.format; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import java.time.LocalDateTime; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -26,9 +27,9 @@ public class Envelope> { this(message,new HashSet<>(Set.of(receiver))); } - public Envelope(T message, HashSet receivers) { + public Envelope(T message, Collection receivers) { this.message = message; - this.receivers = receivers; + this.receivers = new HashSet<>(receivers); time = LocalDateTime.now(); } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java index 6a2853f4..d4627ef2 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -168,12 +168,8 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener @Override public void onEvent(Event event) { - for (var user : event.audience()){ - if (debugAddress != null && !debugAddress.equals(user.email().toString())) continue; - var message = new TranslatableMessage(event.initiator(),event.subject(),event.describe(),null); - var envelope = new Envelope<>(message,user); - send(envelope); - } + var message = new TranslatableMessage(event.initiator(),event.subject(),event.describe(),null); + send(new Envelope<>(message,event.audience())); } private boolean patchSettings(HttpExchange ex, UmbrellaUser user) throws IOException { @@ -216,6 +212,10 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener var date = new Date(); for (var receiver : dueRecipients){ + if (debugAddress != null && !debugAddress.equals(receiver.email().toString())) { + LOG.log(DEBUG,"Debug address is set to {0}, ignoring mail to {1}",debugAddress,receiver); + continue; + } var combined = new CombinedMessage(t("Collected messages"),receiver); try { @@ -300,6 +300,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener env.receivers().add(receiver); } map.values().forEach(queue::push); + break; default: return; } diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java index ef2e924c..2f0b78c6 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java @@ -20,7 +20,7 @@ public class CombinedMessage { private UmbrellaUser sender = null; public CombinedMessage(Translatable subjectForCombinedMessage, User receiver){ - LOG.log(DEBUG,"Creating combined message…"); + LOG.log(DEBUG,"Creating combined message for {0}…",receiver); this.subjectForCombinedMessage = subjectForCombinedMessage; this.receiver = receiver; } @@ -38,12 +38,11 @@ public class CombinedMessage { combinedSubject = subject; break; case 1: - combinedBody.insert(0,format("# {0}:\n# {1}:\n\n",sender,subject)); // insert sender and subject of first message right before the body of the first message + combinedBody.insert(0,format("# {0} / {1}:\n\n",sender,subject)); // insert sender and subject of first message right before the body of the first message combinedSubject = subjectForCombinedMessage.translate(lang); // no break here, we need to append the subject and content default: - combinedBody.append("\n\n# ").append(message.sender()).append(":\n"); - combinedBody.append("# ").append(subject).append(":\n\n"); + combinedBody.append("\n\n━━━━━━━━━━━━━━━━━━━━━\n\n# ").append(message.sender()).append(" / ").append(subject).append(":\n\n"); combinedBody.append(body); } if (message.attachments() != null) attachments.addAll(message.attachments()); diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java b/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java index db6f2d0d..b580de07 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/model/Instantly.java @@ -13,7 +13,7 @@ public class Instantly implements Settings{ @Override public boolean sendAt(Integer scheduledHour) { - return false; + return true; } @Override