/** * */ package ecologylab.oodss.distributed.server.clientsessionmanager; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import ecologylab.collections.Scope; import ecologylab.generic.Debug; import ecologylab.oodss.distributed.server.NIOServerProcessor; import ecologylab.oodss.messages.BadSemanticContentResponse; import ecologylab.oodss.messages.InitConnectionRequest; import ecologylab.oodss.messages.InitConnectionResponse; import ecologylab.oodss.messages.RequestMessage; import ecologylab.oodss.messages.ResponseMessage; import ecologylab.oodss.messages.UpdateMessage; /** * @author Zachary O. Toups (zach@ecologylab.net) * */ public abstract class BaseSessionManager<S extends Scope, PARENT extends Scope> extends Debug { /** * Indicates whether or not one or more messages are queued for execution by this ContextManager. */ protected boolean messageWaiting = false; /** * Session handle available to use by clients */ protected SessionHandle handle; /** * sessionId uniquely identifies this ContextManager. It is used to restore the state of a lost * connection. */ protected String sessionId = null; protected S localScope; protected long lastActivity = System.currentTimeMillis(); /** The selection key for this context manager. */ protected SelectionKey socketKey; /** * The frontend for the server that is running the ContextManager. This is needed in case the * client attempts to restore a session, in which case the frontend must be queried for the old * ContextManager. */ protected NIOServerProcessor frontend = null; /** * Indicates whether the first request message has been received. The first request may be an * InitConnection, which has special properties. */ protected boolean initialized = false; /** * Used for disconnecting. A disconnect message will call the setInvalidating method, which will * set this value to true. The processing method will set itself as pending invalidation after it * has produces the bytes for the response to the disconnect message. */ private boolean invalidating = false; public static final String SESSION_ID = "SESSION_ID"; public static final String CLIENT_MANAGER = "CLIENT_MANAGER"; /** * */ public BaseSessionManager(String sessionId, NIOServerProcessor frontend, SelectionKey socket, PARENT baseScope) { super(); this.frontend = frontend; this.socketKey = socket; this.sessionId = sessionId; this.localScope = generateContextScope(baseScope); this.localScope.put(SESSION_ID, sessionId); this.localScope.put(CLIENT_MANAGER, this); } /** * Provides the context scope for the client attached to this session manager. The base * implementation instantiates a new Scope<?> with baseScope as the argument. Subclasses may * provide specific subclasses of Scope as the return value. They must still incorporate baseScope * as the lexically chained application object scope. * * @param baseScope * @return */ protected S generateContextScope(PARENT baseScope) { return (S) new Scope(baseScope); } /** * Appends the sender's IP address to the incoming message and calls performService on the given * RequestMessage using the local ObjectRegistry. * * performService(RequestMessage) may be overridden by subclasses to provide more specialized * functionality. Generally, overrides should then call super.performService(RequestMessage) so * that the IP address is appended to the message. * * @param requestMessage * @return */ protected ResponseMessage performService(RequestMessage requestMessage, InetAddress address) { requestMessage.setSender(address); try { return requestMessage.performService(localScope); } catch (Exception e) { e.printStackTrace(); return new BadSemanticContentResponse("The request, " + requestMessage.toString() + " caused an exception on the server."); } } /** * Calls RequestMessage.performService(Scope) and returns the result. * * @param request * - the request message to process. */ protected ResponseMessage processRequest(RequestMessage request, InetAddress address) { this.lastActivity = System.currentTimeMillis(); ResponseMessage response = null; if (request == null) { debug("No request."); } else { if (!isInitialized()) { // special processing for InitConnectionRequest if (request instanceof InitConnectionRequest) { String incomingSessionId = ((InitConnectionRequest) request).getSessionId(); if (incomingSessionId == null) { // client is not expecting an old ContextManager response = new InitConnectionResponse(this.sessionId); } else { // client is expecting an old ContextManager if (frontend.restoreContextManagerFromSessionId(incomingSessionId, this)) { response = new InitConnectionResponse(incomingSessionId); } else { response = new InitConnectionResponse(this.sessionId); } } initialized = true; } } else { // perform the service being requested response = performService(request, address); } if (response == null) { debug("context manager did not produce a response message."); } } return response; } /** * Indicates the last System timestamp was when the ContextManager had any activity. * * @return the last System timestamp indicating when the ContextManager had any activity. */ public final long getLastActivity() { return lastActivity; } /** * @return the socket */ public SelectionKey getSocketKey() { return socketKey; } /** * Indicates whether there are any messages queued up to be processed. * * isMessageWaiting() should be overridden if getNextRequest() is overridden so that it properly * reflects the way that getNextRequest() works; it may also be important to override * enqueueRequest(). * * @return true if getNextRequest() can return a value, false if it cannot. */ public boolean isMessageWaiting() { return messageWaiting; } /** * Indicates whether or not this context manager has been initialized. Normally, this means that * it has shared a session id with the client. * * @return */ public boolean isInitialized() { return initialized; } /** * @param invalidating * the invalidating to set */ public void setInvalidating(boolean invalidating) { this.invalidating = invalidating; } /** * Indicates whether or not the client manager is expecting a disconnect. If this method returns * true, then this client manager should be disposed of when the client disconnects; otherwise, it * should be retained until the client comes back, or the client managers are cleaned up. * * @return true if the client manager is expecting the client to disconnect, false otherwise */ public boolean isInvalidating() { return invalidating; } public abstract void sendUpdateToClient(UpdateMessage<?> update); /** * @return the address */ public abstract InetSocketAddress getAddress(); public String getSessionId() { return this.sessionId; } public Scope getScope() { return this.localScope; } /** * Hook method for having shutdown behavior. * * This method is called whenever the server is closing down the connection to this client. */ public void shutdown() { } }