package ecologylab.oodss.distributed.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.bind.DatatypeConverter;
import ecologylab.collections.Scope;
import ecologylab.generic.CharBufferPool;
import ecologylab.generic.HashMapArrayList;
import ecologylab.generic.StringBuilderPool;
import ecologylab.io.ByteBufferPool;
import ecologylab.oodss.distributed.common.SessionObjects;
import ecologylab.oodss.distributed.impl.NIODatagramCore;
import ecologylab.oodss.distributed.server.clientsessionmanager.BaseSessionManager;
import ecologylab.oodss.distributed.server.clientsessionmanager.DatagramClientSessionManager;
import ecologylab.oodss.distributed.server.clientsessionmanager.SessionHandle;
import ecologylab.oodss.messages.MultiRequestMessage;
import ecologylab.oodss.messages.RequestMessage;
import ecologylab.oodss.messages.ResponseMessage;
import ecologylab.oodss.messages.ServiceMessage;
import ecologylab.serialization.SimplTypesScope;
/**
* OODSS Datagram server.
*
* @author bilhamil
*
* @param <S>
* Application scope type parameter.
*/
public class NIODatagramServer<S extends Scope> extends NIODatagramCore<S> implements
NIOServerProcessor
{
private long sidIndex = 1;
private ConcurrentHashMap<String, DatagramClientSessionManager> sidToSessionManager = new ConcurrentHashMap<String, DatagramClientSessionManager>();
private ConcurrentHashMap<SocketAddress, String> socketAddressesToSids = new ConcurrentHashMap<SocketAddress, String>();
private ConcurrentHashMap<String, SocketAddress> sidsToSocketAddresses = new ConcurrentHashMap<String, SocketAddress>();
/** Map in which keys are sessionTokens, and values are associated SessionHandles */
private HashMapArrayList<Object, SessionHandle> clientSessionHandleMap = new HashMapArrayList<Object, SessionHandle>();
// protected S applicationObjectScope;
private MessageDigest digester;
private int dispensedTokens = 1;
private int portNumber;
protected DatagramChannel chan;
/**
* Initializes and starts the datagram Server. Open's up the server on all interfaces.
*
* @param portNumber
* service's port number
* @param translationScope
* @param objectRegistry
* application scope
* @param useCompression
* whether or not to use compression
*/
public NIODatagramServer( int portNumber,
SimplTypesScope translationScope,
S objectRegistry,
boolean useCompression)
{
super(translationScope, objectRegistry, useCompression);
// this.applicationObjectScope = objectRegistry;
this.objectRegistry.put(SessionObjects.SESSIONS_MAP, clientSessionHandleMap);
// applicationObjectScope.put(SessionObjects.SESSIONS_MAP, clientSessionHandleMap);
this.portNumber = portNumber;
try
{
chan = DatagramChannel.open();
chan.socket().bind(new InetSocketAddress(portNumber));
chan.configureBlocking(false);
chan.register(selector, SelectionKey.OP_READ);
}
catch (ClosedChannelException e)
{
debug("Channel isn't open but it should be!: " + e.getMessage());
e.printStackTrace();
}
catch (SocketException e)
{
debug("Failed to open socket!: " + e.getMessage());
e.printStackTrace();
}
catch (IOException e)
{
debug("Failed to open socket!: " + e.getMessage());
e.printStackTrace();
}
try
{
digester = MessageDigest.getInstance("SHA-256");
}
catch (NoSuchAlgorithmException e)
{
weird("This can only happen if the local implementation does not include the given hash algorithm.");
e.printStackTrace();
}
this.start();
}
/**
* Initializes and starts the datagram server. Open's up the server on all interfaces. Doesn't use
* compression by default.
*
* @param portNumber
* service's port number
* @param translationScope
* @param objectRegistry
* application scope
*/
public NIODatagramServer(int portNumber, SimplTypesScope translationScope, S objectRegistry)
{
this(portNumber, translationScope, objectRegistry, false);
}
@Override
protected final void handleMessage(long uid, ServiceMessage<S> message, SelectionKey key,
InetSocketAddress address)
{
DatagramClientSessionManager clientSessionManager = null;
String sid;
// if (message instanceof InitConnectionRequest)
// {
// InitConnectionRequest initReq = (InitConnectionRequest) message;
synchronized (socketAddressesToSids)
{
// if (initReq.getSessionId() == null)
// {
sid = socketAddressesToSids.get(address);
if (sid == null)
{ // no session manager created yet; create one
sid = this.generateSessionToken(address);
debug("New session: " + sid + " at: " + address);
socketAddressesToSids.put(address, sid);
sidsToSocketAddresses.put(sid, address);
DatagramClientSessionManager mngr = this.generateContextManager(sid,
key,
this.objectRegistry,
// this.applicationObjectScope,
address);
SessionHandle hndl = new SessionHandle(mngr);
sidToSessionManager.put(sid, mngr);
clientSessionHandleMap.put(sid, hndl);
mngr.getScope().put(SessionObjects.SESSION_HANDLE, new SessionHandle(mngr));
}
clientSessionManager = sidToSessionManager.get(sid);
/*
* Have to keep track of the most recent key if messages are coming in on multiple ports on
* the server.
*/
clientSessionManager.updateKey(key);
ResponseMessage response = clientSessionManager.processRequest( (RequestMessage) message,
address.getAddress());
if (response != null)
this.sendMessage(response, key, uid, address);
}
}
//
//
//
//
//
// // client expecting a new sid back
// // try to restore previous sid
// if ((sid = socketAddressesToSids.get(address)) != null)
// {
// debug("Restoring session id: " + sid + " at :" + address);
// this.sendMessage(new InitConnectionResponse(socketAddressesToSids.get(address)), key,
// uid, address);
// }
// else
// {
// sid = this.generateSessionToken((InetSocketAddress) address);
//
// debug("New session: " + sid + " at: " + address);
//
// socketAddressesToSids.put(address, sid);
// sidsToSocketAddresses.put(sid, address);
//
// clientSessionManager = new Scope(this.applicationObjectScope);
// clientSessionManager.put(BaseSessionManager.SESSION_ID, sid);
// onSessionCreation(sid, clientSessionManager);
//
// sidToSessionManager.put(sid, clientSessionManager);
// this.sendMessage(new InitConnectionResponse(sid), key, uid, address);
// }
// }
// else
// {
// if (sidToSessionManager.containsKey((initReq.getSessionId())))
// {
// sid = initReq.getSessionId();
//
// debug("Session: " + sid + " moved to " + address);
//
// socketAddressesToSids.put(address, sid);
// sidsToSocketAddresses.put(sid, address);
// this.sendMessage(new InitConnectionResponse(sid), key, uid, address);
// }
// else
// {
// if ((sid = reassignedSessions.get(initReq.getSessionId())) == null)
// {
// sid = this.generateSessionToken((InetSocketAddress) address);
// reassignedSessions.put(initReq.getSessionId(), sid);
//
// socketAddressesToSids.put(address, sid);
// sidsToSocketAddresses.put(sid, address);
//
// clientSessionManager = new Scope(this.applicationObjectScope);
// clientSessionManager.put(BaseSessionManager.SESSION_ID, sid);
// sidToSessionManager.put(sid, new Scope(this.applicationObjectScope));
// }
//
// debug("Unknown session: "
// + initReq.getSessionId()
// + " at "
// + address
// + " reassinged to "
// + sid);
//
// this.sendMessage(new InitConnectionResponse(sid), key, uid, address);
// }
// }
// }
// }
// else
// {
// synchronized (socketAddressesToSids)
// {
// if ((sid = socketAddressesToSids.get(address)) != null)
// {
// clientSessionManager = sidToSessionManager.get(sid);
// }
// else
// {
// this.sendMessage(new InitConnectionRequest(), key, uid, address);
// }
// }
//
// if (clientSessionManager != null)
// {
// handleAssociatedMessage(message, clientSessionManager, key, uid, address);
// }
//
// }
//
// }
protected void handleAssociatedMessage(ServiceMessage<S> message, Scope clientRegistry,
SelectionKey key, Long uid, InetSocketAddress address)
{
if (message instanceof RequestMessage)
{
ResponseMessage<S> response = ((RequestMessage) message).performService(clientRegistry);
if (response != null)
{
this.sendMessage(response, key, uid, address);
}
}
if (message instanceof MultiRequestMessage)
{
Collection<ResponseMessage> responses = ((MultiRequestMessage) message).performService(clientRegistry);
for (ResponseMessage response : responses)
{
this.sendMessage(response, key, uid, address);
}
}
}
@Override
protected void waitForReconnect()
{
}
synchronized protected long getNextSid()
{
return sidIndex++;
}
protected String generateSessionToken(InetSocketAddress incomingSocket)
{
// clear digester
digester.reset();
// we make a string consisting of the following:
// time of initial connection (when this method is called),
// client ip, client actual port
digester.update(String.valueOf(System.currentTimeMillis()).getBytes());
// digester.update(String.valueOf(System.nanoTime()).getBytes());
// digester.update(this.incomingConnectionSockets[0].getInetAddress()
// .toString().getBytes());
digester.update(incomingSocket.getAddress().toString().getBytes());
digester.update(String.valueOf(incomingSocket.getPort()).getBytes());
digester.update(String.valueOf(this.dispensedTokens).getBytes());
dispensedTokens++;
// convert to normal characters and return as a String
return DatatypeConverter.printBase64Binary(digester.digest());
}
public int getPortNumber()
{
return portNumber;
}
/**
* @return the global scope for this server
*/
public S getApplicationObjectScope()
{
return this.objectRegistry;
// return applicationObjectScope;
}
/**
* Hook method to allow changing the ContextManager to enable specific extra functionality.
*
* @param token
* @param sc
* @param translationScopeIn
* @param registryIn
* @return
*/
protected DatagramClientSessionManager generateContextManager(String sessionId, SelectionKey sk,
S registryIn, InetSocketAddress address)
{
return new DatagramClientSessionManager(sessionId, this, sk, registryIn, address);
}
/**
* @see ecologylab.oodss.distributed.server.NIOServerProcessor#invalidate(java.lang.String,
* boolean)
*/
@Override
public boolean invalidate(String sessionId, boolean forcePermanent)
{
DatagramClientSessionManager cm = this.sidToSessionManager.get(sessionId);
// figure out if the disconnect is permanent; will be permanent if forcing
// (usually bad client), if there is no context manager (client never sent
// data), or if the client manager says it is invalidating (client
// disconnected properly)
boolean permanent = (forcePermanent ? true : (cm == null ? true : cm.isInvalidating()));
// get the context manager...
if (permanent)
{
synchronized (sidToSessionManager)
{ // ...if this session will not be restored, remove the context
// manager
sidToSessionManager.remove(sessionId);
clientSessionHandleMap.remove(sessionId);
SocketAddress address = this.sidsToSocketAddresses.remove(sessionId);
this.socketAddressesToSids.remove(address);
}
}
if (cm != null)
{
cm.shutdown();
}
return permanent;
}
/**
* @see ecologylab.oodss.distributed.server.NIOServerProcessor#restoreContextManagerFromSessionId(java.lang.String,
* ecologylab.oodss.distributed.server.clientsessionmanager.BaseSessionManager)
*/
@Override
public boolean restoreContextManagerFromSessionId(String oldId,
BaseSessionManager newContextManager)
{
SocketAddress oldAddress = this.sidsToSocketAddresses.get(oldId);
if (oldAddress != null)
{ // the old session manager is still there
// connect it to the new address through the maps
SocketAddress newAddress = ((DatagramClientSessionManager) newContextManager).getAddress();
this.sidsToSocketAddresses.put(oldId, newAddress);
this.socketAddressesToSids.remove(oldAddress);
this.socketAddressesToSids.put(newAddress, oldId);
return true;
}
return false;
}
/**
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
}
@Override
public ByteBufferPool getSharedByteBufferPool()
{
// TODO Auto-generated method stub
return null;
}
@Override
public CharBufferPool getSharedCharBufferPool()
{
// TODO Auto-generated method stub
return null;
}
@Override
public StringBuilderPool getSharedStringBuilderPool()
{
// TODO Auto-generated method stub
return null;
}
/**
* @see ecologylab.oodss.distributed.impl.NIODatagramCore#stop()
*/
@Override
public void stop()
{
super.stop();
try
{
this.chan.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
@Override
public void increaseSharedBufferPoolSize(int newCapacity) {
// TODO Auto-generated method stub
}
}