This repository has been archived on 2021-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
envoy/src/main/java/envoy/client/net/Client.java

225 lines
7.2 KiB
Java
Raw Normal View History

package envoy.client.net;
2019-10-06 10:45:19 +02:00
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.naming.TimeLimitExceededException;
import envoy.client.data.Cache;
import envoy.client.data.Config;
import envoy.client.data.LocalDb;
import envoy.client.util.EnvoyLog;
import envoy.data.*;
import envoy.event.*;
import envoy.util.SerializationUtils;
2019-10-06 10:45:19 +02:00
/**
* Establishes a connection to the server, performs a handshake and delivers
* certain objects to the server.<br>
* <br>
2019-10-06 10:45:19 +02:00
* Project: <strong>envoy-client</strong><br>
* File: <strong>Client.java</strong><br>
2019-10-06 10:45:19 +02:00
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
2019-10-06 10:45:19 +02:00
*/
public class Client implements Closeable {
// Connection handling
private Socket socket;
private Receiver receiver;
private boolean online;
2019-10-06 10:45:19 +02:00
// Asynchronously initialized during handshake
private volatile User sender;
private volatile Contacts contacts;
2019-10-06 10:45:19 +02:00
// Configuration and logging
private static final Config config = Config.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName());
/**
* Enters the online mode by acquiring a user ID from the server. As a
* connection has to be established and a handshake has to be made, this method
* will block for up to 5 seconds. If the handshake does exceed this time limit,
* an exception is thrown.
*
* @param credentials the login credentials of the user
* @param localDb the local database used to persist the current
* {@link IdGenerator}
* @return a message cache containing all unread messages from the server that
* can be relayed after initialization
* @throws Exception if the online mode could not be entered or the request
* failed for some other reason
* @since Envoy v0.2-alpha
*/
public Cache<Message> onlineInit(LoginCredentials credentials, LocalDb localDb) throws Exception {
// Establish TCP connection
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort());
logger.info("Successfully connected to server.");
// Create message receiver
receiver = new Receiver(socket.getInputStream());
// Create cache for unread messages
final Cache<Message> cache = new Cache<>();
// Register user creation processor, contact list processor and message cache
receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; });
receiver.registerProcessor(Contacts.class, contacts -> { logger.info("Acquired contacts object " + contacts); this.contacts = contacts; });
receiver.registerProcessor(Message.class, cache);
// Start receiver
new Thread(receiver).start();
// Write login credentials
logger.finest("Sending login credentials...");
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
// Wait for a maximum of five seconds to acquire the sender object
long start = System.currentTimeMillis();
while (sender == null || contacts == null) {
if (System.currentTimeMillis() - start > 5000) throw new TimeLimitExceededException("Did not log in after 5 seconds");
Thread.sleep(500);
}
logger.info("Handshake completed.");
online = true;
// Remove user creation processor
receiver.removeAllProcessors();
// Register processors for message and status handling
// Process incoming messages
final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
receiver.registerProcessor(Message.class, receivedMessageProcessor);
// Relay cached unread messages
cache.setProcessor(receivedMessageProcessor);
// Process message status changes
receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor());
// Process user status changes
receiver.registerProcessor(UserStatusChangeEvent.class, new UserStatusChangeProcessor(this));
// Process message ID generation
receiver.registerProcessor(IdGenerator.class, localDb::setIdGenerator);
// Request a generator if none is present or the existing one is consumed
if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator();
return cache;
}
2019-11-09 13:25:18 +01:00
/**
* Creates a new write proxy that uses this client to communicate with the
* server.
*
* @param localDb the local database that the write proxy will use to access
* caches
* @return a new write proxy
* @since Envoy Client v0.3-alpha
*/
public WriteProxy createWriteProxy(LocalDb localDb) { return new WriteProxy(this, localDb); }
/**
* Sends a message to the server. The message's status will be incremented once
* it was delivered successfully.
*
* @param message the message to send
* @throws IOException if the message does not reach the server
* @since Envoy v0.3-alpha
*/
public void sendMessage(Message message) throws IOException {
writeObject(message);
message.nextStatus();
}
/**
* Sends an event to the server.
*
* @param evt the event to send
* @throws IOException if the event did not reach the server
*/
public void sendEvent(Event<?> evt) throws IOException { writeObject(evt); }
/**
* Requests a new {@link IdGenerator} from the server.
*
* @throws IOException if the request does not reach the server
* @since Envoy v0.3-alpha
*/
public void requestIdGenerator() throws IOException {
logger.info("Requesting new id generator...");
writeObject(new IdGeneratorRequest());
}
/**
* @return a {@code Map<String, User>} of all users on the server with their
* user names as keys
* @since Envoy v0.2-alpha
*/
public Map<String, User> getUsers() {
checkOnline();
Map<String, User> users = new HashMap<>();
contacts.getContacts().forEach(u -> users.put(u.getName(), u));
return users;
}
@Override
public void close() throws IOException { if (online) socket.close(); }
private void writeObject(Object obj) throws IOException {
checkOnline();
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
}
private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); }
/**
* @return the sender object that represents this client.
* @since Envoy v0.1-alpha
*/
public User getSender() { return sender; }
/**
* Sets the client user which is used to send messages.
*
* @param sender the client user to set
* @since Envoy v0.2-alpha
*/
public void setSender(User sender) { this.sender = sender; }
/**
* @return the {@link Receiver} used by this {@link Client}
*/
public Receiver getReceiver() { return receiver; }
/**
* @return {@code true} if a connection to the server could be established
* @since Envoy v0.2-alpha
*/
public boolean isOnline() { return online; }
/**
* @return the contacts of this {@link Client}
* @since Envoy v0.3-alpha
*/
public Contacts getContacts() { return contacts; }
/**
* @param contacts the contacts to set
* @since Envoy v0.3-alpha
*/
public void setContacts(Contacts contacts) { this.contacts = contacts; }
2019-12-21 21:23:19 +01:00
}