package ecologylab.oodss.distributed.server.clientsessionmanager; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import org.java_websocket.WebSocket; import ecologylab.collections.Scope; import ecologylab.generic.Debug; import ecologylab.oodss.distributed.common.SessionObjects; import ecologylab.oodss.distributed.server.WebSocketOodssServer; import ecologylab.oodss.distributed.server.WebSocketServerProcessor; 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; import ecologylab.serialization.SIMPLTranslationException; import ecologylab.serialization.SimplTypesScope; import ecologylab.serialization.formatenums.StringFormat; public class WebSocketClientSessionManager extends Debug { /** * 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 Scope localScope; protected SimplTypesScope translationScope; protected long lastActivity = System.currentTimeMillis(); /** The selection key for this context manager. */ protected SelectionKey socketKey; protected WebSocket conn; /** * 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 WebSocketServerProcessor 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; private boolean isShutdown = false; public static final String SESSION_ID = "SESSION_ID"; public static final String CLIENT_MANAGER = "CLIENT_MANAGER"; public WebSocketClientSessionManager(String sessionId, SelectionKey key, WebSocket conn, SimplTypesScope translationScope, Scope baseScope, WebSocketServerProcessor frontend) { super(); this.frontend = frontend; this.socketKey = key; this.sessionId = sessionId; this.conn = conn; this.localScope = generateContextScope(baseScope); this.localScope.put(SESSION_ID, sessionId); this.localScope.put(CLIENT_MANAGER, this); this.translationScope = translationScope; this.handle = new WebSocketSessionHandle(this); this.localScope.put(SessionObjects.SESSION_HANDLE, this.handle); } /** * 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 */ private Scope generateContextScope(Scope baseScope) { return new Scope(baseScope); } /** * process serialized request message. * * @param message * @param uid * @return */ public ResponseMessage processString(String message, long uid) { ResponseMessage responseMessage = null; Object o; try { o = translationScope.deserialize(message, StringFormat.XML); if (o instanceof RequestMessage) { responseMessage = processRequest((RequestMessage) o); } } catch (SIMPLTranslationException e) { // TODO Auto-generated catch block e.printStackTrace(); } return responseMessage; } /** * 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) { 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 requestMessage * @return */ protected ResponseMessage processRequest(RequestMessage requestMessage) { this.lastActivity = System.currentTimeMillis(); ResponseMessage response = null; if (requestMessage == null) { debug("No request."); } else { if (!isInitialized()) { // special processing for InitConnectionRequest if (requestMessage instanceof InitConnectionRequest) { String incomingSessionId = ((InitConnectionRequest) requestMessage).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(requestMessage); } if (response == null) { debug("context manager did not produce a response message."); } } return response; } public void sendUpdateToClient(UpdateMessage update) { if (!isShutDown()) { WebSocketOodssServer server = (WebSocketOodssServer) localScope.get(SessionObjects.OODSS_WEBSOCKET_SERVER); server.sendUpdateMessage(update, conn); } } /** * 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; } /** * Indicates whether or not this context manager has been initialized. Normally, this means that * it has shared a session id with the client. * * @return */ private boolean isInitialized() { return initialized; } public SessionHandle getHandle() { // TODO Auto-generated method stub return handle; } /** * @return the socket */ public SelectionKey getSocketKey() { return socketKey; } public void setSocket(Object socketKey) { // TODO Auto-generated method stub } /** * @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 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() { isShutdown = true; } public boolean isShutDown() { return isShutdown; } public InetSocketAddress getAddress() { // TODO Auto-generated method stub return conn.getRemoteSocketAddress(); } }