From c12786971fbfd32fa7818aa402c9b9a9cc626dfc Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Mon, 26 Jan 2026 08:50:01 +0100 Subject: [PATCH] working on implementatin of MessageQueue by SqliteMessageDb Signed-off-by: Stephan Richter --- .../umbrella/core/constants/Constants.java | 2 + .../umbrella/core/constants/Text.java | 3 + .../umbrella/core/model/Envelope.java | 16 +++-- .../umbrella/documents/DocumentApi.java | 2 +- .../umbrella/message/InMemoryQueue.java | 59 ------------------- .../umbrella/message/MessageQueue.java | 4 +- .../umbrella/message/MessageSystem.java | 4 +- .../umbrella/message/SqliteMessageDb.java | 55 +++++++++++++---- .../srsoftware/umbrella/user/UserModule.java | 2 +- 9 files changed, 68 insertions(+), 79 deletions(-) delete mode 100644 messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java index ab3124c3..6749edea 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Constants.java @@ -11,6 +11,8 @@ public class Constants { private Constants(){ // prevent instantiation } + + public static final String COUNT = "COUNT(*)"; public static final String FALLBACK_LANG = "de"; public static final String HOME = "home"; public static final String JSONARRAY = "json array"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Text.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Text.java index 7a935188..bf91a7c5 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Text.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Text.java @@ -33,6 +33,8 @@ public class Text { public static final String LOGIN_SERVICE = "login service"; public static final String LONG = "Long"; + public static final String MESSAGE = "message"; + public static final String NOTE = "note"; public static final String NOTE_WITH_ID = "note ({id})"; public static final String NUMBER = "number"; @@ -43,6 +45,7 @@ public class Text { public static final String PROPERTIES = "properties"; public static final String PROPERTY = "property"; + public static final String RECEIVER = "receiver"; public static final String RECEIVERS = "receivers"; public static final String SENDER = "sender"; 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 979be7a9..fad56ac2 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 @@ -3,6 +3,7 @@ package de.srsoftware.umbrella.core.model; import static de.srsoftware.umbrella.core.constants.Constants.JSONARRAY; import static de.srsoftware.umbrella.core.constants.Constants.JSONOBJECT; +import static de.srsoftware.umbrella.core.constants.Field.ID; import static de.srsoftware.umbrella.core.constants.Field.RECEIVERS; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Translatable.t; @@ -22,15 +23,17 @@ public class Envelope> { private final T message; private final Set receivers; private final LocalDateTime time; + private final long id; - public Envelope(T message, User receiver){ - this(message,new HashSet<>(Set.of(receiver))); + public Envelope(long id, T message, User receiver){ + this(id, message,new HashSet<>(Set.of(receiver))); } - public Envelope(T message, Collection receivers) { + public Envelope(long id, T message, Collection receivers) { this.message = message; this.receivers = new HashSet<>(receivers); time = LocalDateTime.now(); + this.id = id; } /** @@ -41,6 +44,7 @@ public class Envelope> { */ public static Envelope from(JSONObject json) throws UmbrellaException { if (!json.has(RECEIVERS)) throw missingField(RECEIVERS); + var id = json.has(ID) && json.get(ID) instanceof Number n ? n.longValue() : 0; var message = TranslatedMessage.from(json); var obj = json.get(RECEIVERS); if (obj instanceof JSONObject) obj = new JSONArray(List.of(obj)); @@ -50,7 +54,7 @@ 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<>(id, message, receivers); } @Override @@ -64,6 +68,10 @@ public class Envelope> { return 31 * message.hashCode() + time.hashCode(); } + public long id(){ + return id; + } + public boolean isFor(User receiver) { return receivers.contains(receiver); } 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 fbc96689..45bbf862 100644 --- a/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java +++ b/documents/src/main/java/de/srsoftware/umbrella/documents/DocumentApi.java @@ -578,7 +578,7 @@ public class DocumentApi extends BaseHandler implements DocumentService { } var attachment = new Attachment(doc.number()+".pdf",rendered.mimeType(),rendered.bytes()); 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())); + var envelope = new Envelope<>(0, message,new User(doc.customer().shortName(),new EmailAddress(email),doc.customer().language())); postBox().send(envelope); db.save(doc.set(SENT)); updateTimes(doc); diff --git a/messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java b/messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java deleted file mode 100644 index 72b9667f..00000000 --- a/messages/src/main/java/de/srsoftware/umbrella/message/InMemoryQueue.java +++ /dev/null @@ -1,59 +0,0 @@ -/* © 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<>(); - - private Stream> envelopes(){ - return queue.stream(); - } - - @Override - public List> getEnvelopesFor(User user) { - return envelopes().filter(env -> env.isFor(user)).toList(); - } - - @Override - public Optional> getEnvelope(int hash) { - return envelopes().filter(env -> env.hashCode() == hash).findAny(); - } - - @Override - public Stream getReceivers() { - return envelopes().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 17bd297a..abaf8ebe 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageQueue.java @@ -10,8 +10,8 @@ import java.util.stream.Stream; public interface MessageQueue> { public List> getEnvelopesFor(User user); - public Optional> getEnvelope(int hash); + public Optional> getEnvelope(long id); Stream getReceivers(); - public Optional> markRead(int hash, User user); + public Optional> markRead(long messageId, 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 bd6e9420..849c5888 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java @@ -170,7 +170,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener @Override public void onEvent(Event event) { var message = new TranslatableMessage(event.initiator(),event.subject(),event.describe(),null); - send(new Envelope<>(message,event.audience())); + send(new Envelope<>(0,message,event.audience())); } private boolean patchSettings(HttpExchange ex, UmbrellaUser user) throws IOException { @@ -295,7 +295,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener var env = map.get(lang); if (env == null){ TranslatedMessage translated = tm.translate(lang); - env = new Envelope<>(translated,new HashSet<>()); + env = new Envelope<>(0, translated,new HashSet<>()); map.put(lang,env); } env.receivers().add(receiver); 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 480902f1..453c6c95 100644 --- a/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java +++ b/messages/src/main/java/de/srsoftware/umbrella/message/SqliteMessageDb.java @@ -8,6 +8,7 @@ import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.umbrella.core.Errors.*; import static de.srsoftware.umbrella.core.ModuleRegistry.userService; import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; +import static de.srsoftware.umbrella.core.constants.Constants.COUNT; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Translatable.t; @@ -16,6 +17,7 @@ import static de.srsoftware.umbrella.message.model.Schedule.schedule; import static java.text.MessageFormat.format; import static java.time.ZoneOffset.UTC; +import de.srsoftware.tools.jdbc.Query; import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; @@ -110,8 +112,32 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} INTEGER PRIMARY KEY, {2} VARCHAR(255) NOT N } @Override - public Optional> getEnvelope(int hash) { - throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelope({hash}) not implemented!","class",getClass().getSimpleName(),"hash",hash); // TODO + public Optional> getEnvelope(long messageId) { + try { + var attachments = new ArrayList(); + var rs = select(ALL).from(TABLE_ATTACHMENTS).where(MESSAGE_ID,equal(messageId)).exec(db); + while (rs.next()) attachments.add(new Attachment(rs.getString(NAME),rs.getString(MIME),rs.getBytes(DATA))); + rs.close(); + + var receivers = new ArrayList(); + rs = select(ALL).from(TABLE_ATTACHMENTS).where(MESSAGE_ID,equal(messageId)).exec(db); + while (rs.next()) receivers.add(new User(rs.getString(NAME),new EmailAddress(rs.getString(EMAIL)),null)); + rs.close(); + + Envelope envelope = null; + rs = select(ALL).from(TABLE_MESSAGES).where(ID,equal(messageId)).exec(db); + if (rs.next()){ + var sender = userService().loadUser(rs.getLong(SENDER_USER_ID)); + var msg = new TranslatedMessage(sender,rs.getString(SUBJECT),rs.getString(BODY), attachments); + envelope = new Envelope<>(messageId, msg, receivers); + } + rs.close(); + if (envelope != null) return Optional.of(envelope); + return Optional.empty(); + } catch (SQLException e) { + throw failedToLoadObject(Text.MESSAGE,id); + } + throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelope({id}) not implemented!","class",getClass().getSimpleName(), ID ,id); // TODO } @Override @@ -137,7 +163,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} INTEGER PRIMARY KEY, {2} VARCHAR(255) NOT N var messageId = rs.getLong(ID); var sender = userService().loadUser(rs.getLong(SENDER_USER_ID)); var msg = new TranslatedMessage(sender,rs.getString(SUBJECT),rs.getString(BODY), attachments.get(messageId)); - var envelope = new Envelope<>(msg,user); + var envelope = new Envelope<>(messageId, msg, user); envelopes.add(envelope); } rs.close(); @@ -177,14 +203,23 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} INTEGER PRIMARY KEY, {2} VARCHAR(255) NOT N } } - private void init() { - var version = createTables(); - } - @Override - 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! + public Optional> markRead(long messageId, User user) { + try { + Query.delete().from(TABLE_RECEIVERS).where(MESSAGE_ID,equal(messageId)).where(EMAIL,equal(user.email().toString())).execute(db); + var rs = select(COUNT).from(TABLE_RECEIVERS).where(MESSAGE_ID,equal(messageId)).exec(db); + Integer count = null; + if (rs.next()) count = rs.getInt(1); + rs.close(); + if (count != null && count == 0){ + delete().from(TABLE_ATTACHMENTS).where(MESSAGE_ID,equal(messageId)).execute(db); + delete().from(TABLE_MESSAGES).where(ID,equal(messageId)).execute(db); + } + + // TODO load message or fail + } catch (SQLException e) { + throw failedToDropObject(Text.RECEIVER); + } } @Override 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 4fb900f4..798fd786 100644 --- a/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java +++ b/user/src/main/java/de/srsoftware/umbrella/user/UserModule.java @@ -507,7 +507,7 @@ public class UserModule extends BaseHandler implements UserService { 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 TranslatableMessage(user,subject,content,null); - var envelope = new Envelope<>(message,user); + var envelope = new Envelope<>(0, message, user); postBox().send(envelope); } catch (UmbrellaException e){ return send(ex,e);