Lesson 12 - Java Server - Local Area Network Propagation (Part 2)
In the previous lesson, Java Server - Local Area Network Propagation (Part 1), we started working on the visibility of our Java chat server to local network clients.
Today we're going to continue modifying the remaining classes, whose interfaces we modified.
Modifying Existing Classes
We'll start by editing the ParameterFactory
class. In the first
part, we added the non-parametric getParameters()
method to the
interface, which we'll now implement:
@Singleton public class ParameterFactory implements IParameterFactory { private IParameterProvider parameterProvider; @Override public IParameterProvider getParameters() { if (parameterProvider == null) { throw new IllegalStateException("Initialize parameters first."); } return parameterProvider; } @Override public IParameterProvider getParameters(String[] args) { if (parameterProvider == null) { parameterProvider = new CmdParser(args); } return parameterProvider; } }
We added one instance variable, parameterProvider
, of the
IParameterProvider
type. We'll initialize this variable in the
parametric getParameters()
method. This solution isn't ideal, but
it's the simplest. All we have to do is call the parametric version of the
getParameters()
method first, and then we may call its
parameterless version.
The next class is ConnectionManager
for which we added two new
methods to the interface: getConnectedClientCount()
and
getMaxClients()
. The implementation of the methods is as
follows:
@Override public int getConnectedClientCount() { return clients.size(); } @Override public int getMaxClients() { return maxClients; }
We get the number of connected clients from the size of the
clients
collection. The maximum number of clients is stored in the
maxClients
instance constant.
The third class to edit is ServerThread
, whose interface now
inherits from the ServerInfoProvider
interface. This interface
requires the getServerStatusMessage()
method to be implemented:
@Override public IMessage getServerStatusMessage() { return null; }
We leave the method body empty for now. We'll return to this method later and add it.
Starting the Multicast Sender
We'll stick with ServerThread
for a while, because we'll run the
MulticastSender
class from there. We'll add an instance constant of
the IMulticastSender
type to the class and initialize it in the
constructor from a parameter:
private final IMulticastSender multicastSender; @Inject ServerThread(IConnectionManager connectionManager, IMulticastSenderFactory multicastSenderFactory, int port) { super("ServerThread"); this.connectionManager = connectionManager; this.multicastSender = multicastSenderFactory.getMulticastSender(this); this.port = port; }
We don't add the IMulticastSender
interface to the server
constructor directly, but only its factory. In the constructor, we initialize
multicastSender
using the factory and its
getMulticastSender()
method, to which we pass this
as
a parameter. this
which is a ServerThread
instance
that implements the ServerInfoProvider
interface.
We'll start and stop MulticastSender
in the run()
method:
@Override public void run() { // Method start multicastSender.start(); // Starting the multicast sender connectionManager.onServerStart(); // ... // Method end multicastSender.shutdown(); connectionManager.onServerStop(); }
By modifying the ServerThread
constructor we broke the factory
to instantiate this class. Let's fix it. We'll add a new instance constant of
the IMulticastSenderFactory
type to the
ServerThreadFactory
and initialize it in the class constructor from
a parameter:
private final IMulticastSenderFactory multicastSenderFactory; @Inject public ServerThreadFactory(IConnectionManagerFactory connectionManagerFactory, IMulticastSenderFactory multicastSenderFactory) { this.connectionManagerFactory = connectionManagerFactory; this.multicastSenderFactory = multicastSenderFactory; }
In the getServerThread()
method, we'll just add the factory to
the right place as a parameter:
return new ServerThread(connectionManagerFactory.getConnectionManager(maxClients, waitingQueueSize), multicastSenderFactory, port);
ServerStatusMessage
Now we'll create a message that will contain the server status information. The message will contain a total of 6 attributes:
serverID
- server idserverStatus
- server status (empty, full, have space)clientCount
- number of connected clientsmaxClients
- maximum number of clientsserverName
- server nameport
- port on which the server listens
We'll name the class ServerStatusMessage
and let it implement
the IMessage
interface:
public class ServerStatusMessage implements IMessage { private static final long serialVersionUID = -1429760060957272567L; public static final String MESSAGE_TYPE = "server-status"; private final ServerStatusData statusData; public ServerStatusMessage(ServerStatusData statusData) { this.statusData = statusData; } @Override public String getType() {return MESSAGE_TYPE;} @Override public Object getData() {return statusData;} @Override public String toString() {return String.valueOf(getData());} public static final class ServerStatusData implements Serializable { private static final long serialVersionUID = -4288671744361722044L; public enum ServerStatus {EMPTY, HAVE_SPACE, FULL} public final UUID serverID; public final ServerStatus serverStatus; public final int clientCount; public final int maxClients; public final String serverName; public final int port; public ServerStatusData(UUID serverID, int clientCount, int maxClients, String serverName, int port) { this.serverID = serverID; this.serverStatus = serverStatus; this.clientCount = clientCount; this.maxClients = maxClients; this.serverName = serverName; this.port = port; final int delta = maxClients - clientCount; ServerStatus status = ServerStatus.EMPTY; if (delta == 0) { status = ServerStatus.FULL; } else if (delta > 0 && delta < maxClients) { status = ServerStatus.HAVE_SPACE; } this.serverStatus = status; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ServerStatusData that = (ServerStatusData) o; return clientCount == that.clientCount && maxClients == that.maxClients && port == that.port && Objects.equals(serverID, that.serverID) && serverStatus == that.serverStatus && Objects.equals(serverName, that.serverName); } @Override public int hashCode() { return Objects.hash(serverID, serverStatus, clientCount, maxClients, serverName, port); } @Override public String toString() { return String.format("%s: %d/%d - %s; port=%d", serverName, clientCount, maxClients, serverStatus, port); } } }
The IMessage
interface requires us to implement the
getType()
and getData()
methods. The
getType()
method returns the MESSAGE_TYPE
constant.
The getData()
method returns a ServerStatusData
object
that contains the server information. The ServerStatusData
class is
a container class so it only encapsulates information under one class. The class
must contain data types that are serializable, otherwise an exception
would be thrown when sending the object over the network.
Creating the Server Information Message
Now let's get back to the getServerStatusMessage()
method and
add its body:
@Override public IMessage getServerStatusMessage() { final int connectedClients = connectionManager.getConnectedClientCount(); final int maxClients = connectionManager.getMaxClients(); return new ServerStatusMessage(new ServerStatusData( ID, connectedClients, maxClients, serverName, port)); }
In the method, we obtain information about the number of currently connected
clients and the maximum number of clients from ConnectionManager
.
We must create the static ID
constant and place it in the
ServerThread
class in the definition of the other instance
constants:
private static final UUID ID = UUID.randomUUID();
We must also create the name
instance constant. It'll be
initialized in the class constructor from a parameter:
private final String serverName; @Inject ServerThread(IConnectionManager connectionManager, IMulticastSenderFactory multicastSenderFactory, String serverName, int port) { super("ServerThread"); this.connectionManager = connectionManager; this.multicastSender = multicastSenderFactory.getMulticastSender(this); this.serverName = serverName; this.port = port; }
The port
instance constant was already there.
Again, we changed the constructor of the ServerThread
class, so
we have to modify its factory too:
@Override public IServerThread getServerThread(IParameterProvider parameters) { final String serverName = parameters.getString(CmdParser.SERVER_NAME, DEFAULT_SERVER_NAME); final int port = parameters.getInteger(CmdParser.PORT, DEFAULT_SERVER_PORT); final int maxClients = parameters.getInteger(CmdParser.CLIENTS, DEFAULT_MAX_CLIENTS); final int waitingQueueSize = parameters.getInteger(CmdParser.MAX_WAITING_QUEUE, DEFAULT_WAITING_QUEUE_SIZE); return new ServerThread(connectionManagerFactory.getConnectionManager(maxClients, waitingQueueSize), multicastSenderFactory, serverName, port); }
In the factory, we pass the serverName
variable to the
getServerThread()
method, initializing it from the parameters. We
added this variable to the right place when creating a new instance.
This would be all for implementing the propagation of the server in the local network.
In the next lesson, Java Server - Local Area Network Propagation (Part 3), we'll finish propagating our Java server and create a client-side class to receive datagram packets that the server sends to be visible.