Package com.sun.sgs.impl.service.session

Source Code of com.sun.sgs.impl.service.session.ClientSessionHandler$LoginResultAction

/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* --
*/

package com.sun.sgs.impl.service.session;

import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.util.ManagedSerializable;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.impl.service.session.ClientSessionImpl.SendEvent;
import com.sun.sgs.impl.service.session.ClientSessionServiceImpl.Action;
import com.sun.sgs.impl.kernel.StandardProperties;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import static com.sun.sgs.impl.sharedutil.Objects.checkNull;
import com.sun.sgs.impl.util.AbstractCompletionFuture;
import com.sun.sgs.impl.util.AbstractKernelRunnable;
import static com.sun.sgs.impl.util.AbstractService.isRetryableException;
import com.sun.sgs.kernel.KernelRunnable;
import com.sun.sgs.kernel.TaskQueue;
import com.sun.sgs.protocol.LoginFailureException;
import com.sun.sgs.protocol.LoginRedirectException;
import com.sun.sgs.protocol.ProtocolDescriptor;
import com.sun.sgs.protocol.RelocateFailureException;
import com.sun.sgs.protocol.RequestCompletionHandler;
import com.sun.sgs.protocol.RequestFailureException;
import com.sun.sgs.protocol.SessionProtocol;
import com.sun.sgs.protocol.SessionProtocolHandler;
import com.sun.sgs.protocol.SessionRelocationProtocol;
import com.sun.sgs.service.ClientSessionStatusListener;
import com.sun.sgs.service.DataService;
import com.sun.sgs.service.Node;
import com.sun.sgs.service.SimpleCompletionHandler;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Handles sending/receiving messages to/from a client session and
* disconnecting a client session.
*/
class ClientSessionHandler implements SessionProtocolHandler {

    /** Connection state. */
    private static enum State {
        /** Session is connected */
        CONNECTED,
  /** Session login ack (success, failure, redirect) has been sent */
  LOGIN_HANDLED,
        /** Disconnection is in progress */
        DISCONNECTING,
        /** Session is disconnected */
        DISCONNECTED
    }

    /** The logger for this class. */
    private static final LoggerWrapper logger =
  new LoggerWrapper(Logger.getLogger(
      "com.sun.sgs.impl.service.session.handler"));

    /** Message indicating login was refused for a non-specific reason. */
    private static final String LOGIN_REFUSED_REASON = "Login refused";

    /** Message indicating relocation was refused for a non-specific reason. */
    static final String RELOCATE_REFUSED_REASON = "Relocate refused";

    /** The client session service that created this client session. */
    private final ClientSessionServiceImpl sessionService;

    /** The data service. */
    private final DataService dataService;

    /** The I/O channel for sending messages to the client. */
    private final SessionProtocol protocol;
   
    /** The session ID as a BigInteger. */
    volatile BigInteger sessionRefId;

    /** The identity for this session. */
    final Identity identity;

    /** The login status. */
    private volatile boolean loggedIn;

    /** The lock for accessing the following fields: {@code state},
     * {@code disconnectHandled}, {@code relocatePrepareCompletionHandler},
     * and {@code shutdown}.
     */
    private final Object lock = new Object();

    /** The connection state. */
    private State state = State.CONNECTED;

    /** Indicates whether session disconnection has been handled. */
    private boolean disconnectHandled = false;

    /** If non-null, contains the completion handler for
     * preparing this session to relocate to a new node. */
    private MoveAction relocatePrepareCompletionHandler = null;

    /** Indicates whether this session is shut down. */
    private boolean shutdown = false;

    /** Completion future for setting up the client session. */
    private final SetupCompletionFuture setupCompletionFuture;

    /** The queue of tasks for notifying listeners of received messages. */
    private volatile TaskQueue taskQueue = null;

    /**
     * Constructs an handler for a client session that is logging in.
     *
     * @param  sessionService the ClientSessionService instance
     * @param  dataService the DataService instance
     * @param  sessionProtocol a session protocol
     * @param  identity an identity
     * @param  completionHandler a completion handler for the associated
     *    request
     */
    ClientSessionHandler(
  ClientSessionServiceImpl sessionService,
  DataService dataService,
  SessionProtocol sessionProtocol,
  Identity identity,
  RequestCompletionHandler<SessionProtocolHandler> completionHandler)
    {
  this(sessionService, dataService, sessionProtocol, identity,
       completionHandler, null);
    }

