Package com.sun.sgs.client.simple

Source Code of com.sun.sgs.client.simple.SimpleClient$SimpleClientChannel

/*
* Copyright (c) 2007-2009, Sun Microsystems, Inc.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in
*       the documentation and/or other materials provided with the
*       distribution.
*     * Neither the name of Sun Microsystems, Inc. nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.sun.sgs.client.simple;

import java.io.IOException;
import java.math.BigInteger;
import java.net.PasswordAuthentication;
import java.nio.ByteBuffer;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.sgs.client.ClientChannel;
import com.sun.sgs.client.ClientChannelListener;
import com.sun.sgs.client.ServerSession;
import com.sun.sgs.client.ServerSessionListener;
import com.sun.sgs.impl.client.comm.ClientConnection;
import com.sun.sgs.impl.client.comm.ClientConnectionListener;
import com.sun.sgs.impl.client.comm.ClientConnector;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.impl.sharedutil.MessageBuffer;
import com.sun.sgs.protocol.simple.SimpleSgsProtocol;

/**
* An implementation of {@link ServerSession} that clients can use to manage
* logging in and communicating with the server. A {@code SimpleClient}
* is used to establish (or re-establish) a login session with the server,
* send messages to the server, and log out.
* <p>
* A {@code SimpleClient} is constructed with a {@link
* SimpleClientListener} which receives connection-related events as well
* as messages from the server application.
* <p>
* If the server session associated with a simple client becomes
* disconnected, then its {@link #send send} method will throw
* {@code IllegalStateException}.  A disconnected
* client can use the {@link #login login} method to log in again.
*/
public class SimpleClient implements ServerSession {

    /** The logger for this class. */
    private static final LoggerWrapper logger =
        new LoggerWrapper(Logger.getLogger(SimpleClient.class.getName()));
   
    /** The listener for this simple client. */
    private final SimpleClientListener clientListener;

    /**
     * The current {@code ClientConnection}, if connected, or
     * {@code} null if disconnected.
     */
    private volatile ClientConnection clientConnection = null;

    /**
     * Indicates that either a connection or disconnection attempt
     * is in progress.
     */
    private volatile boolean connectionStateChanging = false;

    /** Indicates whether this client is logged in. */
    private volatile boolean loggedIn = false;

    /** Reconnection key.  TODO reconnect not implemented */
    @SuppressWarnings("unused")
    private byte[] reconnectKey;

    /** The map of channels this client is a member of, keyed by channel ID */
    private final ConcurrentHashMap<BigInteger, SimpleClientChannel> channels =
        new ConcurrentHashMap<BigInteger, SimpleClientChannel>();

    /**
     * Creates an instance of this class with the specified listener. Once
     * this client is logged in (by using the {@link #login login} method),
     * the specified listener receives connection-related events, receives
     * messages from the server, and also receives notification of each
     * channel the client is joined to. If this client becomes disconnected
     * for any reason, it may use the {@code login} method to log in
     * again.
     *
     * @param listener a listener that will receive events for this client
     */
    public SimpleClient(SimpleClientListener listener) {
        if (listener == null) {
            throw new NullPointerException(
                "The SimpleClientListener argument must not be null");
        }
        this.clientListener = listener;
    }

