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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user