package org.menacheri.jetclient.app.impl;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.socket.DatagramChannel;
import org.menacheri.jetclient.NettyTCPClient;
import org.menacheri.jetclient.NettyUDPClient;
import org.menacheri.jetclient.app.Player;
import org.menacheri.jetclient.app.PlayerSession;
import org.menacheri.jetclient.app.Session;
import org.menacheri.jetclient.app.impl.DefaultSession.SessionBuilder;
import org.menacheri.jetclient.communication.MessageBuffer;
import org.menacheri.jetclient.communication.MessageSender.Fast;
import org.menacheri.jetclient.communication.MessageSender.Reliable;
import org.menacheri.jetclient.communication.NettyTCPMessageSender;
import org.menacheri.jetclient.communication.NettyUDPMessageSender;
import org.menacheri.jetclient.event.Event;
import org.menacheri.jetclient.event.EventHandler;
import org.menacheri.jetclient.event.Events;
import org.menacheri.jetclient.event.SessionEventHandler;
import org.menacheri.jetclient.handlers.netty.TCPPipelineFactory;
import org.menacheri.jetclient.handlers.netty.UDPPipelineFactory;
import org.menacheri.jetclient.util.LoginHelper;
/**
* Class used to create a session in jetclient. SessionFactory will also create
* the actual connection to the jetserver by initializing {@link NettyTCPClient}
* and {@link NettyUDPClient} and using their connect methods.
*
* @author Abraham Menacherry
*
*/
public class SessionFactory
{
/**
* This class holds a number of variables like username, password etc which
* are necessary for creating connections to remote jetserver.
*/
private final LoginHelper loginHelper;
private final NettyTCPClient tcpClient;
private final NettyUDPClient udpClient;
private static final AtomicInteger sessionId = new AtomicInteger(0);
/**
* This constructor will take a {@link LoginHelper} and initialize the
* {@link NettyTCPClient} and {@link NettyUDPClient}s using the connection
* parameters provided in this login helper class.
*
* @param theLoginHelper
* @throws UnknownHostException
* @throws Exception
*/
public SessionFactory(final LoginHelper theLoginHelper)
throws UnknownHostException, Exception
{
this.loginHelper = theLoginHelper;
InetSocketAddress tcpAddress = loginHelper.getTcpServerAddress();
this.tcpClient = new NettyTCPClient(tcpAddress);
InetSocketAddress udpAddress = loginHelper.getUdpServerAddress();
if (null == udpAddress)
{
udpClient = null;
}
else
{
udpClient = new NettyUDPClient(udpAddress,
UDPPipelineFactory.getInstance());
}
}
/**
* Creates a {@link Session} and connects it to the remote jetserver.
*
* @return The session instance created and connected to remote jetserver.
* @throws InterruptedException
* @throws Exception
*/
public Session createAndConnectSession() throws InterruptedException,
Exception
{
return createAndConnectSession((EventHandler[]) null);
}
/**
* Creates a {@link Session}, adds the event handlers to the session and
* then connects it to the remote jetserver. This way events will not be
* lost on connect.
*
* @param eventHandlers
* The handlers to be added to listen to session.
* @return The session instance created and connected to remote jetserver.
* @throws InterruptedException
* @throws Exception
*/
public Session createAndConnectSession(EventHandler... eventHandlers)
throws InterruptedException, Exception
{
Session session = createSession();
connectSession(session, eventHandlers);
return session;
}
/**
* @return Returns the session instance created using a
* {@link SessionBuilder}.
*/
public Session createSession()
{
SessionBuilder sessionBuilder = new SessionBuilder().id(sessionId
.incrementAndGet());
return sessionBuilder.build();
}
/**
* Connects the session to remote jetserver. Depending on the connection
* parameters provided to LoginHelper, it can connect both TCP and UDP
* transports.
*
* @param session
* The session to be connected to remote jetserver.
* @throws InterruptedException
* @throws Exception
*/
public void connectSession(final Session session)
throws InterruptedException, Exception
{
connectSession(session, (EventHandler[]) null);
}
/**
* Connects the session to remote jetserver. Depending on the connection
* parameters provided to LoginHelper, it can connect both TCP and UDP
* transports.
*
* @param session
* The session to be connected to remote jetserver.
* @param eventHandlers
* The handlers to be added to session.
* @throws InterruptedException
* @throws Exception
*/
public void connectSession(final Session session,
EventHandler... eventHandlers) throws InterruptedException,
Exception
{
InetSocketAddress udpAddress = null;
if (null != udpClient)
{
udpAddress = doUdpConnection(session);
}
if (null != eventHandlers)
{
for (EventHandler eventHandler : eventHandlers)
{
session.addHandler(eventHandler);
if (eventHandler instanceof SessionEventHandler)
{
((SessionEventHandler) eventHandler).setSession(session);
}
}
}
MessageBuffer<ChannelBuffer> buffer = loginHelper
.getLoginBuffer(udpAddress);
Event loginEvent = Events.event(buffer, Events.LOG_IN);
doTcpConnection(session, loginEvent);
}
/**
* Method used to reconnect existing session which probably got disconnected
* due to some exception. It will first close existing tcp and udp
* connections and then try re-connecting using the reconnect key from
* server.
*
* @param session
* The session which needs to be re-connected.
* @param reconnectKey
* This is provided by the server on
* {@link Events#GAME_ROOM_JOIN_SUCCESS} event and stored in the
* session.
* @throws InterruptedException
* @throws Exception
*/
public void reconnectSession(final Session session, String reconnectKey)
throws InterruptedException, Exception
{
session.getTcpMessageSender().close();
if (null != session.getUdpMessageSender())
session.getUdpMessageSender().close();
InetSocketAddress udpAddress = null;
if (null != udpClient)
{
udpAddress = doUdpConnection(session);
}
Event reconnectEvent = Events.event(
loginHelper.getReconnectBuffer(reconnectKey, udpAddress),
Events.RECONNECT);
doTcpConnection(session, reconnectEvent);
}
protected void doTcpConnection(final Session session, Event event)
throws Exception, InterruptedException
{
// Connect session using tcp to remote jetserver
TCPPipelineFactory tcpFactory = new TCPPipelineFactory(session);
// This will in turn invoke the startEventHandler when server sends
// Events.START event.
Channel channel = tcpClient.connect(tcpFactory, event);
if (null != channel)
{
Reliable tcpMessageSender = new NettyTCPMessageSender(channel);
session.setTcpMessageSender(tcpMessageSender);
}
else
{
throw new Exception("Could not create TCP connection to server");
}
}
protected InetSocketAddress doUdpConnection(final Session session)
throws UnknownHostException
{
InetSocketAddress localAddress;
final DatagramChannel datagramChannel = udpClient
.createDatagramChannel();
localAddress = datagramChannel.getLocalAddress();
// Add a start event handler to the session which will send the udp
// connect on server START signal.
final EventHandler startEventHandler = new EventHandler()
{
@Override
public void onEvent(Event event)
{
try
{
udpClient.connect(session, datagramChannel);
// remove after use
session.removeHandler(this);
}
catch (UnknownHostException e)
{
throw new RuntimeException(e);
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
@Override
public int getEventType()
{
return Events.START;
}
};
session.addHandler(startEventHandler);
Fast udpMessageSender = new NettyUDPMessageSender(
udpClient.getServerAddress(), datagramChannel);
session.setUdpMessageSender(udpMessageSender);
return localAddress;
}
public PlayerSession createPlayerSession(Player player)
{
SessionBuilder sessionBuilder = new SessionBuilder();
DefaultPlayerSession playerSession = new DefaultPlayerSession(
sessionBuilder, player);
return playerSession;
}
public LoginHelper getLoginHelper()
{
return loginHelper;
}
public NettyTCPClient getTcpClient()
{
return tcpClient;
}
public NettyUDPClient getUdpClient()
{
return udpClient;
}
}