    /**
     * Constructs an handler for a client session.  If {@code sessionRefId}
     * is non-{@code null}, then the associated client session is relocating
     * from another node, otherwise it is considered a new client session
     * logging in.
     *
     * @param  sessionService the ClientSessionService instance
     * @param  dataService the DataService instance
     * @param  sessionProtocol a session protocol
     * @param  identity an identity
     * @param  completionHandler a completion handler for the associated
     *    request
     * @param  sessionRefId the client session ID, or {@code null}
     */
    ClientSessionHandler(
  ClientSessionServiceImpl sessionService,
  DataService dataService,
  SessionProtocol sessionProtocol,
  Identity identity,
  RequestCompletionHandler<SessionProtocolHandler> completionHandler,
  BigInteger sessionRefId)
    {
  checkNull("sessionService", sessionService);
  checkNull("dataService", dataService);
  checkNull("sessionProtocol", sessionProtocol);
  checkNull("identity", identity);
  checkNull("completionHandler", completionHandler);
  this.sessionService = sessionService;
        this.dataService = dataService;
  this.protocol = sessionProtocol;
  this.identity = identity;
  this.sessionRefId = sessionRefId;
  setupCompletionFuture =
      new SetupCompletionFuture(this, completionHandler);

  final boolean loggingIn = sessionRefId == null;
  scheduleNonTransactionalTask(
      new AbstractKernelRunnable("HandleLoginOrRelocateRequest") {
    public void run() {
        setupClientSession(loggingIn);
    } });

  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "creating new ClientSessionHandler on nodeId:{0}",
            sessionService.getLocalNodeId());
  }
    }
      
    /* -- Implement SessionProtocolHandler -- */

    /** {@inheritDoc} */
    public void sessionMessage(
  final ByteBuffer message,
  RequestCompletionHandler<Void> completionHandler)
    {
  RequestCompletionFuture future =
      new RequestCompletionFuture(completionHandler);
  if (!readyForRequests(future)) {
      return;
  }
  taskQueue.addTask(
      new AbstractKernelRunnable("NotifyListenerMessageReceived") {
    public void run() {
        ClientSessionImpl sessionImpl =
      ClientSessionImpl.getSession(dataService, sessionRefId);
        if (sessionImpl != null) {
      if (isConnected()) {
          sessionImpl.getClientSessionListener(dataService).
        receivedMessage(
            message.asReadOnlyBuffer());
      }
        } else {
      scheduleHandleDisconnect(false, true);
        }
    } }, identity);

  // Wait until processing is complete before notifying future
  enqueueCompletionNotification(future);
    }

    /** {@inheritDoc} */
    public void channelMessage(final BigInteger channelId,
             final ByteBuffer message,
             RequestCompletionHandler<Void> completionHandler)
    {
  RequestCompletionFuture future =
      new RequestCompletionFuture(completionHandler);
  if (!readyForRequests(future)) {
      return;
  }
  taskQueue.addTask(
      new AbstractKernelRunnable("HandleChannelMessage") {
    public void run() {
        ClientSessionImpl sessionImpl =
      ClientSessionImpl.getSession(dataService, sessionRefId);
        if (sessionImpl != null) {
      if (isConnected()) {
          sessionService.getChannelService().
        handleChannelMessage(
            channelId,
            sessionImpl.getWrappedClientSession(),
            message.asReadOnlyBuffer());
      }
        } else {
      scheduleHandleDisconnect(false, true);
        }
    } }, identity);

  // Wait until processing is complete before notifying future
  enqueueCompletionNotification(future);
    }

    /** {@inheritDoc} */
    public void logoutRequest(
  RequestCompletionHandler<Void> completionHandler)
    {
  RequestCompletionFuture future =
      new RequestCompletionFuture(completionHandler);
  if (!readyForRequests(future)) {
      return;
  }
  scheduleHandleDisconnect(isConnected(), false);

  // Enable protocol message channel to read immediately
  future.done();
    }

    /** {@inheritDoc} */
    public void disconnect(RequestCompletionHandler<Void> completionHandler) {
  RequestCompletionFuture future =
      new RequestCompletionFuture(completionHandler);
 
  // TBD: should this be allowed to disconnect no matter what?
  if (!readyForRequests(future)) {
      return;
  }
  scheduleHandleDisconnect(false, true);
 
  future.done();
    }

    /* -- Implement Object -- */

    /** {@inheritDoc} */
    public String toString() {
  return getClass().getName() + "[" + identity + "]@" + sessionRefId;
    }
   
    /* -- Instance methods -- */

    /**
     * Returns {@code true} if this handler is connected, otherwise
     * returns {@code false}.
     *
     * @return  {@code true} if this handler is connected
     */
    boolean isConnected() {

  State currentState = getCurrentState();
  return
      currentState == State.CONNECTED ||
      currentState == State.LOGIN_HANDLED;
    }

    /**
     * Returns {@code true} if this session's protocol handler supports
     * relocation (i.e., implements the {@link SessionRelocationProtocol}
     * interface; otherwise returns {@code false}.
     */
    boolean supportsRelocation() {
  return protocol instanceof SessionRelocationProtocol;
    }

    /**
     * Returns {@code true} if this client session has begun preparing to
     * relocate, or has relocated to another node.
     */
     boolean isRelocating() {
   synchronized (lock) {
       return relocatePrepareCompletionHandler != null;
   }
    }

    /**
     * Returns {@code true} if this client session is disconnecting from
     * the local node AND terminating the associated client session.  The
     * client session is considered to be disconnecting iff the following
     * conditions are true:
     *
     * 1) this handler has been marked for disconnection
     * 2) the session is not relocating, OR, the session is relocating
     * and its relocation has not completed.
     */
    private boolean isTerminating() {
  synchronized (lock) {
      return !isConnected() &&
    (relocatePrepareCompletionHandler == null ||
     !relocatePrepareCompletionHandler.isCompleted());
  }
    }

    /**
     * Indicates that all parties are done with relocation preparation, and
     * notifies the client that it should suspend messages (before
     * notifying the client to relocate to another node).
     */   
    void setRelocatePreparationComplete() {
  synchronized (lock) {
      if (relocatePrepareCompletionHandler != null) {
    relocatePrepareCompletionHandler.suspend();
      }
  }
    }

    /**
     * Returns {@code true} if the associated client session is ready for
     * requests (i.e., it has completed login and it is not relocating);
     * otherwise, sets the appropriate {@code RequestFailureException} on
     * the specified {@code future} and returns {@code false}. <p>
     *
     * This method is invoked before proceeding with processing a
     * request, and if this method returns {@code false}, the request
     * should be dropped.
     *
     * @param  future a future on which to set an exception if this
     *     session is not ready to process requests
     * @return  {@code true} if requests can be processed by the
     *    associated client session and {@code false} otherwise
     */
    private boolean readyForRequests(RequestCompletionFuture future) {
  if (!loggedIn) {
      logger.log(
    Level.FINE,
    "request received before login completed:{0}", this);
      future.setException(
    new RequestFailureException(
        "session is not logged in",
        RequestFailureException.FailureReason.LOGIN_PENDING));
      return false;
  } else if (relocatePrepareCompletionHandler != null &&
       relocatePrepareCompletionHandler.isCompleted()) {
      logger.log(
    Level.FINE,
    "request received while session is relocating:{0}", this);
      future.setException(
    new RequestFailureException(
        "session is relocating",
        RequestFailureException.FailureReason.RELOCATE_PENDING));
      return false;
  } else if (!isConnected()) {
      logger.log(
    Level.FINE,
    "request received while session is disconnecting:{0}", this);
      future.setException(
    new RequestFailureException(
        "session is disconnecting",
        RequestFailureException.FailureReason.DISCONNECT_PENDING));
      return false;
  } else {
      return true;
  }
    }
   
    /**
     * Returns the protocol for the associated client session, or {@code
     * null} if the session is relocating.
     *
     * @return  a protocol, or {@code null} if the session is relocating
     */
    SessionProtocol getSessionProtocol() {
  return isRelocating() ? null : protocol;
    }
    /**
     * Returns {@code true} if the login for this session has been handled,
     * otherwise returns {@code false}.
     *
     * @return  {@code true} if the login for this session has been handled
     */
    boolean loginHandled() {
  return getCurrentState() != State.CONNECTED;
    }

    /**
     * Notifies the "setup" future that the setup was successful so that it can
     * send the appropriate success indication (either successful login
     * or relocation) to the client, and sets local state indicating that
     * the request has been handled and the client is logged in.
     */
    private void setupSuccess() {
  synchronized (lock) {
      checkConnectedState();
      loggedIn = true;
      setupCompletionFuture.done();
      state = State.LOGIN_HANDLED;
  }
    }
 
    /**
     * Notifies the "setup" future that setup failed with the specified
     * {@code exception} so that it can send the appropriate failure
     * indication to the client, and sets local state indicating that
     * request has been handled.
     *
     * @param  exception the login failure exception
     */
    private void setupFailure(Exception exception) {
  checkNull("exception", exception);
  synchronized (lock) {
      checkConnectedState();
      setupCompletionFuture.setException(exception);
      state = State.LOGIN_HANDLED;
  }
    }

    /**
     * Handles disconnecting the associated client session (if not already
     * handled) by doing the following: <ol>
     *
     * <li> notifies the client session service to clean up the client
     *      session's handler and login information,
     *
     * <li> notifies the node mapping service to deativate the client's
     *      identity if the identity is no longer active on this node,
     *
     * <li> if {@code closeConnection} is {@code true}, closes this
     *      session's connection,
     *
     * <li> if the session is terminating (not relocating), schedules a
     *      transactional task to invoke, on this session's {@code
     *      ClientSessionListener}, the {@code disconnected} callback
     *      with {@code graceful} as its argument and then clean up the
     *      session's persistent data, and also schedules a task to notify
     *      the identity that its corresponding session has logged out.
     * </ol>
     *
     * <p>Note:if {@code graceful} is {@code true}, then {@code
     * closeConnection} must be {@code false} so that the client's {@code
     * SessionProtocol} can send a notification of logout success to the
     * client.  The client may not receive such a notification if the
     * connection is disconnected immediately.
     *
     * <p>In the cases of login redirection, session relocation, and
     * graceful logout, it is the responsibility of the client's {@code
     * SessionProtocol} to close the client's connection in a timely manner
     * after notifying the client.
     *
     * @param  graceful if {@code true}, indicates that disconnection is
     *    due to a (graceful) logout request
     * @param  closeConnection if {@code true}, close this session's
     *    connection immediately
     */
    void handleDisconnect(final boolean graceful, boolean closeConnection) {

  synchronized (lock) {
      if (logger.isLoggable(Level.FINEST)) {
    logger.log(Level.FINEST, "handleDisconnect handler:{0} " +
         "disconnectHandled:{1}", this, disconnectHandled);
      }
      if (disconnectHandled) {
    return;
      }
      disconnectHandled = true;
      if (state != State.DISCONNECTED) {
    state = State.DISCONNECTING;
      }
  }

  if (sessionRefId != null) {
      sessionService.removeHandler(sessionRefId, !isTerminating());
  }
 
  if (sessionService.removeUserLogin(identity, this)) {
      deactivateIdentity();
  }

  if (getCurrentState() != State.DISCONNECTED) {
      if (graceful) {
    assert !closeConnection;
      }

      if (closeConnection) {
    closeConnection();
      }
  }

  if (sessionRefId != null && isTerminating()) {
      scheduleTask(
        new AbstractKernelRunnable("NotifyListenerAndRemoveSession") {
          public void run() {
        ClientSessionImpl sessionImpl =
      ClientSessionImpl.getSession(dataService, sessionRefId);
        sessionImpl.notifyListenerAndRemoveSession(
      dataService, graceful, true);
    }
      });
      // TBD: Due to the scheduler's behavior, this notification
      // may happen out of order with respect to the
      // 'notifyLoggedIn' callback.  Also, this notification may
      // also happen even though 'notifyLoggedIn' was not invoked.
      // Are these behaviors okay?  -- ann (3/19/07)
      scheduleTask(new AbstractKernelRunnable("NotifyLoggedOut") {
        public void run() {
      identity.notifyLoggedOut();
        } });
  }
    }

    /**
     * Schedules a non-transactional task to handle disconnecting the
     * associated client session.
     *
     * @param  graceful if {@code true}, indicates that disconnection is
     *    due to a (graceful) logout request
     * @param  closeConnection if {@code true}, close this session's
     *    connection immediately
     */
    private void scheduleHandleDisconnect(
  final boolean graceful, final boolean closeConnection)
    {
        synchronized (lock) {
      if (disconnectHandled) {
    return;
      }
            if (state != State.DISCONNECTED) {
                state = State.DISCONNECTING;
      }
        }
  scheduleNonTransactionalTask(
    new AbstractKernelRunnable("HandleDisconnect") {
      public void run() {
    handleDisconnect(graceful, closeConnection);
      } });
    }

    /**
     * Closes the connection associated with this instance.
     */
    private void closeConnection() {
  if (protocol.isOpen()) {
      try {
    protocol.close();
      } catch (IOException e) {
    if (logger.isLoggable(Level.WARNING)) {
        logger.logThrow(
      Level.WARNING, e,
      "closing connection for handle:{0} throws",
      protocol);
    }
      }
  }
  synchronized (lock) {
      state = State.DISCONNECTED;
  }
    }

    /**
     * Flags this session as shut down, and closes the connection.
     */
    void shutdown() {
  synchronized (lock) {
      if (shutdown) {
    return;
      }
      shutdown = true;
      disconnectHandled = true;
      closeConnection();
  }
    }
   
    /* -- other private methods and classes -- */

    /**
     * Schedules a task to notify the completion handler.  Use this method
     * to delay notification until a task resulting from an earlier request
     * has been completed.
     *
     * @param  completionHandler a completion handler
     * @param  future a completion future
     */
    private void enqueueCompletionNotification(
  final RequestCompletionFuture future)
    {
  taskQueue.addTask(
      new AbstractKernelRunnable("ScheduleCompletionNotification") {
    public void run() {
        future.done();
    } }, identity);
    }

    /**
     * Invokes the {@code setStatus} method on the node mapping service
     * with {@code false} to mark the identity as inactive.  This method
     * is invoked when a login is redirected and also when this client
     * session is disconnected.
     */
    private void deactivateIdentity() {
  try {
      /*
       * Set identity's status for this class to 'false'.
       */
      sessionService.nodeMapService.setStatus(
    ClientSessionHandler.class, identity, false);
  } catch (Exception e) {
      logger.logThrow(
    Level.WARNING, e,
    "setting status for identity:{0} throws",
    identity.getName());
  }
    }
   
    /**
     * Returns the current state.
     */
    private State getCurrentState() {
  State currentState;
  synchronized (lock) {
      currentState = state;
  }
  return currentState;
    }

    /**
     * If {@code loggingIn} is {@code true} handles a login request to
     * establish a client session); otherwise handles a relocate request
     * to re-establish a client session.  In either case, this method
     * notifies the completion handler specified during construction when
     * the request is completed.
     */
    private void setupClientSession(final boolean loggingIn) {
  logger.log(
      Level.FINEST,
      "setting up client session for identity:{0} loggingIn:{1}",
      identity, loggingIn);
 
        /*
         * Get node assignment.
         */
        long assignedNodeId = -1L;
        try {
            assignedNodeId = sessionService.nodeMapService.assignNode(
                                    ClientSessionHandler.class, identity);
  } catch (Exception e) {
      logger.logThrow(
          Level.WARNING, e,
    "getting node assignment for identity:{0} throws", identity);
  }
     
  if (assignedNodeId < 0) {
      logger.log(Level.WARNING,
           "getting node assignment for identity:{0} failed",
                       identity);
      notifySetupFailureAndDisconnect(
    loggingIn ?
    new LoginFailureException(
        LOGIN_REFUSED_REASON,
        LoginFailureException.FailureReason.SERVER_UNAVAILABLE) :
    new RelocateFailureException(
        RELOCATE_REFUSED_REASON,
        RelocateFailureException.FailureReason.SERVER_UNAVAILABLE));
      return;
        }

  if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "identity:{0} assigned to node:{1}",
           identity, assignedNodeId);
  }

  if (assignedNodeId == sessionService.getLocalNodeId()) {
      /*
       * Handle this login (or relocation) request locally.  First,
       * validate that the user is allowed to log in (or relocate).
       */
      if (!sessionService.validateUserLogin(
        identity, ClientSessionHandler.this, loggingIn))
      {
    // This client session is not allowed to proceed.
    if (logger.isLoggable(Level.FINE)) {
        logger.log(
      Level.FINE,
      "{0} rejected to node:{1} identity:{2}",
      (loggingIn ? "User login" : "Session relocation"),
      sessionService.getLocalNodeId(), identity);
    }
    notifySetupFailureAndDisconnect(
        loggingIn ?
        new LoginFailureException(
      LOGIN_REFUSED_REASON,
      LoginFailureException.FailureReason.DUPLICATE_LOGIN) :
        new RelocateFailureException(
       RELOCATE_REFUSED_REASON,
      RelocateFailureException.
          FailureReason.DUPLICATE_LOGIN));
    return;
      }
      taskQueue = sessionService.createTaskQueue();
      /*
       * If logging in, store the client session in the data store.
       */
      if (loggingIn) {
    CreateClientSessionTask createTask =
        new CreateClientSessionTask();
    try {
        sessionService.runTransactionalTask(createTask, identity);
    } catch (Exception e) {
        logger.logThrow(
            Level.WARNING, e,
      "Storing ClientSession for identity:{0} throws",
      identity);
        notifySetupFailureAndDisconnect(
      new LoginFailureException(LOGIN_REFUSED_REASON, e));
        return;
    }
    sessionRefId = createTask.getId();
      }

      /*
       * Inform the session service that this handler is available.  If
       * logging in, schedule a task to perform client login (which calls
       * the AppListener.loggedIn method), otherwise set the "relocating"
       * flag in the client session's state to false to indicate that
       * relocation is complete.
       */
      sessionService.addHandler(
    sessionRefId, ClientSessionHandler.this,
    loggingIn ? null : identity);
      if (loggingIn) {
    scheduleTask(new LoginTask());
      } else {
    try {
        sessionService.runTransactionalTask(
       new AbstractKernelRunnable("SetSessionRelocated") {
          public void run() {
        ClientSessionImpl sessionImpl =
            ClientSessionImpl.getSession(
                 dataService, sessionRefId);
        sessionImpl.relocationComplete();
          } }, identity);
        setupSuccess();
    } catch (Exception e) {
        logger.logThrow(
            Level.WARNING, e,
      "Relocating ClientSession for identity:{0} " +
      "to local node:{1} throws",
      identity, sessionService.getLocalNodeId());
        notifySetupFailureAndDisconnect(
      new RelocateFailureException(
          RELOCATE_REFUSED_REASON, e));
        return;
    }
      }
     
  } else {
      /*
       * Redirect login to assigned (non-local) node.
       */
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
        Level.FINE,
        "redirecting login for identity:{0} " +
        "from nodeId:{1} to node:{2}",
        identity, sessionService.getLocalNodeId(), assignedNodeId);
      }

      final long nodeId = assignedNodeId;
      scheduleNonTransactionalTask(
          new AbstractKernelRunnable("SendLoginRedirectMessage") {
        public void run() {
      try {
                            try {
                                loginRedirect(nodeId);
                            } catch (Exception ex) {
                                setupFailure(
                                    new LoginFailureException("Redirect failed",
                                                              ex));
                            }
                        } finally {
                            handleDisconnect(false, false);
                        }
        } });
  }
    }

    /**
     * Notifies the "setup" future that login has been redirected to the
     * specified {@code nodeId}, and sets local state indicating that the
     * login request has been handled.
     *
     * @param  node a nodeId
     */
    private void loginRedirect(long nodeId) {
  synchronized (lock) {
      checkConnectedState();
      Set<ProtocolDescriptor> descriptors =
    sessionService.getProtocolDescriptors(nodeId);
      setupCompletionFuture.setException(
     new LoginRedirectException(nodeId, descriptors));
      state = State.LOGIN_HANDLED;
  }
    }

    /**
     * Throws an {@code IllegalStateException} if the associated client
     * session handler is not in the {@code CONNECTED} state.
     */
    private void checkConnectedState() {
  assert Thread.holdsLock(lock);
 
  if (state != State.CONNECTED) {
      if (logger.isLoggable(Level.WARNING)) {
    logger.log(
        Level.WARNING,
        "unexpected state:{0} for login protocol message, " +
        "session:{1}", state.toString(), this);
      }
      throw new IllegalStateException("unexpected state: " +
              state.toString());
  }
    }

    /**
     * Schedules a task to notify the "setup" future of failure and then
     * disconnect the client session.
     *
     * @param  exception an exception that occurred while setting up the
     *    client session, or {@code null}
     */
    private void notifySetupFailureAndDisconnect(final Exception exception) {
  scheduleNonTransactionalTask(
      new AbstractKernelRunnable("NotifySetupFailureAndDisconnect") {
    public void run() {
        try {
                        setupFailure(exception);
                    } finally {
                        handleDisconnect(false, false);
                    }
    } });
    }
   
    /**
     * Schedules a non-durable, transactional task.
     */
    private void scheduleTask(KernelRunnable task) {
  sessionService.scheduleTask(task, identity);
    }

    /**
     * Schedules a non-durable, non-transactional task.
     */
    private void scheduleNonTransactionalTask(KernelRunnable task) {
  sessionService.getTaskScheduler().scheduleTask(task, identity);
    }

    /**
     * Constructs the ClientSession.
     */
    private class CreateClientSessionTask extends AbstractKernelRunnable {
 
  private volatile BigInteger id;;

  /** Constructs and instance. */
  CreateClientSessionTask() {
      super(null);
  }

  /** {@inheritDoc} */
  public void run() {
      ClientSessionImpl sessionImpl =
                    new ClientSessionImpl(sessionService,
                                          identity,
                                          protocol.getDeliveries(),
                                          protocol.getMaxMessageLength());
      id = sessionImpl.getId();
  }

  /**
   * Returns the session ID for the created client session.
   */
  BigInteger getId() {
      return id;
  }
    }
   
    /**
     * This is a transactional task to notify the application's
     * {@code AppListener} that this session has logged in.
     */
    private class LoginTask extends AbstractKernelRunnable {
 
  /** Constructs an instance. */
  LoginTask() {
      super(null);
  }
       
        /**
         * Retrieve the {@code AppListener} from the {@code DataService},
         * unwrapping it from its {@code ManagedSerializable} if necessary.
         *
         * @return the {@code AppListener} for the application
         */
        @SuppressWarnings("unchecked")
        private AppListener getAppListener() {
            ManagedObject obj = dataService.getServiceBinding(
                    StandardProperties.APP_LISTENER);
            return (obj instanceof AppListener) ?
                (AppListener) obj :
                ((ManagedSerializable<AppListener>) obj).get();
        }
 
  /**
   * Invokes the {@code AppListener}'s {@code loggedIn}
   * callback, which returns a client session listener.  If the
   * returned listener is serializable, then this method does
   * the following:
   *
   * a) queues the appropriate acknowledgment to be
   * sent when this transaction commits, and
   * b) schedules a task (on transaction commit) to call
   * {@code notifyLoggedIn} on the identity.
   *
   * If the client session needs to be disconnected (if {@code
   * loggedIn} returns a non-serializable listener (including
   * {@code null}), or throws a non-retryable {@code
   * RuntimeException}, then this method submits a
   * non-transactional task to disconnect the client session.
   * If {@code loggedIn} throws a retryable {@code
   * RuntimeException}, then that exception is thrown to the
   * caller.
   */
  public void run() {
      AppListener appListener = getAppListener();
      logger.log(
    Level.FINEST,
    "invoking AppListener.loggedIn session:{0}", identity);

      ClientSessionListener returnedListener = null;
      RuntimeException ex = null;

      ClientSessionImpl sessionImpl =
    ClientSessionImpl.getSession(dataService, sessionRefId);
      try {
    returnedListener =
        appListener.loggedIn(sessionImpl.getWrappedClientSession());
      } catch (RuntimeException e) {
    ex = e;
      }
   
      if (returnedListener instanceof Serializable) {
    logger.log(
        Level.FINEST,
        "AppListener.loggedIn returned {0}", returnedListener);

    sessionImpl.putClientSessionListener(
        dataService, returnedListener);

    sessionService.checkContext().
        addCommitAction(sessionRefId,
            new LoginResultAction(true, null), true);

    sessionService.scheduleTaskOnCommit(
        new AbstractKernelRunnable("NotifyLoggedIn") {
      public void run() {
          logger.log(
              Level.FINEST,
        "calling notifyLoggedIn on identity:{0}",
        identity);
          // notify that this identity logged in,
          // whether or not this session is connected at
          // the time of notification.
          identity.notifyLoggedIn();
      } });
   
      } else {
    LoginFailureException loginFailureEx;
    if (ex == null) {
        logger.log(
            Level.WARNING,
      "AppListener.loggedIn returned non-serializable " +
      "ClientSessionListener:{0}", returnedListener);
        loginFailureEx = new LoginFailureException(
      LOGIN_REFUSED_REASON,
      LoginFailureException.FailureReason.REJECTED_LOGIN);
    } else if (!isRetryableException(ex)) {
        logger.logThrow(
      Level.WARNING, ex,
      "Invoking loggedIn on AppListener:{0} with " +
      "session: {1} throws",
      appListener, ClientSessionHandler.this);
        loginFailureEx =
      new LoginFailureException(LOGIN_REFUSED_REASON, ex);
    } else {
        throw ex;
    }
    sessionService.checkContext().
        addCommitAction(
      sessionRefId,
      new LoginResultAction(false, loginFailureEx),
      true);

    sessionImpl.disconnect();
      }
  }
    }


    /**
     * This future is constructed with the {@code RequestCompletionHandler}
     * passed to one of the {@link SessionProtocolHandler} methods.
     */
    private static class RequestCompletionFuture
  extends AbstractCompletionFuture<Void>
    {
  /**
   * Constructs an instance with the specified {@code completionHandler}.
   *
   * @param  completionHandler a completionHandler
   */
  RequestCompletionFuture(
      RequestCompletionHandler<Void> completionHandler)
  {
      super(completionHandler);
  }

  /** {@inheritDoc} */
  protected Void getValue() {
      return null;
  }

  /** {@inheritDoc} */
  public void setException(Throwable throwable) {
       super.setException(throwable);
  }

  public void done() {
      super.done();
  }
    }

    /**
     * This future is constructed with the {@link RequestCompletionHandler}
     * passed to one of the {@code ProtocolListener}'s methods: {@code
     * newLogin} or {@code relocatedSession}.
     */
    static class SetupCompletionFuture
  extends AbstractCompletionFuture<SessionProtocolHandler>
    {
  /** The session protocol handler. */
  private final SessionProtocolHandler protocolHandler;

  /**
   * Constructs an instance with the specified {@code protocolHandler}
   * and {@code completionHandler}.
   *
   * @param  protocolHandler a session protocol handler
   * @param  completionHandler a completionHandler
   */
  SetupCompletionFuture(
      SessionProtocolHandler protocolHandler,
      RequestCompletionHandler<SessionProtocolHandler> completionHandler)
        {
      super(completionHandler);
      this.protocolHandler = protocolHandler;
  }

  /** {@inheritDoc} */
  protected SessionProtocolHandler getValue() {
      return protocolHandler;
  }

  /** {@inheritDoc} */
  public void setException(Throwable throwable) {
      super.setException(throwable);
  }

  /** {@inheritDoc} */
  public void done() {
      super.done();
  }
    }

    /* -- Implement Commit Actions -- */

    /**
     * An action to report the result of a login.
     */
    private class LoginResultAction implements Action {
  /** The login result. */
  private final boolean loginSuccess;

  /** The login exception. */
  private final LoginFailureException loginException;
 
  /**
   * Records the login result in this context, so that the specified
   * client {@code session} can be notified when this context
   * commits.  If {@code success} is {@code false}, the specified
   * {@code exception} will be used as the cause of the {@code
   * ExecutionException} in the {@code Future} passed to the {@link
   * RequestCompletionHandler} for the login request, and no
   * subsequent session messages will be forwarded to the session,
   * even if they have been enqueued during the current transaction.
   * If success is {@code true}, then the {@code Future} passed to
   * the {@code RequestCompletionHandler} for the login request will
   * contain this {@link SessionProtocolHandler}.
   *
   * <p>When the transaction commits, the session's associated {@code
   * ClientSessionHandler} is notified of the login result, and if
   * {@code success} is {@code true}, all enqueued messages will be
   * delivered to the client session.
   *
   * @param  success if {@code true}, login was successful
   * @param  ex a login failure exception, or {@code null}
   *    (only valid if {@code success} is {@code false}
   * @throws   TransactionException if there is a problem with the
   *    current transaction
   */
  LoginResultAction(boolean success, LoginFailureException ex) {
      loginSuccess = success;
      loginException = ex;
  }

  /** {@inheritDoc} */
  public boolean flush() {
      if (!isConnected()) {
    return false;
      } else if (loginSuccess) {
    setupSuccess();
    return true;
      } else {
    setupFailure(loginException);
    return false;
      }
  }
    }

    /**
     * An action to send a message.  This commit action is created by the
     * associated session's {@code ClientSessionImpl} when processing a
     * request to send a message to the client.<p>
     *
     * When this action is executed, it notifies the session's {@link
     * SessionProtocol}  to send the message obtained from the
     * {@link SendEvent} specified during construction.
     */
    class SendMessageAction implements Action {

  private final SendEvent sendEvent;

  /**
   * Constructs and instance with the specified {@code sendEvent}.
   *
   * @param sendEvent a send event containing a message and delivery
   *        guarantee
   */
  SendMessageAction(SendEvent sendEvent) {
      this.sendEvent = sendEvent;
  }

  /** {@inheritDoc} */
  public boolean flush() {
      if (!isConnected()) {
    return false;
      }
     
      try {
    protocol.sessionMessage(
        ByteBuffer.wrap(sendEvent.message),
        sendEvent.delivery);
      } catch (Exception e) {
    logger.logThrow(Level.WARNING, e,
        "sessionMessage throws");
      }
      return true;
  }
    }

    /**
     * An action to start the process of moving the associated client
     * session from this node to another node (the {@code newNode}
     * specified during construction).  This commit action is created by
     * the associated session's {@code ClientSessionImpl} when processing a
     * request to move the client to a new node. <p>
     *
     * When this action is executed, it sends a {@link
     * ClientSessionServer#relocatingSession relocatingSession}
     * notification to the new node's {@code ClientSessionServer} to obtain
     * a relocation key for the client session.  Once the relocation key is
     * obtained, it notifies the client session service to notify all
     * registered {@link ClientSessionStatusListener}s (i.e., the
     * {@code ChannelService}) to prepare to relocate the client session.<p>
     *
     * When all {@code ClientSessionStatusListener}s have completed
     * preparing the client session to relocate, this instance's
     * {@link MoveAction#suspend suspend} method is invoked which
     * notifies the associated session's {@code SessionProtocol} to
     * suspend sending messages to the server.<p>
     *
     * When the suspend operation's {@code SuspendCompletionHandler} is
     * notified as {@code completed}, that completion handler notifies
     * this action's {@code completed} method, which, in turn notifies
     * the associated session's {@link SessionProtocol} to {@code
     * relocate} the client's connection, supplying the new node's
     * information and the relocation key.
     */
    class MoveAction implements Action, SimpleCompletionHandler {

  /** The new node. */
  private final Node newNode;

  /** The client session server. */
  private final ClientSessionServer server;

  /** The relocation key. */
  private byte[] relocationKey;

  private boolean isCompleted = false;

  /**
   * Constructs an instance with the specified {@code newNode}.
   *
   * @param newNode the new node for the session
   */
  MoveAction(Node newNode) {
      this.newNode = newNode;
      this.server =
    sessionService.getClientSessionServer(newNode.getId());
  }

  /** {@inheritDoc} */
  public boolean flush() {
      if (!isConnected()) {
    return false;
      }
      byte[] key;
      try {
    /*
     * Notify new node that session is being relocated there and
     * obtain relocation key.
     */
    key = server.relocatingSession(
         identity, sessionRefId.toByteArray(),
        sessionService.getLocalNodeId());
   
      } catch (Exception e) {
    // If there is a problem contacting the destination node or
    // obtaining the relocation key, disconnect the client
    // session immediately.
    if (logger.isLoggable(Level.WARNING)) {
        logger.logThrow(
      Level.WARNING, e,
      "relocating client session:{0} throws", this);
    }
    handleDisconnect(false, true);
    return false;
      }
     
      synchronized (this) {
    this.relocationKey = key;
      }
   
      /*
       * Notify client to relocate its session to the new node
       * specifying the relocation key.
       */
      synchronized (lock) {
    relocatePrepareCompletionHandler = this;
      }
     
      sessionService.notifyPrepareToRelocate(
    sessionRefId, newNode.getId());
      // TBD: why is this return value false?
      return false;
  }

  /**
   * Returns {@code true} if relocation preparation is completed.
   * @return {@code true} if relocation preparation is completed
   */
  public synchronized boolean isCompleted() {
      return isCompleted;
  }

  /**
   * Suspends messages to the client before sending the 'relocate'
   * notification.   This method is invoked after the session has
   * been prepared for relocation.  When message suspension is
   * complete, this instance's {@code completed} method is invoked
   * to notify the client to relocate.
   */
  public void suspend() {
      try {
    if (!supportsRelocation()) {
        logger.log(
      Level.WARNING,
      "Disconnecting a non-relocatable session:{0} " +
      "that was erroneously prepared to relocate", identity);
        handleDisconnect(false, true);
        return;
    }
    ((SessionRelocationProtocol) protocol).suspend(
        new SuspendCompletionHandler());
   
      } catch (Exception e) {
    if (logger.isLoggable(Level.WARNING)) {
        logger.logThrow(
      Level.WARNING, e,
      "suspending messages to client session:{0} throws",
      this);
    }
      }
  }

  /** {@inheritDoc} <p>
   *
   * This method is invoked after the session has been prepared for
   * relocation and messages have been suspended from the client
   * (i.e., invoked from the {@code SuspendCompletionHandler.completed}
   * method).
   */
  public void completed() {
      synchronized (this) {
    assert relocationKey != null;
    if (isCompleted) {
        return;
    }
    isCompleted = true;
      }
     
      if (!supportsRelocation()) {
    logger.log(
        Level.WARNING,
      "Disconnecting a non-relocatable session:{0} " +
        "that was erroneously prepared to relocate", identity);
    handleDisconnect(false, true);
    return;
      }
     
      final Set<ProtocolDescriptor> descriptors =
    sessionService.getProtocolDescriptors(newNode.getId());
      final byte[] key;
      synchronized (this) {
    key = this.relocationKey;
      }

      // Add client 'relocate' notification to the task queue to
      // ensure that all previous requests sent before the client
      // was suspended are processed before relocation.
      taskQueue.addTask(
    new AbstractKernelRunnable("NotifySessionRelocate") {
        public void run() {
      try {
          ((SessionRelocationProtocol) protocol).relocate(
         descriptors, ByteBuffer.wrap(key),
        new RelocateCompletionHandler());
      } catch (Exception e) {
          if (logger.isLoggable(Level.WARNING)) {
        logger.logThrow(
            Level.WARNING, e,
            "relocating client session:{0} throws",
            this);
          }
          // If there is a problem with relocation, the
          // client session will be cleaned up by one
          // of the "monitors" (on the old or new
          // node) keeping track of this session's
          // relocation, so there is no need to do it
          // here.
      }
        } }, identity);
  }
    }

    /**
     * An action to disconnect the client session.  This commit action is
     * created by the associated session's {@code ClientSessionImpl} when
     * processing a request to disconnect the client session.
     */
    class DisconnectAction implements Action {
  /** {@inheritDoc} */
  public boolean flush() {
      handleDisconnect(false, true);
      return false;
  }
    }

    /**
     * A completion handler for notifying the client to suspend messages.
     * When messages suspension is completed, this handler notifies {@code
     * MoveAction} that suspension relocation preparation is complete so that
     * it can send a 'relocate' notification to the client.
     */
    private class SuspendCompletionHandler
  implements RequestCompletionHandler<Void>
    {
  private boolean isCompleted = false;

  /** {@inheritDoc} */
  public synchronized void completed(Future<Void> result) {
      synchronized (this) {
    isCompleted = true;
      }

      if (logger.isLoggable(Level.FINE)) {
    logger.log(Level.FINE,
         "suspend completed, identity:{0} localNodeId:{1}",
         identity, sessionService.getLocalNodeId());
      }
      if (relocatePrepareCompletionHandler != null) {
    relocatePrepareCompletionHandler.completed();
      }
  }

  synchronized boolean isCompleted() {
      return isCompleted;
  }
    }
   
    /**
     * A completion handler for notifying the client to relocate.
     */
    private class RelocateCompletionHandler
  implements RequestCompletionHandler<Void>
    {
  private boolean isCompleted = false;

  /** {@inheritDoc} */
  public synchronized void completed(Future<Void> result) {
      // TBD: need to check result for Exception and disconnect if an
      // exception is thrown.
      isCompleted = true;
      if (logger.isLoggable(Level.FINE)) {
    logger.log(Level.FINE,
         "relocate completed, identity:{0} localNodeId:{1}",
         identity, sessionService.getLocalNodeId());
      }
  }

  synchronized boolean isCompleted() {
      return isCompleted;
  }
    }
}
TOP

Related Classes of com.sun.sgs.impl.service.session.ClientSessionHandler$LoginResultAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.