    /**
     * Initiates a login session with the server. A session is established
     * asynchronously with the server as follows:
     *
     * <p>First, this client attempts to establish a connection with the
     * server.  If the client fails to establish a connection, then the
     * client listener's {@link SimpleClientListener#disconnected
     * disconnected} method is invoked with a {@code String} indicating the
     * reason for the failure.
     *
     * <p>If a connection with the server is successfully established, this
     * client's {@link PasswordAuthentication login credential}
     * is obtained by invoking its {@link SimpleClientListener listener}'s
     * {@link SimpleClientListener#getPasswordAuthentication
     * getPasswordAuthentication} method with a login prompt.
     *
     * <p>Next, this client sends a login request to the server.  If the
     * login request is malformed, the client listener's {@link
     * SimpleClientListener#disconnected disconnected} method is invoked
     * with a {@code String} indicating the reason for the failure or
     * {@code null} if no reason can be determined.
     *
     * <p>If the client's login credential (as obtained above) is
     * verified, then the client listener's {@link
     * SimpleClientListener#loggedIn loggedIn} method is invoked. If,
     * however, the login fails due to a login authentication failure or
     * some other failure on the server while processing the login request,
     * the client listener's {@link SimpleClientListener#loginFailed
     * loginFailed} method is invoked with a {@code String} indicating the
     * reason for the failure.
     *
     * <p>If this client is disconnected for any reason (including login
     * failure), this method may be used again to log in.
     * <p>
     * The supported connection properties are:
     * <table summary="Shows property keys and associated values">
     * <tr><th>Key</th>
     *     <th>Description of Associated Value</th></tr>
     * <tr><td>{@code host}</td>
     *     <td>SGS host address <b>(required)</b></td></tr>
     * <tr><td>{@code port}</td>
     *     <td>SGS port <b>(required)</b></td></tr>
     * </table>
     *
     * @param props the connection properties to use in creating the
     *        client's session
     *
     * @throws IOException if a synchronous IO error occurs
     * @throws IllegalStateException if this session is already connected
     *         or connecting
     * @throws SecurityException if the caller does not have permission
     *         to connect to the remote endpoint
     */
    public void login(Properties props) throws IOException {
        synchronized (this) {
            if (connectionStateChanging || clientConnection != null) {
                RuntimeException re =
                    new IllegalStateException(
                        "Session already connected or connecting");
                logger.logThrow(Level.FINE, re, re.getMessage());
                throw re;
            }
            connectionStateChanging = true;
        }
        ClientConnector connector = ClientConnector.create(props);
        connector.connect(new SimpleClientConnectionListener());
    }

    /* -- Implement ServerSession -- */
   
    /**
     * {@inheritDoc}
     */
    public boolean isConnected() {
        return (clientConnection != null);
    }

