refaturing message system, step 1: making message abstract and add TranslatableMessage and TranslatedMessage

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-01-21 18:04:09 +01:00
parent 8c10f820d5
commit 94d430a5a5
11 changed files with 125 additions and 75 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<T> {
private final Message<T> message;
private final Set<User> receivers;
private final LocalDateTime time;
public Envelope(Message message, User receiver){
public Envelope(Message<T> message, User receiver){
this(message,new HashSet<>(Set.of(receiver)));
}
public Envelope(Message message, HashSet<User> receivers) {
public Envelope(Message<T> message, HashSet<User> 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<T> message(){
return message;
}

View File

@@ -1,57 +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.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 abstract class Message<T> {
private final Collection<Attachment> attachments;
private final T body, subject;
private final UmbrellaUser sender;
public Message(UmbrellaUser sender, T subject, T body, Collection<Attachment> attachments){
this.sender = sender;
this.subject = subject;
this.body = body;
this.attachments = attachments;
}
public Collection<Attachment> attachments(){
return attachments;
}
public T body(){
return body;
}
public record Message(UmbrellaUser sender, Translatable subject, Translatable body, List<Attachment> attachments) {
@Override
public boolean equals(Object o) {
if (!(o instanceof Message message)) return false;
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 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));
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<Attachment>();
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);
}
@Override
public int hashCode() {
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);

View File

@@ -0,0 +1,14 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model;
import java.util.Collection;
public class TranslatableMessage extends Message<Translatable> {
public TranslatableMessage(UmbrellaUser sender, Translatable subject, Translatable body, Collection<Attachment> attachments) {
super(sender, subject, body, attachments);
}
public TranslatedMessage translate(String lang){
return new TranslatedMessage(sender(),subject().translate(lang),body().translate(lang),attachments());
}
}

View File

@@ -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<String> {
public TranslatedMessage(UmbrellaUser sender, String subject, String body, Collection<Attachment> 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<Attachment>();
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);
}
}

View File

@@ -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));

View File

@@ -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,21 +247,22 @@ 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();
return new JSONObject(Map.of(SENDER,sender,SUBJECT,subject,TIMESTAMP,time,HASH,hash));

View File

@@ -15,7 +15,7 @@ public class CombinedMessage {
private final StringBuilder combinedBody = new StringBuilder();
private final User receiver;
private String combinedSubject = null;
private final List<Message> mergedMessages = new ArrayList<>();
private final List<Message<?>> 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<Message> messages() {
public List<Message<?>> messages() {
return mergedMessages;
}

View File

@@ -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 {

View File

@@ -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);