working on implementatin of MessageQueue by SqliteMessageDb
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -11,6 +11,8 @@ public class Constants {
|
|||||||
private Constants(){
|
private Constants(){
|
||||||
// prevent instantiation
|
// prevent instantiation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String COUNT = "COUNT(*)";
|
||||||
public static final String FALLBACK_LANG = "de";
|
public static final String FALLBACK_LANG = "de";
|
||||||
public static final String HOME = "home";
|
public static final String HOME = "home";
|
||||||
public static final String JSONARRAY = "json array";
|
public static final String JSONARRAY = "json array";
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ public class Text {
|
|||||||
public static final String LOGIN_SERVICE = "login service";
|
public static final String LOGIN_SERVICE = "login service";
|
||||||
public static final String LONG = "Long";
|
public static final String LONG = "Long";
|
||||||
|
|
||||||
|
public static final String MESSAGE = "message";
|
||||||
|
|
||||||
public static final String NOTE = "note";
|
public static final String NOTE = "note";
|
||||||
public static final String NOTE_WITH_ID = "note ({id})";
|
public static final String NOTE_WITH_ID = "note ({id})";
|
||||||
public static final String NUMBER = "number";
|
public static final String NUMBER = "number";
|
||||||
@@ -43,6 +45,7 @@ public class Text {
|
|||||||
public static final String PROPERTIES = "properties";
|
public static final String PROPERTIES = "properties";
|
||||||
public static final String PROPERTY = "property";
|
public static final String PROPERTY = "property";
|
||||||
|
|
||||||
|
public static final String RECEIVER = "receiver";
|
||||||
public static final String RECEIVERS = "receivers";
|
public static final String RECEIVERS = "receivers";
|
||||||
|
|
||||||
public static final String SENDER = "sender";
|
public static final String SENDER = "sender";
|
||||||
|
|||||||
@@ -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.JSONARRAY;
|
||||||
import static de.srsoftware.umbrella.core.constants.Constants.JSONOBJECT;
|
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.constants.Field.RECEIVERS;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||||
import static de.srsoftware.umbrella.core.model.Translatable.t;
|
import static de.srsoftware.umbrella.core.model.Translatable.t;
|
||||||
@@ -22,15 +23,17 @@ public class Envelope<T extends Message<?>> {
|
|||||||
private final T message;
|
private final T message;
|
||||||
private final Set<User> receivers;
|
private final Set<User> receivers;
|
||||||
private final LocalDateTime time;
|
private final LocalDateTime time;
|
||||||
|
private final long id;
|
||||||
|
|
||||||
public Envelope(T message, User receiver){
|
public Envelope(long id, T message, User receiver){
|
||||||
this(message,new HashSet<>(Set.of(receiver)));
|
this(id, message,new HashSet<>(Set.of(receiver)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Envelope(T message, Collection<? extends User> receivers) {
|
public Envelope(long id, T message, Collection<? extends User> receivers) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.receivers = new HashSet<>(receivers);
|
this.receivers = new HashSet<>(receivers);
|
||||||
time = LocalDateTime.now();
|
time = LocalDateTime.now();
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,6 +44,7 @@ public class Envelope<T extends Message<?>> {
|
|||||||
*/
|
*/
|
||||||
public static Envelope<TranslatedMessage> from(JSONObject json) throws UmbrellaException {
|
public static Envelope<TranslatedMessage> from(JSONObject json) throws UmbrellaException {
|
||||||
if (!json.has(RECEIVERS)) throw missingField(RECEIVERS);
|
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 message = TranslatedMessage.from(json);
|
||||||
var obj = json.get(RECEIVERS);
|
var obj = json.get(RECEIVERS);
|
||||||
if (obj instanceof JSONObject) obj = new JSONArray(List.of(obj));
|
if (obj instanceof JSONObject) obj = new JSONArray(List.of(obj));
|
||||||
@@ -50,7 +54,7 @@ public class Envelope<T extends Message<?>> {
|
|||||||
if (!(o instanceof JSONObject receiverData)) throw invalidField("entries of "+ RECEIVERS, t(JSONOBJECT));
|
if (!(o instanceof JSONObject receiverData)) throw invalidField("entries of "+ RECEIVERS, t(JSONOBJECT));
|
||||||
receivers.add(User.of(receiverData));
|
receivers.add(User.of(receiverData));
|
||||||
}
|
}
|
||||||
return new Envelope<>(message,receivers);
|
return new Envelope<>(id, message, receivers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,6 +68,10 @@ public class Envelope<T extends Message<?>> {
|
|||||||
return 31 * message.hashCode() + time.hashCode();
|
return 31 * message.hashCode() + time.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long id(){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFor(User receiver) {
|
public boolean isFor(User receiver) {
|
||||||
return receivers.contains(receiver);
|
return receivers.contains(receiver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -578,7 +578,7 @@ public class DocumentApi extends BaseHandler implements DocumentService {
|
|||||||
}
|
}
|
||||||
var attachment = new Attachment(doc.number()+".pdf",rendered.mimeType(),rendered.bytes());
|
var attachment = new Attachment(doc.number()+".pdf",rendered.mimeType(),rendered.bytes());
|
||||||
var message = new TranslatedMessage(user,subject,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()));
|
var envelope = new Envelope<>(0, message,new User(doc.customer().shortName(),new EmailAddress(email),doc.customer().language()));
|
||||||
postBox().send(envelope);
|
postBox().send(envelope);
|
||||||
db.save(doc.set(SENT));
|
db.save(doc.set(SENT));
|
||||||
updateTimes(doc);
|
updateTimes(doc);
|
||||||
|
|||||||
@@ -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<TranslatedMessage> {
|
|
||||||
public static final System.Logger LOG = System.getLogger(InMemoryQueue.class.getSimpleName());
|
|
||||||
private LinkedList<Envelope<TranslatedMessage>> queue = new LinkedList<>();
|
|
||||||
|
|
||||||
private Stream<Envelope<TranslatedMessage>> envelopes(){
|
|
||||||
return queue.stream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Envelope<TranslatedMessage>> getEnvelopesFor(User user) {
|
|
||||||
return envelopes().filter(env -> env.isFor(user)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Envelope<TranslatedMessage>> getEnvelope(int hash) {
|
|
||||||
return envelopes().filter(env -> env.hashCode() == hash).findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<User> getReceivers() {
|
|
||||||
return envelopes().map(Envelope::receivers).flatMap(Set::stream).distinct();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Envelope<TranslatedMessage>> 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<TranslatedMessage> 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(", ")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,8 +10,8 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
public interface MessageQueue<T extends Message<?>> {
|
public interface MessageQueue<T extends Message<?>> {
|
||||||
public List<Envelope<T>> getEnvelopesFor(User user);
|
public List<Envelope<T>> getEnvelopesFor(User user);
|
||||||
public Optional<Envelope<T>> getEnvelope(int hash);
|
public Optional<Envelope<T>> getEnvelope(long id);
|
||||||
Stream<User> getReceivers();
|
Stream<User> getReceivers();
|
||||||
public Optional<Envelope<T>> markRead(int hash, User user);
|
public Optional<Envelope<T>> markRead(long messageId, User user);
|
||||||
public void push(Envelope<T> envelope);
|
public void push(Envelope<T> envelope);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ public class MessageSystem extends BaseHandler implements PostBox, EventListener
|
|||||||
@Override
|
@Override
|
||||||
public void onEvent(Event<?> event) {
|
public void onEvent(Event<?> event) {
|
||||||
var message = new TranslatableMessage(event.initiator(),event.subject(),event.describe(),null);
|
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 {
|
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);
|
var env = map.get(lang);
|
||||||
if (env == null){
|
if (env == null){
|
||||||
TranslatedMessage translated = tm.translate(lang);
|
TranslatedMessage translated = tm.translate(lang);
|
||||||
env = new Envelope<>(translated,new HashSet<>());
|
env = new Envelope<>(0, translated,new HashSet<>());
|
||||||
map.put(lang,env);
|
map.put(lang,env);
|
||||||
}
|
}
|
||||||
env.receivers().add(receiver);
|
env.receivers().add(receiver);
|
||||||
|
|||||||
@@ -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.Errors.*;
|
||||||
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
||||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
|
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.constants.Field.*;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||||
import static de.srsoftware.umbrella.core.model.Translatable.t;
|
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.text.MessageFormat.format;
|
||||||
import static java.time.ZoneOffset.UTC;
|
import static java.time.ZoneOffset.UTC;
|
||||||
|
|
||||||
|
import de.srsoftware.tools.jdbc.Query;
|
||||||
import de.srsoftware.umbrella.core.BaseDb;
|
import de.srsoftware.umbrella.core.BaseDb;
|
||||||
import de.srsoftware.umbrella.core.constants.Text;
|
import de.srsoftware.umbrella.core.constants.Text;
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
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
|
@Override
|
||||||
public Optional<Envelope<TranslatedMessage>> getEnvelope(int hash) {
|
public Optional<Envelope<TranslatedMessage>> getEnvelope(long messageId) {
|
||||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.getEnvelope({hash}) not implemented!","class",getClass().getSimpleName(),"hash",hash); // TODO
|
try {
|
||||||
|
var attachments = new ArrayList<Attachment>();
|
||||||
|
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<User>();
|
||||||
|
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<TranslatedMessage> 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
|
@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 messageId = rs.getLong(ID);
|
||||||
var sender = userService().loadUser(rs.getLong(SENDER_USER_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 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);
|
envelopes.add(envelope);
|
||||||
}
|
}
|
||||||
rs.close();
|
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
|
@Override
|
||||||
public Optional<Envelope<TranslatedMessage>> markRead(int hash, User user) {
|
public Optional<Envelope<TranslatedMessage>> markRead(long messageId, User user) {
|
||||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"{class}.markRead(hash, user) not implemented!","class",getClass().getSimpleName()); // TODO
|
try {
|
||||||
// TODO: throw exception if message not found!
|
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
|
@Override
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ public class UserModule extends BaseHandler implements UserService {
|
|||||||
var subject = t("Your token to create a new password");
|
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 content = t("To receive a new password, open the following link: {url}",URL,url);
|
||||||
var message = new TranslatableMessage(user,subject,content,null);
|
var message = new TranslatableMessage(user,subject,content,null);
|
||||||
var envelope = new Envelope<>(message,user);
|
var envelope = new Envelope<>(0, message, user);
|
||||||
postBox().send(envelope);
|
postBox().send(envelope);
|
||||||
} catch (UmbrellaException e){
|
} catch (UmbrellaException e){
|
||||||
return send(ex,e);
|
return send(ex,e);
|
||||||
|
|||||||
Reference in New Issue
Block a user