    /**
     * {@inheritDoc}
     */
    public void logout(boolean force) {
        synchronized (this) {
            if (connectionStateChanging || clientConnection == null) {
                RuntimeException re =
                    new IllegalStateException("Client not connected");
                logger.logThrow(Level.FINE, re, re.getMessage());
                throw re;
            }
            connectionStateChanging = true;
        }
        if (force) {
            try {
                loggedIn = false;
                clientConnection.disconnect();
            } catch (IOException e) {
                logger.logThrow(Level.FINE, e, "During forced logout:");
                // ignore
            }
        } else {
            try {
                ByteBuffer msg =
                    ByteBuffer.wrap(
                        new byte[] { SimpleSgsProtocol.LOGOUT_REQUEST });
                sendRaw(msg.asReadOnlyBuffer());
            } catch (IOException e) {
                logger.logThrow(Level.FINE, e, "During graceful logout:");
                try {
                    loggedIn = false;
                    clientConnection.disconnect();
                } catch (IOException e2) {
                    logger.logThrow(Level.FINE, e2, "During forced logout:");
                    // ignore
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     * <p>Note: The server does not guarantee delivery of any session
     * message (sent via this method) that is received by the server before
     * the sending client is logged in.  Therefore messages sent before this
     * client is logged in, that is, before the {@link
     * SimpleClientListener#loggedIn SimpleClientListener.loggedIn} method
     * is invoked, may be dropped by the server.
     */
    public void send(ByteBuffer message) throws IOException {
        checkConnected();
        ByteBuffer msg = ByteBuffer.allocate(1 + message.remaining());
        msg.put(SimpleSgsProtocol.SESSION_MESSAGE)
           .put(message)
           .flip();
        sendRaw(msg);
    }

    /**
     * Sends raw data to the underlying connection.
     *
     * @param buf the data to send
     * @throws IOException if an IO problem occurs
     */
    private void sendRaw(ByteBuffer buf) throws IOException {
        clientConnection.sendMessage(buf);
    }

    /**
     * Throws an exception if this client is not connected.
     *
     * @throws IllegalStateException if this client is not connected
     */
    private void checkConnected() {
        if (!isConnected()) {
            RuntimeException re =
                new IllegalStateException("Client not connected");
            logger.logThrow(Level.FINE, re, re.getMessage());
            throw re;
        }
    }

    /**
     * Throws an exception if this client is not logged in.
     *
     * @throws IllegalStateException if this client is not logged in
     */
    private void checkLoggedIn() {
        if (!loggedIn) {
            RuntimeException re =
                new IllegalStateException("Client not logged in");
            logger.logThrow(Level.FINE, re, re.getMessage());
            throw re;
        }
    }

    /**
     * Receives callbacks on the associated {@code ClientConnection}.
     */
    final class SimpleClientConnectionListener
        implements ClientConnectionListener
    {
           
  /** The password authentication used for login.
   */
  private volatile PasswordAuthentication authentication = null;
   
        /** Indicates whether this listener expects a disconnect message. */
        private volatile boolean expectingDisconnect = false;

  /** Indicates whether the disconnected callback should not be
   * invoked. */
  private volatile boolean suppressDisconnectedCallback = false;

        /** Indicates whether this listener has been disabled because
         *  of an automatic login redirect to another host and port.
         *  We need to disconnect our previous connection, but we don't
         *  want to tell the client listener.  We'll accept no messages
         *  when we're in this state.
         */
        private volatile boolean redirect = false;

  /** Constructs an instance. */
  SimpleClientConnectionListener() {
  }

  /**
   * Constructs an instance with the specified password {@code
   * authentication}.  This is used in the redirect case, so the
   * password authentication doesn't need to be reobtained from the
   * user.
   */
  SimpleClientConnectionListener(PasswordAuthentication authentication) {
      this.authentication = authentication;
  }
       
        /* -- Implement ClientConnectionListener -- */
        /**
         * {@inheritDoc}
         */
        public void connected(ClientConnection connection)
        {
            logger.log(Level.FINER, "Connected");
            synchronized (SimpleClient.this) {
                connectionStateChanging = false;
                clientConnection = connection;
            }

            // First time through, we haven't authenticated yet.
            // We don't want to have to reauthenticate for each login
            // redirect.
            if (authentication == null) {
                authentication = clientListener.getPasswordAuthentication();
            }

            if (authentication == null) {
                logout(true);
                throw new NullPointerException(
                    "The returned PasswordAuthentication must not be null");
            }

            String user = authentication.getUserName();
            String pass = new String(authentication.getPassword());
            MessageBuffer msg =
                new MessageBuffer(2 +
                    MessageBuffer.getSize(user) +
                    MessageBuffer.getSize(pass));
            msg.putByte(SimpleSgsProtocol.LOGIN_REQUEST).
                putByte(SimpleSgsProtocol.VERSION).
                putString(user).
                putString(pass);
            try {
                sendRaw(ByteBuffer.wrap(msg.getBuffer()).asReadOnlyBuffer());
            } catch (IOException e) {
                logger.logThrow(Level.FINE, e, "During login request:");
                logout(true);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void disconnected(boolean graceful, byte[] message) {
            if (redirect) {
                // This listener has been redirected, and this callback
                // should be ignored.  In particular, we don't want to
                // change the clientConnection state (this could be a
                // real problem if the disconnected callback was delayed
                // to after the connected callback from the automatic
                // login redirect), and we don't want to notify the
                // client listener of the redirect.
                return;
            }
            synchronized (SimpleClient.this) {
                if (clientConnection == null && (!connectionStateChanging)) {
                    // Someone else beat us here
                    return;
                }
                clientConnection = null;
                connectionStateChanging = false;
            }
            String reason = null;
            if (message != null) {
                MessageBuffer msg = new MessageBuffer(message);
                reason = msg.getString();
            }

            for (SimpleClientChannel channel : channels.values()) {
                try {
                    channel.left();
                } catch (RuntimeException e) {
                    logger.logThrow(Level.FINE, e,
                        "During leftChannel ({0}) on disconnect:",
                        channel.getName());
                    // ignore the exception
                }
            }
            channels.clear();

            // TBI implement graceful disconnect.
            // For now, look at the boolean we set when expecting
            // disconnect
            if (!suppressDisconnectedCallback) {
    clientListener.disconnected(expectingDisconnect, reason);
      }
      suppressDisconnectedCallback = false;
            expectingDisconnect = false;
        }

        /**
         * {@inheritDoc}
         */
        public void receivedMessage(byte[] message) {
            try {
                MessageBuffer msg = new MessageBuffer(message);
               
                if (logger.isLoggable(Level.FINER)) {
                    String logMessage = String.format(
                        "Message length:%d", message.length);
                    logger.log(Level.FINER, logMessage);
                }

                handleApplicationMessage(msg);

            } catch (IOException e) {
                logger.logThrow(Level.FINER, e, e.getMessage());
                if (isConnected()) {
                    try {
                        clientConnection.disconnect();
                    } catch (IOException e2) {
                        logger.logThrow(Level.FINEST, e2,
                            "Disconnect failed after {0}", e.getMessage());
                        // Ignore
                    }
                }
            }
        }
       
        /**
         * Processes an application message.
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleApplicationMessage(MessageBuffer msg)
            throws IOException
        {
            byte command = msg.getByte();
            switch (command) {
            case SimpleSgsProtocol.LOGIN_SUCCESS:
                handleLoginSuccess(msg);
                break;

            case SimpleSgsProtocol.LOGIN_FAILURE:
                handleLoginFailure(msg);
                break;

            case SimpleSgsProtocol.LOGIN_REDIRECT:
                handleLoginRedirect(msg);
                break;

            case SimpleSgsProtocol.SESSION_MESSAGE:
                handleSessionMessage(msg);
                break;

            case SimpleSgsProtocol.RECONNECT_SUCCESS:
                handleReconnectSuccess(msg);
                break;

            case SimpleSgsProtocol.RECONNECT_FAILURE:
                handleReconnectFailure(msg);
                break;

            case SimpleSgsProtocol.LOGOUT_SUCCESS:
                handleLogoutSuccess(msg);
                break;

            case SimpleSgsProtocol.CHANNEL_JOIN:
                handleChannelJoin(msg);
                break;

            case SimpleSgsProtocol.CHANNEL_LEAVE:
                handleChannelLeave(msg);
                break;

            case SimpleSgsProtocol.CHANNEL_MESSAGE:
                handleChannelMessage(msg);
                break;

            default:
                throw new IOException(
                    String.format("Unknown session opcode: 0x%02X", command));
            }
        }
       
        /**
         * Process a login success message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleLoginSuccess(MessageBuffer msg) {
            logger.log(Level.FINER, "Logged in");
            reconnectKey = msg.getBytes(msg.limit() - msg.position());
            loggedIn = true;
            clientListener.loggedIn();
        }
       
        /**
         * Process a login failure message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleLoginFailure(MessageBuffer msg) {
            String reason = msg.getString();
            logger.log(Level.FINER, "Login failed: {0}", reason);
            suppressDisconnectedCallback = true;
            try {
                clientConnection.disconnect();
            } catch (IOException e) {
                // ignore
                if (logger.isLoggable(Level.FINE)) {
                    logger.logThrow(Level.FINE, e,
                                    "Disconnecting after login failure throws");
                }
            }
            clientListener.loginFailed(reason);
        }
       
        /**
         * Process a login redirect message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleLoginRedirect(MessageBuffer msg)
                throws IOException {
            String host = msg.getString();
            int port = msg.getInt();
            logger.log(Level.FINER, "Login redirect: {0}:{1}", host, port);

            // Disconnect our current connection, and connect to the
            // new host and port
            ClientConnection oldConnection = clientConnection;
            synchronized (SimpleClient.this) {
                clientConnection = null;
                connectionStateChanging = true;
            }
            try {
                oldConnection.disconnect();
            } catch (IOException e) {
                // ignore
                if (logger.isLoggable(Level.FINE)) {
                    logger.logThrow(Level.FINE, e,
                                    "Disconnecting after login redirect " +
                                    "throws");
                }
            }
            redirect = true;
            Properties props = new Properties();
            props.setProperty("host", host);
            props.setProperty("port", String.valueOf(port));
            ClientConnector connector = ClientConnector.create(props);
            // This eventually causes connected to be called
            connector.connect(
    new SimpleClientConnectionListener(authentication));
        }
       
        /**
         * Process a session message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleSessionMessage(MessageBuffer msg) {
            logger.log(Level.FINEST, "Direct receive");
            checkLoggedIn();
            byte[] msgBytes = msg.getBytes(msg.limit() - msg.position());
            ByteBuffer buf = ByteBuffer.wrap(msgBytes);
            try {
                clientListener.receivedMessage(buf.asReadOnlyBuffer());
            } catch (RuntimeException e) {
                if (logger.isLoggable(Level.WARNING)) {
                    logger.logThrow(
                            Level.WARNING, e,
                            "SimpleClientListener.receivedMessage callback " +
                            "throws");
                }
            }
        }
       
        /**
         * Process a reconnect success message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleReconnectSuccess(MessageBuffer msg) {
            logger.log(Level.FINER, "Reconnected");
            loggedIn = true;
            reconnectKey = msg.getBytes(msg.limit() - msg.position());
            clientListener.reconnected();
        }
       
        /**
         * Process a reconnect failure message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleReconnectFailure(MessageBuffer msg) {
            try {
                String reason = msg.getString();
                logger.log(Level.FINER, "Reconnect failed: {0}", reason);
                clientConnection.disconnect();
            } catch (IOException e) {
                // ignore
                if (logger.isLoggable(Level.FINE)) {
                    logger.logThrow(Level.FINE, e,
                                    "Disconnecting a failed reconnect throws");
                }
            }
        }
       
        /**
         * Process a logout success message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleLogoutSuccess(MessageBuffer msg) {
            logger.log(Level.FINER, "Logged out gracefully");
            expectingDisconnect = true;
            loggedIn = false;
            try {
                clientConnection.disconnect();
            } catch (IOException e) {
                // ignore
                if (logger.isLoggable(Level.FINE)) {
                    logger.logThrow(Level.FINE, e,
                                    "Disconnecting after graceful logout " +
                                    "throws");
                }
            }
        }
       
        /**
         * Process a channel join message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleChannelJoin(MessageBuffer msg) {
            logger.log(Level.FINER, "Channel join");
            checkLoggedIn();
            String channelName = msg.getString();
            byte[] channelIdBytes =
                    msg.getBytes(msg.limit() - msg.position());
            BigInteger channelId = new BigInteger(1, channelIdBytes);
            SimpleClientChannel channel =
                    new SimpleClientChannel(channelName, channelId);
            if (channels.putIfAbsent(channelId, channel) == null) {
                channel.joined();
            } else {
                logger.log(Level.WARNING,
                           "Cannot join channel {0}: already a member",
                           channelName);
            }
        }
       
        /**
         * Process a channel leave message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleChannelLeave(MessageBuffer msg) {
            logger.log(Level.FINER, "Channel leave");
            checkLoggedIn();
            byte[] channelIdBytes =
                    msg.getBytes(msg.limit() - msg.position());
            BigInteger channelId = new BigInteger(1, channelIdBytes);
            SimpleClientChannel channel = channels.remove(channelId);
            if (channel != null) {
                channel.left();
            } else {
                logger.log(Level.WARNING,
                           "Cannot leave channel {0}: not a member",
                           channelId);
            }
        }
       
        /**
         * Process a channel message message
         *
         * @param msg the message to process
         * @throws IOException if an IO problem occurs
         */
        private void handleChannelMessage(MessageBuffer msg) {
            logger.log(Level.FINEST, "Channel recv");
            checkLoggedIn();
            BigInteger channelId =
                    new BigInteger(1, msg.getBytes(msg.getShort()));
            SimpleClientChannel channel = channels.get(channelId);
            if (channel == null) {
                logger.log(Level.WARNING,
                           "Ignore message on channel {0}: not a member",
                           channelId);
                return;
            }
            byte[] msgBytes = msg.getBytes(msg.limit() - msg.position());
            ByteBuffer buf = ByteBuffer.wrap(msgBytes);
            channel.receivedMessage(buf.asReadOnlyBuffer());
        }

        /**
         * {@inheritDoc}
         */
        public void reconnected(byte[] message) {
            RuntimeException re =
                new UnsupportedOperationException(
                        "Not supported by SimpleClient");
            logger.logThrow(Level.WARNING, re, re.getMessage());
            throw re;
        }

        /**
         * {@inheritDoc}
         */
        public void reconnecting(byte[] message) {
            RuntimeException re =
                new UnsupportedOperationException(
                        "Not supported by SimpleClient");
            logger.logThrow(Level.WARNING, re, re.getMessage());
            throw re;
        }

        /**
         * {@inheritDoc}
         */
        public ServerSessionListener sessionStarted(byte[] message) {
            RuntimeException re =
                new UnsupportedOperationException(
                        "Not supported by SimpleClient");
            logger.logThrow(Level.WARNING, re, re.getMessage());
            throw re;
        }
    }

    /**
     * Simple ClientChannel implementation
     */
    final class SimpleClientChannel implements ClientChannel {

        private final String channelName;
  private final BigInteger channelId;
       
        /**
         * The listener for this channel if the client is a member,
         * or null if the client is no longer a member of this channel.
         */
        private volatile ClientChannelListener listener = null;

        private final AtomicBoolean isJoined = new AtomicBoolean(false);

        SimpleClientChannel(String name, BigInteger id) {
            this.channelName = name;
      this.channelId = id;
        }

        // Implement ClientChannel

        /**
         * {@inheritDoc}
         */
        public String getName() {
            return channelName;
        }

        /**
         * {@inheritDoc}
         */
        public void send(ByteBuffer message) throws IOException {
            if (!isJoined.get()) {
                throw new IllegalStateException(
                    "Cannot send on unjoined channel " + channelName);
            }
      byte[] idBytes = channelId.toByteArray();
      ByteBuffer msg =
    ByteBuffer.allocate(3 + idBytes.length + message.remaining());
      msg.put(SimpleSgsProtocol.CHANNEL_MESSAGE)
         .putShort((short) idBytes.length)
         .put(idBytes)
         .put(message)
         .flip();
            sendRaw(msg);
        }

        // Implementation details

        void joined() {
            if (!isJoined.compareAndSet(false, true)) {
                throw new IllegalStateException(
                    "Already joined to channel " + channelName);
            }

            assert listener == null;

            try {
                listener = clientListener.joinedChannel(this);

                if (listener == null) {
        // FIXME: print a warning?
                    throw new NullPointerException(
                        "The returned ClientChannelListener must not be null");
                }
            } catch (RuntimeException ex) {
                isJoined.set(false);
                throw ex;
            }
        }

        void left() {
            if (!isJoined.compareAndSet(true, false)) {
                throw new IllegalStateException(
                    "Cannot leave unjoined channel " + channelName);
            }

            final ClientChannelListener l = this.listener;
            this.listener = null;

            l.leftChannel(this);
       }
       
        void receivedMessage(ByteBuffer message) {
            if (!isJoined.get()) {
                throw new IllegalStateException(
                    "Cannot receive on unjoined channel " + channelName);
            }

            listener.receivedMessage(this, message);
        }
    }
}
TOP

Related Classes of com.sun.sgs.client.simple.SimpleClient$SimpleClientChannel

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.