Move Envoy Server Standalone to server/ subdirectory
This commit is contained in:
89
server/src/main/java/envoy/server/Startup.java
Executable file
89
server/src/main/java/envoy/server/Startup.java
Executable file
@ -0,0 +1,89 @@
|
||||
package envoy.server;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.jenkov.nioserver.Server;
|
||||
|
||||
import envoy.data.Config;
|
||||
import envoy.data.ConfigItem;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectMessageProcessor;
|
||||
import envoy.server.net.ObjectMessageReader;
|
||||
import envoy.server.processors.*;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* Starts the server.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>Startup.java</strong><br>
|
||||
* Created: <strong>24.12.2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class Startup {
|
||||
|
||||
/**
|
||||
* Initializes the logger with a new config instance.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
private static void initLogging() {
|
||||
final var items = new HashMap<String, ConfigItem<?>>();
|
||||
items.put("homeDirectory",
|
||||
new ConfigItem<>("homeDirectory", "h", File::new, new File(System.getProperty("user.home"), ".envoy-server"), true));
|
||||
items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.WARNING, true));
|
||||
items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.FINEST, true));
|
||||
|
||||
final var config = new Config();
|
||||
config.load(items);
|
||||
|
||||
EnvoyLog.initialize(config);
|
||||
EnvoyLog.attach("envoy");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server.
|
||||
*
|
||||
* @param args the run configuration. If it is "no-enter-to-stop" at position 0,
|
||||
* no command to read in an enter press will be generated
|
||||
* @throws IOException if the server crashes
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
initLogging();
|
||||
|
||||
final Server server = new Server(8080, ObjectMessageReader::new,
|
||||
new ObjectMessageProcessor(Set.of(new LoginCredentialProcessor(),
|
||||
new MessageProcessor(),
|
||||
new GroupMessageProcessor(),
|
||||
new GroupCreationProcessor(),
|
||||
new MessageStatusChangeProcessor(),
|
||||
new GroupMessageStatusChangeProcessor(),
|
||||
new UserStatusChangeProcessor(),
|
||||
new IDGeneratorRequestProcessor(),
|
||||
new ContactSearchProcessor(),
|
||||
new ContactOperationProcessor())));
|
||||
|
||||
// Initialize the current message ID
|
||||
final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
if (persistenceManager.getConfigItemByID("currentMessageId") == null)
|
||||
persistenceManager.addConfigItem(new envoy.server.data.ConfigItem("currentMessageId", "0"));
|
||||
|
||||
server.start();
|
||||
server.getSocketProcessor().registerSocketIdListener(ConnectionManager.getInstance());
|
||||
|
||||
if (args.length == 0 || !args[0].equalsIgnoreCase("no-enter-to-stop")) {
|
||||
System.out.println("Press the return key to stop the server...");
|
||||
System.in.read();
|
||||
System.out.println("Stopped");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
65
server/src/main/java/envoy/server/data/ConfigItem.java
Executable file
65
server/src/main/java/envoy/server/data/ConfigItem.java
Executable file
@ -0,0 +1,65 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ConfigItem.java</strong><br>
|
||||
* Created: <strong>28 Jan 2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "configuration")
|
||||
public class ConfigItem {
|
||||
|
||||
@Id
|
||||
private String key;
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* Creates an instance of @link{ConfigItem}.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public ConfigItem() {}
|
||||
|
||||
/**
|
||||
* Creates an instance of @link{ConfigItem}.
|
||||
*
|
||||
* @param key the name of this {@link ConfigItem}
|
||||
* @param value the value of this {@link ConfigItem}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public ConfigItem(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the key
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public String getKey() { return key; }
|
||||
|
||||
/**
|
||||
* @param key the key to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setKey(String key) { this.key = key; }
|
||||
|
||||
/**
|
||||
* @return the value
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public String getValue() { return value; }
|
||||
|
||||
/**
|
||||
* @param value the value to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setValue(String value) { this.value = value; }
|
||||
}
|
107
server/src/main/java/envoy/server/data/Contact.java
Normal file
107
server/src/main/java/envoy/server/data/Contact.java
Normal file
@ -0,0 +1,107 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
/**
|
||||
* This class acts as a superclass for all contacts, being {@link User}s and
|
||||
* {@link Group}s. <br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>Contact.java</strong><br>
|
||||
* Created: <strong>24.03.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
|
||||
@Entity
|
||||
@Table(name = "contacts")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
public abstract class Contact {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
protected long id;
|
||||
protected String name;
|
||||
|
||||
@Column(name = "creation_date")
|
||||
private LocalDateTime creationDate;
|
||||
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
protected Set<Contact> contacts;
|
||||
|
||||
/**
|
||||
* @return a {@link envoy.data.Contact} object of this envoy.server.data.Contact
|
||||
* object.
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public abstract envoy.data.Contact toCommon();
|
||||
|
||||
/**
|
||||
* Transforms this contact into a {@link envoy.data.Contact} where the contacts
|
||||
* set of contacts is empty.
|
||||
*
|
||||
* @return a {@link envoy.data.Contact} object of this contact
|
||||
* object.
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
protected abstract envoy.data.Contact toFlatCommon();
|
||||
|
||||
/**
|
||||
* @return the ID of this contact.
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public long getID() { return id; }
|
||||
|
||||
/**
|
||||
* Sets the ID of this contact.
|
||||
*
|
||||
* @param id to set for this contact
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setID(long id) { this.id = id; }
|
||||
|
||||
/**
|
||||
* @return the name of this contact.
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public String getName() { return name; }
|
||||
|
||||
/**
|
||||
* Sets the name of this contact.
|
||||
*
|
||||
* @param name to set for this contact
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
/**
|
||||
* @return the contacts
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public Set<Contact> getContacts() { return contacts; }
|
||||
|
||||
/**
|
||||
* @param contacts the contacts to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setContacts(Set<Contact> contacts) { this.contacts = contacts; }
|
||||
|
||||
/**
|
||||
* @return the creationDate
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public LocalDateTime getCreationDate() { return creationDate; }
|
||||
|
||||
/**
|
||||
* @param creationDate the creationDate to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setCreationDate(LocalDateTime creationDate) { this.creationDate = creationDate; }
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("%s[id=%d,name=%s, %d contact(s)]", getClass().getSimpleName(), id, name, contacts.size()); }
|
||||
}
|
54
server/src/main/java/envoy/server/data/Group.java
Normal file
54
server/src/main/java/envoy/server/data/Group.java
Normal file
@ -0,0 +1,54 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
|
||||
/**
|
||||
* Represents a group inside the database. Referred to as "server group" as
|
||||
* opposed to "group" from Envoy Common.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>Group.java</strong><br>
|
||||
* Created: <strong>24.03.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(
|
||||
name = Group.findByName,
|
||||
query = "SELECT g FROM Group g WHERE g.name = :name"
|
||||
),
|
||||
@NamedQuery(
|
||||
name = Group.findPendingGroups,
|
||||
query = "SELECT g FROM Group g WHERE g.creationDate > :lastSeen AND :user MEMBER OF g.contacts"
|
||||
)
|
||||
})
|
||||
public class Group extends Contact {
|
||||
|
||||
/**
|
||||
* Named query retrieving a group by name (parameter {@code :name}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String findByName = "Group.findByName";
|
||||
|
||||
/**
|
||||
* Named query retrieving all pending groups for a specific user (parameter {@code :user}, {@code :lastSeen}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String findPendingGroups = "Group.findPendingGroups";
|
||||
|
||||
@Override
|
||||
public envoy.data.Group toCommon() {
|
||||
return new envoy.data.Group(id, name, contacts.parallelStream().map(User.class::cast).map(User::toFlatCommon).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected envoy.data.Group toFlatCommon() { return toCommon(); }
|
||||
}
|
104
server/src/main/java/envoy/server/data/GroupMessage.java
Normal file
104
server/src/main/java/envoy/server/data/GroupMessage.java
Normal file
@ -0,0 +1,104 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
import envoy.data.Group;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>GroupMessage.java</strong><br>
|
||||
* Created: <strong>18.04.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
@Entity
|
||||
@NamedQuery(
|
||||
name = GroupMessage.getPendingGroupMsg,
|
||||
query = "SELECT m FROM GroupMessage m JOIN m.memberMessageStatus s WHERE (KEY(s) = :userId) AND ((m.creationDate > :lastSeen)"
|
||||
+ "OR ((m.status = envoy.data.Message$MessageStatus.RECEIVED) AND (m.receivedDate > :lastSeen))"
|
||||
+ "OR ((m.status = envoy.data.Message$MessageStatus.READ) AND (m.readDate > :lastSeen))"
|
||||
+ "OR ((m.lastStatusChangeDate > :lastSeen)))"
|
||||
)
|
||||
public class GroupMessage extends Message {
|
||||
|
||||
/**
|
||||
* Named query retrieving pending group messages sent to a group containing a
|
||||
* specific user (parameter {@code userId}) that were sent after a certain time
|
||||
* stamp (parameter {@code :lastSeen}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String getPendingGroupMsg = "GroupMessage.getPendingGroupMsg";
|
||||
|
||||
@ElementCollection
|
||||
private Map<Long, envoy.data.Message.MessageStatus> memberMessageStatus;
|
||||
|
||||
@Column(name = "last_status_change_date")
|
||||
protected LocalDateTime lastStatusChangeDate;
|
||||
|
||||
/**
|
||||
* The constructor for a database object.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public GroupMessage() {}
|
||||
|
||||
/**
|
||||
* Constructs a database groupMessage from a common groupMessage.
|
||||
*
|
||||
* @param groupMessage the {@link envoy.data.GroupMessage} to convert
|
||||
* into a
|
||||
* database {@link GroupMessage}
|
||||
* @param lastStatusChangeDate the time stamp to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public GroupMessage(envoy.data.GroupMessage groupMessage, LocalDateTime lastStatusChangeDate) {
|
||||
super(groupMessage);
|
||||
memberMessageStatus = groupMessage.getMemberStatuses();
|
||||
this.lastStatusChangeDate = lastStatusChangeDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this groupMessage into an instance of
|
||||
* {@link envoy.data.GroupMessage}.
|
||||
*
|
||||
* @return a {@link envoy.data.GroupMessage} containing the same values as this
|
||||
* groupMessage
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
@Override
|
||||
public envoy.data.GroupMessage toCommon() {
|
||||
return prepareBuilder().buildGroupMessage((Group) recipient.toCommon(), new HashMap<>(memberMessageStatus));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the memberMessageStatus
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public Map<Long, envoy.data.Message.MessageStatus> getMemberMessageStatus() { return memberMessageStatus; }
|
||||
|
||||
/**
|
||||
* @param memberMessageStatus the memberMessageStatus to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setMemberMessageStatus(Map<Long, envoy.data.Message.MessageStatus> memberMessageStatus) {
|
||||
this.memberMessageStatus = memberMessageStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the date at which one of the member statuses changed last
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public LocalDateTime getLastStatusChangeDate() { return lastStatusChangeDate; }
|
||||
|
||||
/**
|
||||
* @param date the date to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setLastStatusChangeDate(LocalDateTime date) { lastStatusChangeDate = date; }
|
||||
}
|
290
server/src/main/java/envoy/server/data/Message.java
Executable file
290
server/src/main/java/envoy/server/data/Message.java
Executable file
@ -0,0 +1,290 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import static envoy.data.Message.MessageStatus.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
import envoy.data.Attachment;
|
||||
import envoy.data.Attachment.AttachmentType;
|
||||
import envoy.data.Message.MessageStatus;
|
||||
import envoy.data.MessageBuilder;
|
||||
|
||||
/**
|
||||
* This class serves as a way to let Hibernate communicate with the server
|
||||
* without bringing the dependency of JPA/Hibernate into the client.<br>
|
||||
* It will be referenced as "database message" to clarify between the different
|
||||
* message objects.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>Message.java</strong><br>
|
||||
* Created: <strong>02.01.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "messages")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@NamedQuery(
|
||||
name = Message.getPending,
|
||||
query = "SELECT m FROM Message m WHERE (m.recipient = :user AND m.status = envoy.data.Message$MessageStatus.SENT) "
|
||||
+ "OR (m.sender = :user) AND ((m.status = envoy.data.Message$MessageStatus.RECEIVED) AND (m.receivedDate > :lastSeen)"
|
||||
+ "OR (m.status = envoy.data.Message$MessageStatus.READ) AND (m.readDate > :lastSeen))"
|
||||
)
|
||||
public class Message {
|
||||
|
||||
/**
|
||||
* Named query retrieving pending messages for a user (parameter {@code :user})
|
||||
* which was last seen after a specific date (parameter {@code :lastSeen}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String getPending = "Message.getPending";
|
||||
|
||||
@Id
|
||||
protected long id;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn
|
||||
protected User sender;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn
|
||||
protected Contact recipient;
|
||||
|
||||
@Column(name = "creation_date")
|
||||
protected LocalDateTime creationDate;
|
||||
|
||||
@Column(name = "received_date")
|
||||
protected LocalDateTime receivedDate;
|
||||
|
||||
@Column(name = "read_date")
|
||||
protected LocalDateTime readDate;
|
||||
|
||||
protected String text;
|
||||
protected envoy.data.Message.MessageStatus status;
|
||||
protected AttachmentType attachmentType;
|
||||
protected byte[] attachment;
|
||||
protected boolean forwarded;
|
||||
|
||||
/**
|
||||
* The constructor for a database object.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public Message() {}
|
||||
|
||||
/**
|
||||
* Constructs a database message from a common message.
|
||||
*
|
||||
* @param message the {@link envoy.data.Message} to convert into a database
|
||||
* {@link Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public Message(envoy.data.Message message) {
|
||||
PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
id = message.getID();
|
||||
status = message.getStatus();
|
||||
text = message.getText();
|
||||
creationDate = message.getCreationDate();
|
||||
receivedDate = message.getReceivedDate();
|
||||
readDate = message.getReadDate();
|
||||
sender = persistenceManager.getUserByID(message.getSenderID());
|
||||
recipient = persistenceManager.getContactByID(message.getRecipientID());
|
||||
forwarded = message.isForwarded();
|
||||
if (message.hasAttachment()) {
|
||||
attachment = message.getAttachment().getData();
|
||||
attachmentType = message.getAttachment().getType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this message into an instance of {@link envoy.data.Message}.
|
||||
*
|
||||
* @return a {@link envoy.data.Message} containing the same values as this
|
||||
* message
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public envoy.data.Message toCommon() {
|
||||
return prepareBuilder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a message builder containing the state of this message
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
MessageBuilder prepareBuilder() {
|
||||
var builder = new MessageBuilder(sender.getID(), recipient.getID(), id).setText(
|
||||
text)
|
||||
.setCreationDate(creationDate)
|
||||
.setReceivedDate(receivedDate)
|
||||
.setReadDate(readDate)
|
||||
.setStatus(status)
|
||||
.setForwarded(forwarded);
|
||||
if (attachment != null) builder.setAttachment(new Attachment(attachment, attachmentType));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message status to {@link MessageStatus#RECEIVED} and sets the
|
||||
* current time stamp as the received date.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void received() {
|
||||
receivedDate = LocalDateTime.now();
|
||||
status = RECEIVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message status to {@link MessageStatus#READ} and sets the
|
||||
* current time stamp as the read date.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void read() {
|
||||
readDate = LocalDateTime.now();
|
||||
status = READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id of a {link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public long getID() { return id; }
|
||||
|
||||
/**
|
||||
* @param id the id to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
* @see Message#getID()
|
||||
*/
|
||||
public void setId(long id) { this.id = id; }
|
||||
|
||||
/**
|
||||
* @return the sender of a {link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public User getSender() { return sender; }
|
||||
|
||||
/**
|
||||
* @param sender the sender to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
* @see Message#getSender()
|
||||
*/
|
||||
public void setSender(User sender) { this.sender = sender; }
|
||||
|
||||
/**
|
||||
* @return the recipient of a {link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public Contact getRecipient() { return recipient; }
|
||||
|
||||
/**
|
||||
* @param recipient the recipient to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
* @see Message#getRecipient()
|
||||
*/
|
||||
public void setRecipient(User recipient) { this.recipient = recipient; }
|
||||
|
||||
/**
|
||||
* @return the date at which a {link envoy.data.Message} has been created
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public LocalDateTime getCreationDate() { return creationDate; }
|
||||
|
||||
/**
|
||||
* @param creationDate the creation date to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
* @see Message#getCreationDate()
|
||||
*/
|
||||
public void setCreationDate(LocalDateTime creationDate) { this.creationDate = creationDate; }
|
||||
|
||||
/**
|
||||
* @return the date at which a {link envoy.data.Message} has been received by
|
||||
* the server
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public LocalDateTime getReceivedDate() { return receivedDate; }
|
||||
|
||||
/**
|
||||
* @param receivedDate the received date to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
* @see Message#getReceivedDate()
|
||||
*/
|
||||
public void setReceivedDate(LocalDateTime receivedDate) { this.receivedDate = receivedDate; }
|
||||
|
||||
/**
|
||||
* @return the date at which a {link envoy.data.Message} has been read
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public LocalDateTime getReadDate() { return readDate; }
|
||||
|
||||
/**
|
||||
* @param readDate the read date to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
* @see Message#getReadDate()
|
||||
*/
|
||||
public void setReadDate(LocalDateTime readDate) { this.readDate = readDate; }
|
||||
|
||||
/**
|
||||
* @return the status of a {link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public envoy.data.Message.MessageStatus getStatus() { return status; }
|
||||
|
||||
/**
|
||||
* @param status the new status of a {link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setStatus(envoy.data.Message.MessageStatus status) { this.status = status; }
|
||||
|
||||
/**
|
||||
* @return the text content of a {link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public String getText() { return text; }
|
||||
|
||||
/**
|
||||
* @param text the new text content of a {@link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setText(String text) { this.text = text; }
|
||||
|
||||
/**
|
||||
* @return the attachment of a {@link envoy.data.Message}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public byte[] getAttachment() { return attachment; }
|
||||
|
||||
/**
|
||||
* @param attachment the new attachment
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setAttachment(byte[] attachment) { this.attachment = attachment; }
|
||||
|
||||
/**
|
||||
* @return the attachmentType
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public AttachmentType getAttachmentType() { return attachmentType; }
|
||||
|
||||
/**
|
||||
* @param attachmentType the attachmentType to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setAttachmentType(AttachmentType attachmentType) { this.attachmentType = attachmentType; }
|
||||
|
||||
/**
|
||||
* @return whether this message is a forwarded message
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public boolean isForwarded() { return forwarded; }
|
||||
|
||||
/**
|
||||
* @param forwarded this message should be a forwarded message.
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setForwarded(boolean forwarded) { this.forwarded = forwarded; }
|
||||
}
|
281
server/src/main/java/envoy/server/data/PersistenceManager.java
Executable file
281
server/src/main/java/envoy/server/data/PersistenceManager.java
Executable file
@ -0,0 +1,281 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
import javax.persistence.Persistence;
|
||||
|
||||
import envoy.data.User.UserStatus;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>PersistenceManager.java</strong><br>
|
||||
* Created: <strong>1 Jan 2020</strong><br>
|
||||
*
|
||||
* @author Leon Hofmeister
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class PersistenceManager {
|
||||
|
||||
private final EntityManager entityManager = Persistence.createEntityManagerFactory("envoy").createEntityManager();
|
||||
private final EntityTransaction transaction = entityManager.getTransaction();
|
||||
|
||||
private static final PersistenceManager persistenceManager = new PersistenceManager();
|
||||
|
||||
/**
|
||||
* Creates the singleton instance of the @link{PersistenceManager}.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
private PersistenceManager() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
transaction.begin();
|
||||
ConnectionManager.getInstance()
|
||||
.getOnlineUsers()
|
||||
.stream()
|
||||
.map(this::getUserByID)
|
||||
.forEach(user -> { user.setStatus(UserStatus.OFFLINE); user.setLastSeen(LocalDateTime.now()); entityManager.merge(user); });
|
||||
transaction.commit();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link PersistenceManager} singleton
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public static PersistenceManager getInstance() { return persistenceManager; }
|
||||
|
||||
/**
|
||||
* Adds a {@link Contact} to the database.
|
||||
*
|
||||
* @param contact the {@link Contact} to add to the database
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void addContact(Contact contact) { persist(contact); }
|
||||
|
||||
/**
|
||||
* Adds a {@link Message} to the database.
|
||||
*
|
||||
* @param message the {@link Message} to add to the database
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void addMessage(Message message) { persist(message); }
|
||||
|
||||
/**
|
||||
* Adds a {@link ConfigItem} to the database.
|
||||
*
|
||||
* @param configItem the {@link ConfigItem} to add to the database
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void addConfigItem(ConfigItem configItem) { persist(configItem); }
|
||||
|
||||
/**
|
||||
* Updates a {@link Contact} in the database
|
||||
*
|
||||
* @param contact the {@link Contact} to add to the database
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void updateContact(Contact contact) { merge(contact); }
|
||||
|
||||
/**
|
||||
* Updates a {@link Message} in the database.
|
||||
*
|
||||
* @param message the message to update
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void updateMessage(Message message) { merge(message); }
|
||||
|
||||
/**
|
||||
* Updates a {@link ConfigItem} in the database.
|
||||
*
|
||||
* @param configItem the configItem to update
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void updateConfigItem(ConfigItem configItem) { merge(configItem); }
|
||||
|
||||
/**
|
||||
* Deletes a {@link Contact} in the database.
|
||||
*
|
||||
* @param contact the {@link Contact} to delete
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void deleteContact(Contact contact) { remove(contact); }
|
||||
|
||||
/**
|
||||
* Deletes a {@link Message} in the database.
|
||||
*
|
||||
* @param message the {@link Message} to delete
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void deleteMessage(Message message) { remove(message); }
|
||||
|
||||
/**
|
||||
* Searches for a {@link User} with a specific ID.
|
||||
*
|
||||
* @param id the id to search for
|
||||
* @return the user with the specified ID or {@code null} if none was found
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public User getUserByID(long id) { return entityManager.find(User.class, id); }
|
||||
|
||||
/**
|
||||
* Searches for a {@link Group} with a specific ID.
|
||||
*
|
||||
* @param id the id to search for
|
||||
* @return the group with the specified ID or {@code null} if none was found
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public Group getGroupByID(long id) { return entityManager.find(Group.class, id); }
|
||||
|
||||
/**
|
||||
* Searches for a {@link Contact} with a specific ID.
|
||||
*
|
||||
* @param id the id to search for
|
||||
* @return the contact with the specified ID or {@code null} if none was found
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public Contact getContactByID(long id) { return entityManager.find(Contact.class, id); }
|
||||
|
||||
/**
|
||||
* Searched for a {@link User} with a specific name.
|
||||
*
|
||||
* @param name the name of the user
|
||||
* @return the user with the specified name
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public User getUserByName(String name) {
|
||||
return (User) entityManager.createNamedQuery(User.findByName).setParameter("name", name).getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searched for a {@link Group} with a specific name.
|
||||
*
|
||||
* @param name the name of the group
|
||||
* @return the group with the specified name
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public Group getGroupByName(String name) {
|
||||
return (Group) entityManager.createNamedQuery(Group.findByName).setParameter("name", name).getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a {@link Message} with a specific id.
|
||||
*
|
||||
* @param id the id to search for
|
||||
* @return the message with the specified ID or {@code null} if none is found
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public Message getMessageByID(long id) { return entityManager.find(Message.class, id); }
|
||||
|
||||
/**
|
||||
* @param key the name of this {@link ConfigItem}
|
||||
* @return the {@link ConfigItem} with the given name
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public ConfigItem getConfigItemByID(String key) { return entityManager.find(ConfigItem.class, key); }
|
||||
|
||||
/**
|
||||
* Returns all messages received while being offline or the ones that have
|
||||
* changed.
|
||||
*
|
||||
* @param user the user who wants to receive his unread messages
|
||||
* @return all messages that the client does not yet have (unread messages)
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public List<Message> getPendingMessages(User user) {
|
||||
return entityManager
|
||||
.createNamedQuery(Message.getPending)
|
||||
.setParameter("user", user)
|
||||
.setParameter("lastSeen", user.getLastSeen())
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all groupMessages received while being offline or the ones that have
|
||||
* changed.
|
||||
*
|
||||
* @param user the user who wants to receive his unread groupMessages
|
||||
* @return all groupMessages that the client does not yet have (unread
|
||||
* groupMessages)
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public List<GroupMessage> getPendingGroupMessages(User user) {
|
||||
return entityManager.createNamedQuery(GroupMessage.getPendingGroupMsg)
|
||||
.setParameter("userId", user.getID())
|
||||
.setParameter("lastSeen", user.getLastSeen())
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for users matching a search phrase. Contacts of the attached user
|
||||
* and the attached user is ignored.
|
||||
*
|
||||
* @param searchPhrase the search phrase
|
||||
* @param userId the ID of the user in whose context the search is
|
||||
* performed
|
||||
* @return a list of all users who matched the criteria
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public List<User> searchUsers(String searchPhrase, long userId) {
|
||||
return entityManager.createNamedQuery(
|
||||
User.searchByName)
|
||||
.setParameter("searchPhrase", searchPhrase + "%")
|
||||
.setParameter("context", getUserByID(userId))
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a contact to the contact list of another contact and vice versa.
|
||||
*
|
||||
* @param contactID1 the ID of the first contact
|
||||
* @param contactID2 the ID of the second contact
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void addContactBidirectional(long contactID1, long contactID2) {
|
||||
|
||||
// Get users by ID
|
||||
Contact c1 = getContactByID(contactID1);
|
||||
Contact c2 = getContactByID(contactID2);
|
||||
|
||||
// Add users to each others contact lists
|
||||
c1.getContacts().add(c2);
|
||||
c2.getContacts().add(c1);
|
||||
|
||||
// Synchronize changes with the database
|
||||
transaction.begin();
|
||||
entityManager.merge(c1);
|
||||
entityManager.merge(c2);
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user the User whose contacts should be retrieved
|
||||
* @return the contacts of this User
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public List<User> getContacts(User user) {
|
||||
return entityManager.createNamedQuery(User.findContacts).setParameter("user", user).getResultList();
|
||||
}
|
||||
|
||||
private void persist(Object obj) {
|
||||
transaction.begin();
|
||||
entityManager.persist(obj);
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
private void merge(Object obj) {
|
||||
transaction.begin();
|
||||
entityManager.merge(obj);
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
private void remove(Object obj) {
|
||||
transaction.begin();
|
||||
entityManager.remove(obj);
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
116
server/src/main/java/envoy/server/data/User.java
Executable file
116
server/src/main/java/envoy/server/data/User.java
Executable file
@ -0,0 +1,116 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
import envoy.data.User.UserStatus;
|
||||
|
||||
/**
|
||||
* This class enables the storage of user specific data inside a database using
|
||||
* Hibernate. Its objects will be referred to as database users as opposed to
|
||||
* the common user objects present on both the client and the server.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>User.java</strong><br>
|
||||
* Created: <strong>02.01.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(
|
||||
name = User.findByName,
|
||||
query = "SELECT u FROM User u WHERE u.name = :name"
|
||||
),
|
||||
@NamedQuery(
|
||||
name = User.findContacts,
|
||||
query = "SELECT u.contacts FROM User u WHERE u = :user"
|
||||
),
|
||||
@NamedQuery(
|
||||
name = User.searchByName,
|
||||
query = "SELECT u FROM User u WHERE (lower(u.name) LIKE lower(:searchPhrase) AND u <> :context AND :context NOT MEMBER OF u.contacts)"
|
||||
)
|
||||
})
|
||||
public class User extends Contact {
|
||||
|
||||
/**
|
||||
* Named query retrieving a user by name (parameter {@code :name}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String findByName = "User.findByName";
|
||||
|
||||
/**
|
||||
* Named query retrieving the contacts of a given user (parameter
|
||||
* {@code :user}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String findContacts = "User.findContacts";
|
||||
|
||||
/**
|
||||
* Named query searching for users with a name like a search phrase (parameter
|
||||
* {@code :searchPhrase}) that are not in the contact list of a given user
|
||||
* (parameter {@code :context}).
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String searchByName = "User.searchByName";
|
||||
|
||||
@Column(name = "password_hash")
|
||||
private String passwordHash;
|
||||
|
||||
@Column(name = "last_seen")
|
||||
private LocalDateTime lastSeen;
|
||||
|
||||
private UserStatus status;
|
||||
|
||||
@Override
|
||||
public envoy.data.User toCommon() {
|
||||
return new envoy.data.User(id, name, status, contacts.parallelStream().map(Contact::toFlatCommon).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected envoy.data.User toFlatCommon() { return new envoy.data.User(id, name, status, Set.of()); }
|
||||
|
||||
/**
|
||||
* @return the password hash
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public String getPasswordHash() { return passwordHash; }
|
||||
|
||||
/**
|
||||
* @param passwordHash the password hash to set
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
|
||||
|
||||
/**
|
||||
* @return the last date the user has been online
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public LocalDateTime getLastSeen() { return lastSeen; }
|
||||
|
||||
/**
|
||||
* @param lastSeen the latest date at which the user has been online to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setLastSeen(LocalDateTime lastSeen) { this.lastSeen = lastSeen; }
|
||||
|
||||
/**
|
||||
* @return the status
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public UserStatus getStatus() { return status; }
|
||||
|
||||
/**
|
||||
* @param status the status to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void setStatus(UserStatus status) { this.status = status; }
|
||||
}
|
9
server/src/main/java/envoy/server/data/package-info.java
Executable file
9
server/src/main/java/envoy/server/data/package-info.java
Executable file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This package contains classes related to persistence.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Leon Hofmeister
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
package envoy.server.data;
|
115
server/src/main/java/envoy/server/net/ConnectionManager.java
Executable file
115
server/src/main/java/envoy/server/net/ConnectionManager.java
Executable file
@ -0,0 +1,115 @@
|
||||
package envoy.server.net;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.jenkov.nioserver.ISocketIdListener;
|
||||
|
||||
import envoy.data.User.UserStatus;
|
||||
import envoy.server.data.Group;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.processors.UserStatusChangeProcessor;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ConnectionManager.java</strong><br>
|
||||
* Created: <strong>03.01.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class ConnectionManager implements ISocketIdListener {
|
||||
|
||||
/**
|
||||
* Contains all socket IDs that have not yet performed a handshake / acquired
|
||||
* their corresponding user ID.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
private Set<Long> pendingSockets = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Contains all socket IDs that have acquired a user ID as keys to these IDs.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
private Map<Long, Long> sockets = new HashMap<>();
|
||||
|
||||
private static ConnectionManager connectionManager = new ConnectionManager();
|
||||
|
||||
private ConnectionManager() {}
|
||||
|
||||
/**
|
||||
* @return a singleton instance of this object
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public static ConnectionManager getInstance() { return connectionManager; }
|
||||
|
||||
@Override
|
||||
public void socketCancelled(long socketID) {
|
||||
if (!pendingSockets.remove(socketID)) {
|
||||
// Notify contacts of this users offline-going
|
||||
envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
|
||||
user.setStatus(UserStatus.OFFLINE);
|
||||
user.setLastSeen(LocalDateTime.now());
|
||||
UserStatusChangeProcessor.updateUserStatus(user);
|
||||
|
||||
// Remove the socket
|
||||
sockets.entrySet().removeIf(e -> e.getValue() == socketID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void socketRegistered(long socketID) { pendingSockets.add(socketID); }
|
||||
|
||||
/**
|
||||
* Associates a socket ID with a user ID.
|
||||
*
|
||||
* @param userID the user ID
|
||||
* @param socketID the socket ID
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void registerUser(long userID, long socketID) {
|
||||
sockets.put(userID, socketID);
|
||||
pendingSockets.remove(socketID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userID the ID of the user registered at a socket
|
||||
* @return the ID of the socket
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public long getSocketID(long userID) { return sockets.get(userID); }
|
||||
|
||||
/**
|
||||
* @param socketID the id of the socket whose User is needed
|
||||
* @return the userId associated with this socketId
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public long getUserIDBySocketID(long socketID) {
|
||||
return sockets.entrySet().stream().filter(entry -> entry.getValue().equals(socketID)).findFirst().get().getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userID the ID of the user to check for
|
||||
* @return {@code true} if the user is online
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public boolean isOnline(long userID) { return sockets.containsKey(userID); }
|
||||
|
||||
/**
|
||||
* @return the userIDs of all users who are currently online
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public Set<Long> getOnlineUsers() { return sockets.keySet(); }
|
||||
|
||||
/**
|
||||
* @param group the group to search for
|
||||
* @return a set of all IDs of currently active members in this group
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public Set<Long> getOnlineUsersOfGroup(Group group) {
|
||||
return group.getContacts().stream().map(envoy.server.data.Contact::getID).filter(this::isOnline).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
65
server/src/main/java/envoy/server/net/ObjectMessageProcessor.java
Executable file
65
server/src/main/java/envoy/server/net/ObjectMessageProcessor.java
Executable file
@ -0,0 +1,65 @@
|
||||
package envoy.server.net;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jenkov.nioserver.IMessageProcessor;
|
||||
import com.jenkov.nioserver.Message;
|
||||
import com.jenkov.nioserver.WriteProxy;
|
||||
|
||||
import envoy.server.processors.ObjectProcessor;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* Handles incoming objects.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ObjectMessageProcessor.java</strong><br>
|
||||
* Created: <strong>28.12.2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class ObjectMessageProcessor implements IMessageProcessor {
|
||||
|
||||
private final Set<ObjectProcessor<?>> processors;
|
||||
|
||||
private static final Logger logger = EnvoyLog.getLogger(ObjectMessageProcessor.class);
|
||||
|
||||
/**
|
||||
* The constructor to set the {@link ObjectProcessor}s.
|
||||
*
|
||||
* @param processors the {@link ObjectProcessor} to set
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public ObjectMessageProcessor(Set<ObjectProcessor<?>> processors) { this.processors = processors; }
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void process(Message message, WriteProxy writeProxy) {
|
||||
try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(message.sharedArray, message.offset + 4, message.length - 4))) {
|
||||
Object obj = in.readObject();
|
||||
if (obj == null) {
|
||||
logger.warning("Received a null object");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.fine("Received " + obj);
|
||||
|
||||
// Process object
|
||||
processors.stream().filter(p -> p.getInputClass().equals(obj.getClass())).forEach((@SuppressWarnings("rawtypes") ObjectProcessor p) -> {
|
||||
try {
|
||||
p.process(p.getInputClass().cast(obj), message.socketId, new ObjectWriteProxy(writeProxy));
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Exception during processor execution: ", e);
|
||||
}
|
||||
});
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
71
server/src/main/java/envoy/server/net/ObjectMessageReader.java
Executable file
71
server/src/main/java/envoy/server/net/ObjectMessageReader.java
Executable file
@ -0,0 +1,71 @@
|
||||
package envoy.server.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jenkov.nioserver.*;
|
||||
|
||||
import envoy.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* This {@link IMessageReader} decodes serialized Java objects.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ObjectMessageReader.java</strong><br>
|
||||
* Created: <strong>28.12.2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class ObjectMessageReader implements IMessageReader {
|
||||
|
||||
private List<Message> completeMessages = new ArrayList<>();
|
||||
private Message nextMessage;
|
||||
private MessageBuffer messageBuffer;
|
||||
|
||||
@Override
|
||||
public List<Message> getMessages() { return completeMessages; }
|
||||
|
||||
@Override
|
||||
public void init(MessageBuffer messageBuffer) {
|
||||
this.messageBuffer = messageBuffer;
|
||||
nextMessage = messageBuffer.getMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Socket socket, ByteBuffer buffer) throws IOException {
|
||||
socket.read(buffer);
|
||||
buffer.flip();
|
||||
|
||||
if (!buffer.hasRemaining()) {
|
||||
buffer.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
nextMessage.writeToMessage(buffer);
|
||||
buffer.clear();
|
||||
|
||||
// Get message length
|
||||
if (nextMessage.length < 4) return;
|
||||
int length = SerializationUtils.bytesToInt(nextMessage.sharedArray, nextMessage.offset) + 4;
|
||||
do {
|
||||
|
||||
// Separate first complete message
|
||||
if (nextMessage.length >= length) {
|
||||
Message message = messageBuffer.getMessage();
|
||||
message.writePartialMessageToMessage(nextMessage, length);
|
||||
message.length = nextMessage.length - length;
|
||||
nextMessage.length = length;
|
||||
completeMessages.add(nextMessage);
|
||||
nextMessage = message;
|
||||
}
|
||||
|
||||
// Get message length
|
||||
if (nextMessage.length < 4) return;
|
||||
length = SerializationUtils.bytesToInt(nextMessage.sharedArray, nextMessage.offset) + 4;
|
||||
|
||||
} while (nextMessage.length >= length);
|
||||
}
|
||||
}
|
88
server/src/main/java/envoy/server/net/ObjectWriteProxy.java
Executable file
88
server/src/main/java/envoy/server/net/ObjectWriteProxy.java
Executable file
@ -0,0 +1,88 @@
|
||||
package envoy.server.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.jenkov.nioserver.Message;
|
||||
import com.jenkov.nioserver.WriteProxy;
|
||||
|
||||
import envoy.server.data.Contact;
|
||||
import envoy.util.EnvoyLog;
|
||||
import envoy.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* This class defines methods to send an object to a client.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ObjectWriteProxy.java</strong><br>
|
||||
* Created: <strong>04.01.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class ObjectWriteProxy {
|
||||
|
||||
private final WriteProxy writeProxy;
|
||||
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(ObjectWriteProxy.class);
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link ObjectWriteProxy}.
|
||||
*
|
||||
* @param writeProxy the {@link WriteProxy} to write objects to another client
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public ObjectWriteProxy(WriteProxy writeProxy) { this.writeProxy = writeProxy; }
|
||||
|
||||
/**
|
||||
* @param recipientSocketID the socket id of the recipient
|
||||
* @param obj the object to return to the client
|
||||
* @throws RuntimeException if the serialization of the object failed (this is
|
||||
* highly unlikely)
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public void write(long recipientSocketID, Object obj) {
|
||||
// Create message targeted at the client
|
||||
final Message response = writeProxy.getMessage();
|
||||
response.socketId = recipientSocketID;
|
||||
logger.fine("Sending " + obj);
|
||||
|
||||
try {
|
||||
|
||||
// Serialize object to byte array
|
||||
final byte[] objBytes = SerializationUtils.writeToByteArray(obj);
|
||||
|
||||
// Acquire object length in bytes
|
||||
final byte[] objLen = SerializationUtils.intToBytes(objBytes.length);
|
||||
|
||||
response.writeToMessage(objLen);
|
||||
response.writeToMessage(objBytes);
|
||||
writeProxy.enqueue(response);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an object to all contact in a set that are online.
|
||||
*
|
||||
* @param contacts the contacts to send the object to
|
||||
* @param message the object to send
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void writeToOnlineContacts(Set<? extends Contact> contacts, Object message) { writeToOnlineContacts(contacts.stream(), message); }
|
||||
|
||||
/**
|
||||
* Sends an object to all contact in a set that are online.
|
||||
*
|
||||
* @param contacts the contacts to send the object to
|
||||
* @param message the object to send
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public void writeToOnlineContacts(Stream<? extends Contact> contacts, Object message) {
|
||||
contacts.map(Contact::getID).filter(connectionManager::isOnline).map(connectionManager::getSocketID).forEach(id -> write(id, message));
|
||||
}
|
||||
}
|
9
server/src/main/java/envoy/server/net/package-info.java
Executable file
9
server/src/main/java/envoy/server/net/package-info.java
Executable file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This package contains all classes related to client connection management.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Leon Hofmeister
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
package envoy.server.net;
|
9
server/src/main/java/envoy/server/package-info.java
Executable file
9
server/src/main/java/envoy/server/package-info.java
Executable file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This package contains the class that manages application startup.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Leon Hofmeister
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
package envoy.server;
|
47
server/src/main/java/envoy/server/processors/ContactOperationProcessor.java
Executable file
47
server/src/main/java/envoy/server/processors/ContactOperationProcessor.java
Executable file
@ -0,0 +1,47 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import envoy.event.ElementOperation;
|
||||
import envoy.event.contact.ContactOperation;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ContactOperationProcessor.java</strong><br>
|
||||
* Created: <strong>08.02.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class ContactOperationProcessor implements ObjectProcessor<ContactOperation> {
|
||||
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(ContactOperationProcessor.class);
|
||||
|
||||
@Override
|
||||
public void process(ContactOperation evt, long socketId, ObjectWriteProxy writeProxy) {
|
||||
switch (evt.getOperationType()) {
|
||||
case ADD:
|
||||
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId);
|
||||
final long contactId = evt.get().getID();
|
||||
|
||||
logger.fine(String.format("Adding user %s to the contact list of user %d.%n", evt.get(), userID));
|
||||
PersistenceManager.getInstance().addContactBidirectional(userID, contactId);
|
||||
|
||||
// Notify the contact if online
|
||||
if (ConnectionManager.getInstance().isOnline(contactId))
|
||||
writeProxy.write(connectionManager.getSocketID(contactId),
|
||||
new ContactOperation(PersistenceManager.getInstance().getUserByID(userID).toCommon(), ElementOperation.ADD));
|
||||
break;
|
||||
default:
|
||||
logger.warning(String.format("Received %s with an unsupported operation.", evt));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ContactOperation> getInputClass() { return ContactOperation.class; }
|
||||
}
|
44
server/src/main/java/envoy/server/processors/ContactSearchProcessor.java
Executable file
44
server/src/main/java/envoy/server/processors/ContactSearchProcessor.java
Executable file
@ -0,0 +1,44 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import envoy.data.Contact;
|
||||
import envoy.event.contact.ContactSearchRequest;
|
||||
import envoy.event.contact.ContactSearchResult;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.data.User;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ContactSearchProcessor.java</strong><br>
|
||||
* Created: <strong>08.02.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class ContactSearchProcessor implements ObjectProcessor<ContactSearchRequest> {
|
||||
|
||||
/**
|
||||
* Writes a list of contacts to the client containing all {@link Contact}s
|
||||
* matching the search phrase contained inside the request. The client and their
|
||||
* contacts are excluded from the result.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
@Override
|
||||
public void process(ContactSearchRequest request, long socketID, ObjectWriteProxy writeProxy) throws IOException {
|
||||
writeProxy.write(socketID,
|
||||
new ContactSearchResult(PersistenceManager.getInstance()
|
||||
.searchUsers(request.get(), ConnectionManager.getInstance().getUserIDBySocketID(socketID))
|
||||
.stream()
|
||||
.map(User::toCommon)
|
||||
.collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ContactSearchRequest> getInputClass() { return ContactSearchRequest.class; }
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import envoy.event.ElementOperation;
|
||||
import envoy.event.GroupCreation;
|
||||
import envoy.event.contact.ContactOperation;
|
||||
import envoy.server.data.Contact;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>GroupCreationProcessor.java</strong><br>
|
||||
* Created: <strong>26.03.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class GroupCreationProcessor implements ObjectProcessor<GroupCreation> {
|
||||
|
||||
private final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void process(GroupCreation groupCreation, long socketID, ObjectWriteProxy writeProxy) {
|
||||
envoy.server.data.Group group = new envoy.server.data.Group();
|
||||
group.setName(groupCreation.get());
|
||||
group.setContacts(new HashSet<>());
|
||||
groupCreation.getInitialMemberIDs().stream().map(persistenceManager::getUserByID).forEach(group.getContacts()::add);
|
||||
group.getContacts().add(persistenceManager.getContactByID(connectionManager.getUserIDBySocketID(socketID)));
|
||||
group.getContacts().forEach(c -> c.getContacts().add(group));
|
||||
group.getContacts().add(persistenceManager.getUserByID(connectionManager.getUserIDBySocketID(socketID)));
|
||||
persistenceManager.addContact(group);
|
||||
group.getContacts()
|
||||
.stream()
|
||||
.map(Contact::getID)
|
||||
.filter(connectionManager::isOnline)
|
||||
.map(connectionManager::getSocketID)
|
||||
.forEach(memberSocketID -> writeProxy.write(memberSocketID, new ContactOperation(group.toCommon(), ElementOperation.ADD)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<GroupCreation> getInputClass() { return GroupCreation.class; }
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import static envoy.data.Message.MessageStatus.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
import envoy.data.GroupMessage;
|
||||
import envoy.event.MessageStatusChange;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>GroupMessageProcessor.java</strong><br>
|
||||
* Created: <strong>18.04.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class GroupMessageProcessor implements ObjectProcessor<GroupMessage> {
|
||||
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(GroupCreationProcessor.class);
|
||||
|
||||
@Override
|
||||
public void process(GroupMessage groupMessage, long socketID, ObjectWriteProxy writeProxy) {
|
||||
groupMessage.nextStatus();
|
||||
|
||||
// Update statuses to SENT / RECEIVED depending on online status
|
||||
groupMessage.getMemberStatuses().replaceAll((memberID, status) -> connectionManager.isOnline(memberID) ? RECEIVED : SENT);
|
||||
|
||||
// Set status for sender to READ
|
||||
groupMessage.getMemberStatuses().replace(groupMessage.getSenderID(), READ);
|
||||
|
||||
// Increment the overall status to RECEIVED if necessary
|
||||
if (Collections.min(groupMessage.getMemberStatuses().values()) == RECEIVED) {
|
||||
groupMessage.nextStatus();
|
||||
|
||||
// Notify the sender of the status change
|
||||
writeProxy.write(socketID, new MessageStatusChange(groupMessage));
|
||||
}
|
||||
|
||||
// Deliver the message to the recipients that are online
|
||||
writeProxy.writeToOnlineContacts(
|
||||
persistenceManager.getGroupByID(groupMessage.getRecipientID())
|
||||
.getContacts()
|
||||
.stream()
|
||||
.filter(c -> c.getID() != groupMessage.getSenderID()),
|
||||
groupMessage);
|
||||
|
||||
try {
|
||||
PersistenceManager.getInstance().addMessage(new envoy.server.data.GroupMessage(groupMessage, LocalDateTime.now()));
|
||||
} catch (EntityExistsException e) {
|
||||
logger.warning("Received a groupMessage with an ID that already exists");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<GroupMessage> getInputClass() { return GroupMessage.class; }
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import static envoy.data.Message.MessageStatus.READ;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import envoy.data.Message.MessageStatus;
|
||||
import envoy.event.GroupMessageStatusChange;
|
||||
import envoy.event.MessageStatusChange;
|
||||
import envoy.server.data.GroupMessage;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>GroupMessageStatusChangeProcessor.java</strong><br>
|
||||
* Created: <strong>03.07.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class GroupMessageStatusChangeProcessor implements ObjectProcessor<GroupMessageStatusChange> {
|
||||
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeProcessor.class);
|
||||
|
||||
@Override
|
||||
public void process(GroupMessageStatusChange statusChange, long socketID, ObjectWriteProxy writeProxy) {
|
||||
GroupMessage gmsg = (GroupMessage) persistenceManager.getMessageByID(statusChange.getID());
|
||||
|
||||
// Any other status than READ is not supposed to be sent to the server
|
||||
if (statusChange.get() != MessageStatus.READ) {
|
||||
logger.log(Level.WARNING, "Invalid " + statusChange);
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply the status change
|
||||
gmsg.getMemberMessageStatus().replace(statusChange.getMemberID(), statusChange.get());
|
||||
gmsg.setLastStatusChangeDate(LocalDateTime.now());
|
||||
|
||||
// Notifying the other members about the status change
|
||||
final var userID = connectionManager.getUserIDBySocketID(socketID);
|
||||
gmsg.getMemberMessageStatus()
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(k -> userID != k)
|
||||
.filter(connectionManager::isOnline)
|
||||
.forEach(k -> writeProxy.write(connectionManager.getSocketID(k), statusChange));
|
||||
|
||||
// Increment overall status to READ if necessary
|
||||
if (Collections.min(gmsg.getMemberMessageStatus().values()) == READ) {
|
||||
gmsg.read();
|
||||
|
||||
// Notify online members about the status change
|
||||
writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(),
|
||||
new MessageStatusChange(gmsg.getID(), gmsg.getStatus(), LocalDateTime.now()));
|
||||
}
|
||||
persistenceManager.updateMessage(gmsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<GroupMessageStatusChange> getInputClass() { return GroupMessageStatusChange.class; }
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import envoy.event.GroupResize;
|
||||
import envoy.server.data.Contact;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>GroupResizeProcessor.java</strong><br>
|
||||
* Created: <strong>03.04.2020</strong><br>
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class GroupResizeProcessor implements ObjectProcessor<GroupResize> {
|
||||
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void process(GroupResize groupResize, long socketID, ObjectWriteProxy writeProxy) {
|
||||
|
||||
// Acquire the group to resize from the database
|
||||
var group = persistenceManager.getGroupByID(groupResize.getGroupID());
|
||||
|
||||
// Perform the desired operation
|
||||
switch (groupResize.getOperation()) {
|
||||
case ADD:
|
||||
group.getContacts().add(persistenceManager.getUserByID(groupResize.get().getID()));
|
||||
break;
|
||||
case REMOVE:
|
||||
group.getContacts().remove(persistenceManager.getUserByID(groupResize.get().getID()));
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the group in the database
|
||||
persistenceManager.updateContact(group);
|
||||
|
||||
// Send the updated group to all of its members
|
||||
var commonGroup = group.toCommon();
|
||||
group.getContacts()
|
||||
.stream()
|
||||
.map(Contact::getID)
|
||||
.filter(connectionManager::isOnline)
|
||||
.map(connectionManager::getSocketID)
|
||||
.forEach(memberSocketID -> writeProxy.write(memberSocketID, commonGroup));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<GroupResize> getInputClass() { return GroupResize.class; }
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import envoy.data.IDGenerator;
|
||||
import envoy.event.IDGeneratorRequest;
|
||||
import envoy.server.data.ConfigItem;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>IDGeneratorRequestProcessor.java</strong><br>
|
||||
* Created: <strong>28 Jan 2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class IDGeneratorRequestProcessor implements ObjectProcessor<IDGeneratorRequest> {
|
||||
|
||||
private static final long ID_RANGE = 200;
|
||||
|
||||
@Override
|
||||
public Class<IDGeneratorRequest> getInputClass() { return IDGeneratorRequest.class; }
|
||||
|
||||
@Override
|
||||
public void process(IDGeneratorRequest input, long socketID, ObjectWriteProxy writeProxy) throws IOException {
|
||||
writeProxy.write(socketID, createIDGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new IDGenerator
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static IDGenerator createIDGenerator() { return createIDGenerator(ID_RANGE); }
|
||||
|
||||
/**
|
||||
* @param range of IDs used by the new IDGenerator
|
||||
* @return a new IDGenerator with a specific range of IDs
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static IDGenerator createIDGenerator(long range) {
|
||||
ConfigItem currentID = PersistenceManager.getInstance().getConfigItemByID("currentMessageId");
|
||||
IDGenerator generator = new IDGenerator(Integer.parseInt(currentID.getValue()), range);
|
||||
currentID.setValue(String.valueOf(Integer.parseInt(currentID.getValue()) + range));
|
||||
PersistenceManager.getInstance().updateConfigItem(currentID);
|
||||
return generator;
|
||||
}
|
||||
}
|
189
server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java
Executable file
189
server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java
Executable file
@ -0,0 +1,189 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import static envoy.data.Message.MessageStatus.*;
|
||||
import static envoy.data.User.UserStatus.ONLINE;
|
||||
import static envoy.event.HandshakeRejection.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.persistence.NoResultException;
|
||||
|
||||
import envoy.data.LoginCredentials;
|
||||
import envoy.event.GroupMessageStatusChange;
|
||||
import envoy.event.HandshakeRejection;
|
||||
import envoy.event.MessageStatusChange;
|
||||
import envoy.server.data.GroupMessage;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.data.User;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.server.util.PasswordUtil;
|
||||
import envoy.server.util.VersionUtil;
|
||||
import envoy.util.Bounds;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* This {@link ObjectProcessor} handles {@link LoginCredentials}.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>LoginCredentialProcessor.java</strong><br>
|
||||
* Created: <strong>30.12.2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public final class LoginCredentialProcessor implements ObjectProcessor<LoginCredentials> {
|
||||
|
||||
private final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
|
||||
private static final Logger logger = EnvoyLog.getLogger(LoginCredentialProcessor.class);
|
||||
|
||||
@Override
|
||||
public void process(LoginCredentials credentials, long socketID, ObjectWriteProxy writeProxy) {
|
||||
|
||||
// Cache this write proxy for user-independant notifications
|
||||
UserStatusChangeProcessor.setWriteProxy(writeProxy);
|
||||
|
||||
if (!VersionUtil.verifyCompatibility(credentials.getClientVersion())) {
|
||||
logger.info("The client has the wrong version.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_VERSION));
|
||||
return;
|
||||
}
|
||||
|
||||
// Acquire a user object (or reject the handshake if that's impossible)
|
||||
User user = null;
|
||||
if (!credentials.isRegistration()) {
|
||||
try {
|
||||
user = persistenceManager.getUserByName(credentials.getIdentifier());
|
||||
|
||||
// Checking if user is already online
|
||||
if (connectionManager.isOnline(user.getID())) {
|
||||
logger.warning(user + " is already online!");
|
||||
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
|
||||
return;
|
||||
}
|
||||
// Evaluating the correctness of the password hash
|
||||
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
||||
logger.info(user + " has entered the wrong password.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||
return;
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
logger.info("The requested user does not exist.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Validate user name
|
||||
if (!Bounds.isValidContactName(credentials.getIdentifier())) {
|
||||
logger.info("The requested user name is not valid.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Checking that no user already has this identifier
|
||||
PersistenceManager.getInstance().getUserByName(credentials.getIdentifier());
|
||||
|
||||
// This code only gets executed if this user already exists
|
||||
logger.info("The requested user already exists.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
|
||||
return;
|
||||
} catch (NoResultException e) {
|
||||
// Creation of a new user
|
||||
user = new User();
|
||||
user.setName(credentials.getIdentifier());
|
||||
user.setLastSeen(LocalDateTime.now());
|
||||
user.setStatus(ONLINE);
|
||||
user.setPasswordHash(PasswordUtil.hash(credentials.getPassword()));
|
||||
user.setContacts(new HashSet<>());
|
||||
persistenceManager.addContact(user);
|
||||
logger.info("Registered new " + user);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(user + " successfully authenticated.");
|
||||
connectionManager.registerUser(user.getID(), socketID);
|
||||
|
||||
// Change status and notify contacts about it
|
||||
user.setStatus(ONLINE);
|
||||
UserStatusChangeProcessor.updateUserStatus(user);
|
||||
|
||||
// Complete the handshake
|
||||
writeProxy.write(socketID, user.toCommon());
|
||||
|
||||
final var pendingMessages = PersistenceManager.getInstance().getPendingMessages(user);
|
||||
pendingMessages.removeIf(GroupMessage.class::isInstance);
|
||||
logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "...");
|
||||
|
||||
for (var msg : pendingMessages) {
|
||||
final var msgCommon = msg.toCommon();
|
||||
if (msg.getStatus() == SENT) {
|
||||
|
||||
// Send the message
|
||||
writeProxy.write(socketID, msgCommon);
|
||||
msg.received();
|
||||
PersistenceManager.getInstance().updateMessage(msg);
|
||||
|
||||
// Notify the sender about the delivery
|
||||
if (connectionManager.isOnline(msg.getSender().getID())) {
|
||||
msgCommon.nextStatus();
|
||||
writeProxy.write(connectionManager.getSocketID(msg.getSender().getID()), new MessageStatusChange(msgCommon));
|
||||
}
|
||||
} else writeProxy.write(socketID, new MessageStatusChange(msgCommon));
|
||||
}
|
||||
|
||||
List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user);
|
||||
logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "...");
|
||||
|
||||
for (var gmsg : pendingGroupMessages) {
|
||||
final var gmsgCommon = gmsg.toCommon();
|
||||
|
||||
// Deliver the message to the user if he hasn't received it yet
|
||||
if (gmsg.getMemberMessageStatus().get(user.getID()) == SENT) {
|
||||
gmsg.getMemberMessageStatus().replace(user.getID(), RECEIVED);
|
||||
|
||||
gmsg.setLastStatusChangeDate(LocalDateTime.now());
|
||||
|
||||
writeProxy.write(socketID, gmsgCommon);
|
||||
|
||||
// Notify all online group members about the status change
|
||||
writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(),
|
||||
new GroupMessageStatusChange(gmsg.getID(), RECEIVED, LocalDateTime
|
||||
.now(),
|
||||
connectionManager.getUserIDBySocketID(socketID)));
|
||||
|
||||
if (Collections.min(gmsg.getMemberMessageStatus().values()) == RECEIVED) {
|
||||
gmsg.received();
|
||||
|
||||
// Notify online members about the status change
|
||||
writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(),
|
||||
new MessageStatusChange(gmsg.getID(), gmsg.getStatus(), LocalDateTime.now()));
|
||||
}
|
||||
|
||||
PersistenceManager.getInstance().updateMessage(gmsg);
|
||||
} else {
|
||||
|
||||
// Sending group message status changes
|
||||
if (gmsg.getStatus() == SENT && gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate())
|
||||
|| gmsg.getStatus() == RECEIVED && gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate())) {
|
||||
gmsg.getMemberMessageStatus().forEach((memberID, memberStatus) ->
|
||||
writeProxy.write(socketID, new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID)));
|
||||
}
|
||||
|
||||
// Deliver just a status change instead of the whole message
|
||||
if (gmsg.getStatus() == SENT && user.getLastSeen().isBefore(gmsg.getCreationDate())
|
||||
|| gmsg.getStatus() == RECEIVED && user.getLastSeen().isBefore(gmsg.getReceivedDate()))
|
||||
writeProxy.write(socketID, new MessageStatusChange(gmsgCommon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<LoginCredentials> getInputClass() { return LoginCredentials.class; }
|
||||
}
|
64
server/src/main/java/envoy/server/processors/MessageProcessor.java
Executable file
64
server/src/main/java/envoy/server/processors/MessageProcessor.java
Executable file
@ -0,0 +1,64 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
import envoy.data.Message;
|
||||
import envoy.event.MessageStatusChange;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* This {@link ObjectProcessor} handles incoming {@link Message}s.
|
||||
* <p>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>MessageProcessor.java</strong><br>
|
||||
* Created: <strong>30.12.2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class MessageProcessor implements ObjectProcessor<Message> {
|
||||
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(MessageProcessor.class);
|
||||
|
||||
@Override
|
||||
public void process(Message message, long socketID, ObjectWriteProxy writeProxy) {
|
||||
message.nextStatus();
|
||||
|
||||
// Convert to server message
|
||||
final var serverMessage = new envoy.server.data.Message(message);
|
||||
|
||||
try {
|
||||
|
||||
// Persist the message
|
||||
persistenceManager.addMessage(serverMessage);
|
||||
|
||||
// Send the message to the recipient if online
|
||||
if (connectionManager.isOnline(message.getRecipientID())) {
|
||||
writeProxy.write(connectionManager.getSocketID(message.getRecipientID()), message);
|
||||
|
||||
// Increment status
|
||||
message.nextStatus();
|
||||
serverMessage.received();
|
||||
persistenceManager.updateMessage(serverMessage);
|
||||
|
||||
// Notify the sender about the delivery
|
||||
// Note that the exact time stamp might differ slightly
|
||||
writeProxy.write(socketID, new MessageStatusChange(message));
|
||||
}
|
||||
} catch (EntityExistsException e) {
|
||||
logger.log(Level.WARNING, "Received " + message + " with an ID that already exists!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Message> getInputClass() { return Message.class; }
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import envoy.data.Message.MessageStatus;
|
||||
import envoy.event.MessageStatusChange;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>MessageStatusChangeProcessor.java</strong><br>
|
||||
* Created: <strong>10 Jan 2020</strong><br>
|
||||
*
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class MessageStatusChangeProcessor implements ObjectProcessor<MessageStatusChange> {
|
||||
|
||||
private final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private final Logger logger = EnvoyLog.getLogger(MessageStatusChangeProcessor.class);
|
||||
|
||||
@Override
|
||||
public void process(MessageStatusChange statusChange, long socketID, ObjectWriteProxy writeProxy) throws IOException {
|
||||
|
||||
// Any other status than READ is not supposed to be sent to the server
|
||||
if (statusChange.get() != MessageStatus.READ) {
|
||||
logger.log(Level.WARNING, "Invalid " + statusChange);
|
||||
return;
|
||||
}
|
||||
|
||||
final var msg = persistenceManager.getMessageByID(statusChange.getID());
|
||||
msg.read();
|
||||
persistenceManager.updateMessage(msg);
|
||||
|
||||
// Notifies the sender of the message about the status-update to READ
|
||||
final long senderID = msg.getSender().getID();
|
||||
if (connectionManager.isOnline(senderID)) writeProxy.write(connectionManager.getSocketID(senderID), statusChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<MessageStatusChange> getInputClass() { return MessageStatusChange.class; }
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import envoy.event.NameChange;
|
||||
import envoy.server.data.Contact;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
|
||||
/**
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>NameChangeProcessor.java</strong><br>
|
||||
* Created: <strong>26 Mar 2020</strong><br>
|
||||
*
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class NameChangeProcessor implements ObjectProcessor<NameChange> {
|
||||
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void process(NameChange nameChange, long socketID, ObjectWriteProxy writeProxy) throws IOException {
|
||||
Contact toUpdate = persistenceManager.getContactByID(nameChange.getID());
|
||||
toUpdate.setName(nameChange.get());
|
||||
persistenceManager.updateContact(toUpdate);
|
||||
|
||||
// Notify online contacts of the name change
|
||||
writeProxy.writeToOnlineContacts(toUpdate.getContacts(), nameChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<NameChange> getInputClass() { return NameChange.class; }
|
||||
}
|
35
server/src/main/java/envoy/server/processors/ObjectProcessor.java
Executable file
35
server/src/main/java/envoy/server/processors/ObjectProcessor.java
Executable file
@ -0,0 +1,35 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
|
||||
/**
|
||||
* This interface defines methods for processing objects of a specific
|
||||
* type incoming from a client.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>ObjectProcessor.java</strong><br>
|
||||
* Created: <strong>30.12.2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @param <T> type of the request object
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public interface ObjectProcessor<T> {
|
||||
|
||||
/**
|
||||
* @return the class of the request object
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
Class<T> getInputClass();
|
||||
|
||||
/**
|
||||
* @param input the request object
|
||||
* @param socketID the ID of the socket from which the object was received
|
||||
* @param writeProxy the object that allows writing to a client
|
||||
* @throws IOException if something went wrong during processing
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
void process(T input, long socketID, ObjectWriteProxy writeProxy) throws IOException;
|
||||
}
|
77
server/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java
Executable file
77
server/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java
Executable file
@ -0,0 +1,77 @@
|
||||
package envoy.server.processors;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import envoy.data.User.UserStatus;
|
||||
import envoy.event.UserStatusChange;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.data.User;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
/**
|
||||
* This processor handles incoming {@link UserStatusChange}.
|
||||
* <p>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>UserStatusChangeProcessor.java</strong><br>
|
||||
* Created: <strong>1 Feb 2020</strong><br>
|
||||
*
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public class UserStatusChangeProcessor implements ObjectProcessor<UserStatusChange> {
|
||||
|
||||
private static ObjectWriteProxy writeProxy;
|
||||
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(UserStatusChangeProcessor.class);
|
||||
|
||||
@Override
|
||||
public Class<UserStatusChange> getInputClass() { return UserStatusChange.class; }
|
||||
|
||||
@Override
|
||||
public void process(UserStatusChange input, long socketID, ObjectWriteProxy writeProxy) {
|
||||
// new status should not equal old status
|
||||
if (input.get().equals(persistenceManager.getUserByID(input.getID()).getStatus())) {
|
||||
logger.warning("Received an unnecessary UserStatusChange");
|
||||
return;
|
||||
}
|
||||
updateUserStatus(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link UserStatus} for a given user. Both offline contacts and
|
||||
* currently online contacts are notified.
|
||||
*
|
||||
* @param user the {@link UserStatusChange} that signals the change
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
|
||||
public static void updateUserStatus(User user) {
|
||||
|
||||
// Handling for newly logged in clients
|
||||
persistenceManager.updateContact(user);
|
||||
|
||||
// Handling for contacts that are already online
|
||||
writeProxy.writeToOnlineContacts(user.getContacts(), new UserStatusChange(user.getID(), user.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt the {@link UserStatusChange}
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public static void updateUserStatus(UserStatusChange evt) { updateUserStatus(persistenceManager.getUserByID(evt.getID())); }
|
||||
|
||||
/**
|
||||
* This method is only called by the LoginCredentialProcessor because every
|
||||
* user needs to login (open a socket) before changing his status.
|
||||
* Needed to ensure propagation of events because an uninitialized writeProxy
|
||||
* would cause problems.
|
||||
*
|
||||
* @param writeProxy the writeProxy that is used to send objects back to clients
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public static void setWriteProxy(ObjectWriteProxy writeProxy) { UserStatusChangeProcessor.writeProxy = writeProxy; }
|
||||
// TODO may cause an problem if two clients log in at the same time.
|
||||
// Change Needed.
|
||||
}
|
10
server/src/main/java/envoy/server/processors/package-info.java
Executable file
10
server/src/main/java/envoy/server/processors/package-info.java
Executable file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* This package contains all classes that process data received from client
|
||||
* connections.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Leon Hofmeister
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
package envoy.server.processors;
|
98
server/src/main/java/envoy/server/util/PasswordUtil.java
Normal file
98
server/src/main/java/envoy/server/util/PasswordUtil.java
Normal file
@ -0,0 +1,98 @@
|
||||
package envoy.server.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
/**
|
||||
* Provides a password hashing and comparison mechanism using the
|
||||
* {@code PBKDF2WithHmacSHA1} algorithm.
|
||||
* <p>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>PasswordUtil.java</strong><br>
|
||||
* Created: <strong>10.07.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class PasswordUtil {
|
||||
|
||||
private static final int ITERATIONS = 1000;
|
||||
private static final int KEY_LENGTH = 64 * 8;
|
||||
private static final String SALT_ALGORITHM = "SHA1PRNG";
|
||||
private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA1";
|
||||
|
||||
private PasswordUtil() {}
|
||||
|
||||
/**
|
||||
* Validates a password against a stored password hash
|
||||
*
|
||||
* @param current the password to validate
|
||||
* @param stored the hash to validate against
|
||||
* @return {@code true} if the password is correct
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static boolean validate(String current, String stored) {
|
||||
try {
|
||||
String[] parts = stored.split(":");
|
||||
int iterations = Integer.parseInt(parts[0]);
|
||||
byte[] salt = fromHex(parts[1]);
|
||||
byte[] hash = fromHex(parts[2]);
|
||||
|
||||
var spec = new PBEKeySpec(current.toCharArray(), salt, iterations, KEY_LENGTH);
|
||||
var skf = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
|
||||
byte[] testHash = skf.generateSecret(spec).getEncoded();
|
||||
|
||||
int diff = hash.length ^ testHash.length;
|
||||
for (int i = 0; i < hash.length && i < testHash.length; ++i)
|
||||
diff |= hash[i] ^ testHash[i];
|
||||
|
||||
return diff == 0;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a parameterized salted password hash.
|
||||
*
|
||||
* @param password the password to hash
|
||||
* @return a result string in the form of {@code iterations:salt:hash}
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static String hash(String password) {
|
||||
try {
|
||||
byte[] salt = salt();
|
||||
var spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
|
||||
var skf = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
|
||||
byte[] hash = skf.generateSecret(spec).getEncoded();
|
||||
return ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] salt() throws NoSuchAlgorithmException {
|
||||
SecureRandom sr = SecureRandom.getInstance(SALT_ALGORITHM);
|
||||
byte[] salt = new byte[16];
|
||||
sr.nextBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
|
||||
private static String toHex(byte[] bytes) {
|
||||
String hex = new BigInteger(1, bytes).toString(16);
|
||||
int padding = bytes.length * 2 - hex.length();
|
||||
return padding > 0 ? String.format("%0" + padding + "d", 0) + hex : hex;
|
||||
}
|
||||
|
||||
private static byte[] fromHex(String hex) {
|
||||
byte[] bytes = new byte[hex.length() / 2];
|
||||
for (int i = 0; i < bytes.length; ++i)
|
||||
bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
|
||||
return bytes;
|
||||
}
|
||||
}
|
94
server/src/main/java/envoy/server/util/VersionUtil.java
Normal file
94
server/src/main/java/envoy/server/util/VersionUtil.java
Normal file
@ -0,0 +1,94 @@
|
||||
package envoy.server.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Implements a comparison algorithm between Envoy versions and defines minimal
|
||||
* and maximal client versions compatible with this server.
|
||||
* <p>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>VersionUtil.java</strong><br>
|
||||
* Created: <strong>23.06.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public class VersionUtil {
|
||||
|
||||
/**
|
||||
* The minimal client version compatible with this server.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String MIN_CLIENT_VERSION = "0.1-beta";
|
||||
|
||||
/**
|
||||
* The maximal client version compatible with this server.
|
||||
*
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static final String MAX_CLIENT_VERSION = "0.1-beta";
|
||||
|
||||
private static final Pattern versionPattern = Pattern.compile("(?<major>\\d).(?<minor>\\d)(?:-(?<suffix>\\w+))?");
|
||||
|
||||
private VersionUtil() {}
|
||||
|
||||
/**
|
||||
* Parses an Envoy Client version string and checks whether that version is
|
||||
* compatible with this server.
|
||||
*
|
||||
* @param version the version string to parse
|
||||
* @return {@code true} if the given version is compatible with this server
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
public static boolean verifyCompatibility(String version) {
|
||||
final var currentMatcher = versionPattern.matcher(version);
|
||||
|
||||
if (!currentMatcher.matches()) return false;
|
||||
|
||||
final var minMatcher = versionPattern.matcher(MIN_CLIENT_VERSION);
|
||||
final var maxMatcher = versionPattern.matcher(MAX_CLIENT_VERSION);
|
||||
|
||||
if (!minMatcher.matches() || !maxMatcher.matches()) throw new RuntimeException("Invalid min or max client version configured!");
|
||||
|
||||
// Compare suffixes
|
||||
{
|
||||
final var currentSuffix = convertSuffix(currentMatcher.group("suffix"));
|
||||
final var minSuffix = convertSuffix(minMatcher.group("suffix"));
|
||||
final var maxSuffix = convertSuffix(maxMatcher.group("suffix"));
|
||||
|
||||
if (currentSuffix < minSuffix || currentSuffix > maxSuffix) return false;
|
||||
}
|
||||
|
||||
// Compare major
|
||||
{
|
||||
final var currentMajor = Integer.parseInt(currentMatcher.group("major"));
|
||||
final var minMajor = Integer.parseInt(minMatcher.group("major"));
|
||||
final var maxMajor = Integer.parseInt(maxMatcher.group("major"));
|
||||
|
||||
if (currentMajor < minMajor || currentMajor > maxMajor) return false;
|
||||
}
|
||||
|
||||
// Compare minor
|
||||
{
|
||||
final var currentMinor = Integer.parseInt(currentMatcher.group("minor"));
|
||||
final var minMinor = Integer.parseInt(minMatcher.group("minor"));
|
||||
final var maxMinor = Integer.parseInt(maxMatcher.group("minor"));
|
||||
|
||||
if (currentMinor < minMinor || currentMinor > maxMinor) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int convertSuffix(String suffix) {
|
||||
switch (suffix == null ? "" : suffix) {
|
||||
case "alpha":
|
||||
return 0;
|
||||
case "beta":
|
||||
return 1;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
11
server/src/main/java/envoy/server/util/package-info.java
Normal file
11
server/src/main/java/envoy/server/util/package-info.java
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* This package contains utility classes used in Envoy Server.
|
||||
* <p>
|
||||
* Project: <strong>envoy-server-standalone</strong><br>
|
||||
* File: <strong>package-info.java</strong><br>
|
||||
* Created: <strong>23.06.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
package envoy.server.util;
|
20
server/src/main/java/module-info.java
Executable file
20
server/src/main/java/module-info.java
Executable file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* This module contains all classes defining the server application of the Envoy
|
||||
* project.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @author Leon Hofmeister
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Server Standalone v0.1-beta
|
||||
*/
|
||||
module envoy.server {
|
||||
|
||||
opens envoy.server.data;
|
||||
|
||||
requires transitive envoy.common;
|
||||
requires transitive java.nio.server;
|
||||
requires transitive java.persistence;
|
||||
requires transitive java.sql;
|
||||
requires transitive org.hibernate.orm.core;
|
||||
|
||||
}
|
27
server/src/main/resources/META-INF/persistence.xml
Executable file
27
server/src/main/resources/META-INF/persistence.xml
Executable file
@ -0,0 +1,27 @@
|
||||
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
|
||||
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
|
||||
version="2.1">
|
||||
<persistence-unit name="envoy"
|
||||
transaction-type="RESOURCE_LOCAL">
|
||||
<properties>
|
||||
|
||||
<!-- JDBC -->
|
||||
<property name="javax.persistence.jdbc.driver"
|
||||
value="org.postgresql.Driver" />
|
||||
<property name="javax.persistence.jdbc.url"
|
||||
value="jdbc:postgresql://localhost/envoy" />
|
||||
<property name="javax.persistence.jdbc.user" value="envoy" />
|
||||
<property name="javax.persistence.jdbc.password"
|
||||
value="envoy" />
|
||||
|
||||
|
||||
<!-- Hibernate -->
|
||||
<property name="hibernate.dialect"
|
||||
value="org.hibernate.dialect.PostgreSQL95Dialect" />
|
||||
<property name="hibernate.hbm2ddl.auto" value="update" />
|
||||
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
Reference in New Issue
Block a user