Package com.sun.sgs.test.util

Source Code of com.sun.sgs.test.util.AbstractDummyClient$Listener

/*
* 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.test.util;

import com.sun.sgs.impl.io.SocketEndpoint;
import com.sun.sgs.impl.io.TransportType;
import com.sun.sgs.impl.sharedutil.HexDumper;
import com.sun.sgs.impl.sharedutil.MessageBuffer;
import com.sun.sgs.io.Connection;
import com.sun.sgs.io.ConnectionListener;
import com.sun.sgs.io.Connector;
import com.sun.sgs.protocol.simple.SimpleSgsProtocol;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.junit.Assert;

/**
* Abstract dummy client code for testing purposes.
*/
public abstract class AbstractDummyClient extends Assert {
   
    private static final int WAIT_TIME = 5000;

    private static final byte RELOCATE_PROTOCOL_VERSION = 5;

    /* -- client state -- */
    public final String name;
    private final byte protocolVersion;
    protected volatile byte[] reconnectKey = new byte[0];
    private final Object lock = new Object();

    /* -- connection state -- */
    private Connector<SocketAddress> connector;
    private Listener listener;
    private Connection connection;
    private volatile int connectPort = 0;
    private boolean connected = false;
    private boolean waitForSuspendMessages = false;
    private boolean suspendMessages = false;

    /* -- login/logout state -- */
    private boolean loginAck = false;
    private boolean loginSuccess = false;
    private boolean logoutAck = false;

    /* -- redirection state -- */
    private boolean loginRedirect = false;
    private String redirectHost;
    public int redirectPort;

    /* -- relocation state -- */
    private boolean relocateSession;
    private String relocateHost;
    private int relocatePort;
    private byte[] relocateKey = new byte[0];
    private boolean relocateAck;
    private boolean relocateSuccess;
 
 
    /**
     * Constructs an instance with the given {@code name} with the latest
     * protocol version.
     */
    public AbstractDummyClient(String name) {
  this(name, SimpleSgsProtocol.VERSION);
    }

    /**
     * Constructs an instance with the given {@code name} and the specified
     * protocol version.
     */
    public AbstractDummyClient(String name, byte protocolVersion) {
  this.name = name;
  this.protocolVersion = protocolVersion;
    }

    /**
     * Connects this client to the given {@code port} and returns this
     * instance.
     */
    public AbstractDummyClient connect(int port) {
  connectPort = port;
  connected = false;
  listener = new Listener();
  try {
      SocketEndpoint endpoint =
    new SocketEndpoint(
        new InetSocketAddress(InetAddress.getLocalHost(), port),
        TransportType.RELIABLE);
      connector = endpoint.createConnector();
      connector.connect(listener);
  } catch (Exception e) {
      System.err.println(toString() + " connect throws: " + e);
      e.printStackTrace();
      throw new RuntimeException("DummyClient.connect failed", e);
  }
  synchronized (lock) {
      try {
    if (connected == false) {
        lock.wait(WAIT_TIME);
    }
    if (connected != true) {
        throw new RuntimeException(
      toString() + " connect timed out to " + port);
    }
      } catch (InterruptedException e) {
    throw new RuntimeException(
        toString() + " connect timed out to " + port, e);
      }
  }
  return this;
    }

    /**
     * Returns {@code true} if this client is connected.
     */
    public boolean isConnected() {
  synchronized (lock) {
      return connected;
  }
    }

    /**
     * Returns the port last specified for the {@link #connect connect}
     * method.
     */
    public int getConnectPort() {
  return connectPort;
    }

    /**
     * Returns the redirect port.
     */
    public int getRedirectPort() {
  synchronized (lock) {
      return redirectPort;
  }
    }
   
    /**
     * Throws a {@code RuntimeException} if this session is not
     * logged in.
     */
    protected void checkLoggedIn() {
  synchronized (lock) {
      if (!(connected && (loginSuccess || relocateSuccess ))) {
    throw new RuntimeException(
        toString() + " not connected or loggedIn");
      }
  }
    }

    /**
     * Sends a login request and waits for it to be acknowledged,
     * returning {@code true} if login was successful, and {@code
     * false} if login was redirected.  If the login was not successful
     * or redirected, then a {@code RuntimeException} is thrown because
     * the login operation timed out before being acknowledged.
     */
    public boolean login() {
  return login(true);
    }

    /**
     * Sends a login request and if {@code waitForLogin} is {@code
     * true} waits for the request to be acknowledged, returning {@code
     * true} if login was successful, and {@code false} if login was
     * redirected, otherwise a {@code RuntimeException} is thrown
     * because the login operation timed out before being acknowledged.
     *
     * If {@code waitForLogin} is false, this method returns {@code
     * true} if the login is known to be successful (the outcome may
     * not yet be known because the login operation is asynchronous),
     * otherwise it returns false.  Invoke {@code waitForLogin} to wait
     * for an expected successful login.
     */
    public boolean login(boolean waitForLogin) {
  synchronized (lock) {
      if (connected == false) {
    throw new RuntimeException(toString() + " not connected");
      }
  }
  String password = "password";

  MessageBuffer buf =
      new MessageBuffer(2 + MessageBuffer.getSize(name) +
            MessageBuffer.getSize(password));
  buf.putByte(SimpleSgsProtocol.LOGIN_REQUEST).
      putByte(protocolVersion).
      putString(name).
      putString(password);
  loginAck = false;
  try {
      connection.sendBytes(buf.getBuffer());
  } catch (IOException e) {
      throw new RuntimeException(e);
  }
  if (waitForLogin) {
      if (waitForLogin()) {
    return true;
      } else if (isLoginRedirected()) {
    int port = redirectPort;
    disconnect();
    connect(port);
    return login();
      }
  }
  synchronized (lock) {
      return loginSuccess;
  }
    }

    /**
     * Waits for a login acknowledgement, and returns {@code true} if
     * login was successful, {@code false} if login was redirected or
     * failed, otherwise a {@code RuntimeException} is thrown because
     * the login operation timed out before being acknowledged.
     */
    public boolean waitForLogin() {
  synchronized (lock) {
      try {
    if (loginAck == false) {
        lock.wait(WAIT_TIME);
    }
    if (loginAck != true) {
        throw new RuntimeException(toString() + " login timed out");
    }
    if (loginRedirect == true) {
        return false;
    }
    return loginSuccess;
      } catch (InterruptedException e) {
    throw new RuntimeException(toString() + " login timed out", e);
      }
  }
    }

    /**
     * Returns {@code true} if the login is redirected, otherwise returns
     * {@code false}.
     */
    public boolean isLoginRedirected() {
  return loginRedirect;
    }

    /**
     * Notifies this client that it is logged in or relocated and a new
     * reconnect key has been granted.
     */
    protected void newReconnectKey(byte[] reconnectKey) {
    }
   
    /**
     * Waits for this client to receive a RELOCATE_NOTIFICATION message.
     * Throws {@code AssertionFailedError} if the notification is not
     * received before the timeout period, or if {@code expectedPort} is
     * non-zero and does not match the relocation port specified in the
     * received RELOCATE_NOTIFICATION.
     */
    public void waitForRelocationNotification(int expectedPort) {
  checkRelocateProtocolVersion();
  System.err.println(toString() +
         " waiting for relocation notification...");
  synchronized (lock) {
      try {
    if (relocateSession == false) {
        lock.wait(WAIT_TIME);
    }
    if (relocateSession != true) {
        fail(toString() + " relocate notification timed out");
    }
    if (expectedPort != 0) {
        assertEquals(expectedPort, relocatePort);
    }
      } catch (InterruptedException e) {
    fail(toString() + " relocated timed out: " + e.toString());
      }
  }
    }

    /**
     * Relocates this client's connection, if the server has instructed it
     * to do so via a RELOCATE_NOTIFICATION. <p>
     *
     * If this client has not yet received a RELOCATE_NOTIFICATION, it first
     * waits until one is received or the timeout expires, which ever comes
     * first.  If a RELOCATE_NOTIFICATION is not received or if the
     * specified {@code expectedPort} is non-zero and does not match the
     * relocation port, then {@code AssertionFailedError} is thrown. <p>
     *
     * If a RELOCATE_NOTIFICATION is correctly received, then this method
     * sends a RELOCATE_REQUEST message to the local host on the relocation
     * port received by a RELOCATE_NOTIFICATION. If {@code useValidKey} is
     * {@code true}, the current valid relocation key is used in the
     * relocate request, otherwise an invalid relocation key is used. <p>
     *
     * This method waits for an acknowledgment (either RELOCATE_SUCCESS or
     * RELOCATE_FAILURE).  If {@code shouldSucceed} is {@code true} and a
     * RELOCATE_FAILURE is received, this method throws {@code
     * AssertionFailedError}; similarly if {@code shouldSucceed} is {@code
     * false} and a RELOCATE_SUCCESS is received, {@code
     * AssertionFailedError} will be thrown.
     */
    public void relocate(int expectedPort, boolean useValidKey,
       boolean shouldSucceed)
    {
  checkRelocateProtocolVersion();
  synchronized (lock) {
      if (!relocateSession) {
    waitForRelocationNotification(expectedPort);
      } else {
    if (expectedPort != 0) {
        assertEquals(expectedPort, relocatePort);
    }
      }
  }
  System.err.println(toString() + " relocating...");
  disconnect();
  relocateAck = false;
  relocateSuccess = false;
  suspendMessages = false;
  connect(relocatePort);
  byte[] key = useValidKey ? relocateKey : new byte[0];
  ByteBuffer buf = ByteBuffer.allocate(2 + key.length);
  buf.put(SimpleSgsProtocol.RELOCATE_REQUEST).
      put(SimpleSgsProtocol.VERSION).
      put(key).
      flip();
  try {
      connection.sendBytes(buf.array());
  } catch (IOException e) {
      throw new RuntimeException(e);
  }
  synchronized (lock) {
      try {
    if (!relocateAck) {
        lock.wait(WAIT_TIME);
    }
    if (!relocateAck) {
        throw new RuntimeException(
      toString() + " relocate timed out");
    }
    if (shouldSucceed) {
        if (!relocateSuccess) {
      fail("relocation failed");
        }
    } else if (relocateSuccess) {
        fail("relocation succeeded");
    }
      } catch (InterruptedException e) {
    throw new RuntimeException(
        toString() + " relocate timed out", e);
      }
      relocateSession = false;
      relocateAck = false;
      relocatePort = 0;
      relocateSuccess = false;
  }
 
    }

    /**
     * Sends a SESSION_MESSAGE to the server with the specified content.
     * If {@code checkSuspend} is {@code true}, then, if the subclass
     * supports suspending messages, it should check to see if messages are
     * suspended before forwarding the message to the server.  The method
     * default implementation of {@link #sendRaw} will check if messages
     * are suspended.
     */
    public abstract void sendMessage(byte[] message, boolean checkSuspend);

    /**
     * Writes the specified {@code bytes} directly to the underlying
     * connection.  If {@code checkSuspended} is {@code true}, and messages
     * sending is currently suspended, then throw an {@code
     * IllegalStateException}.
     */
    protected void sendRaw(byte[] bytes, boolean checkSuspended) {
  synchronized (lock) {
      if (checkSuspended && suspendMessages) {
    throw new IllegalStateException("messages suspended");
      }
         
      try {
    connection.sendBytes(bytes);
      } catch (IOException e) {
    throw new RuntimeException(e);
      }
  }
    }

    /**
     * If this session is not connected, this method returns; otherwise
     * this method sends a LOGOUT_REQUEST to the server, and waits for
     * this client to receive a LOGOUT_SUCCESS acknowledgment or the
     * timeout to expire, whichever comes first.  If the LOGOUT_SUCCESS
     * acknowledgment is not received, then {@code AssertionFailedError}
     * is thrown.
     */
    public void logout() {
  synchronized (lock) {
      if (connected == false) {
    return;
      }
      logoutAck = false;
  }
  MessageBuffer buf = new MessageBuffer(1);
  buf.putByte(SimpleSgsProtocol.LOGOUT_REQUEST);
  try {
      connection.sendBytes(buf.getBuffer());
  } catch (IOException e) {
      throw new RuntimeException(e);
  }
  synchronized (lock) {
      try {
    if (logoutAck == false) {
        lock.wait(WAIT_TIME);
    }
    if (logoutAck != true) {
        fail(toString() + " logout timed out");
    }
      } catch (InterruptedException e) {
    fail(toString() + " logout timed out: " + e.toString());
      } finally {
    if (! logoutAck)
        disconnect();
      }
  }
    }

    /**
     * Disconnects this client, and returns either when the connection
     * is closed or the timeout expires, which ever comes first.
     */
    public void disconnect() {
  System.err.println(toString() + " disconnecting");

  synchronized (lock) {
      if (! connected) {
    return;
      }
      try {
    connection.close();
    lock.wait(WAIT_TIME);
      } catch (Exception e) {
    System.err.println(toString() + " disconnect exception:" + e);
    lock.notifyAll();
      } finally {
    if (connected) {
        reset();
    }
      }
  }
    }

    /**
     * Resets the connection state so that the client can connect and
     * login again.
     */
    private void reset() {
  assert Thread.holdsLock(lock);
  connected = false;
  connection = null;
  loginAck = false;
  loginSuccess = false;
  loginRedirect = false;
    }

    /**
     * Waits for the connection to close or the timeout to expire,
     * whichever comes first, and returns {@code true} if the
     * underlying connection disconnected, and {@code false} otherwise.
     */
    public boolean waitForDisconnect() {
  synchronized (lock) {
      try {
    if (connected) {
        lock.wait(WAIT_TIME);
    }
      } catch (InterruptedException ignore) {
      }
      return !connected;
  }
    }

    /**
     * Sets a flag that indicates this client should wait for a
     * SUSPEND_MESSAGES notification, instead of replying with a
     * SUSPEND_MESSAGES_COMPLETE ack to the server.  To wait for the
     * SUSPEND_MESSAGES notification, invoke the {@link
     * #waitForSuspendMessages} method.
     */
    public void setWaitForSuspendMessages() {
  checkRelocateProtocolVersion();
  synchronized (lock) {
      waitForSuspendMessages = true;
  }
    }

    /**
     * Waits for a SUSPEND_MESSAGES notification to be sent to this
     * client.  A previous call to {@link #setWaitForSuspendMessages} must
     * be invoked before the server sends a SUSPEND_MESSAGES notification
     * in order for this wait to be successful.  This method returns when a
     * SUSPEND_MESSAGES notification has been received; it does NOT allow a
     * SUSPEND_MESSAGES_COMPLETE ack to be sent.  To send the
     * SUSPEND_MESSAGES_COMPLETE ack, invoke the {@link
     * #sendSuspendMessagesComplete} method.
     */
    public void waitForSuspendMessages() {
  checkRelocateProtocolVersion()
  synchronized (lock) {
      try {
    if (!suspendMessages) {
        lock.wait(WAIT_TIME);
    }
      } catch (InterruptedException ignore) {
      }
      if (!suspendMessages) {
    fail(toString() +
         " time out waiting for SUSPEND_MESSAGES");
      }
  }
    }

    /**
     * Sends a SUSPEND_MESSAGES_COMPLETE ack to the server.
     */
    public void sendSuspendMessagesComplete() {
  checkRelocateProtocolVersion();
  synchronized (lock) {
      ByteBuffer msg = ByteBuffer.allocate(1);
      msg.put(SimpleSgsProtocol.SUSPEND_MESSAGES_COMPLETE).
    flip();
      try {
    connection.sendBytes(msg.array());
      } catch (IOException e) {
    throw new RuntimeException(e);
      }
  }
    }

    /**
     * Gives a subclass a chance to handle an {@code opcode}.  This
     * implementation handles login, redirection, relocation, and
     * logout but does not handle session or channel messages.
     */
    protected void handleOpCode(byte opcode, MessageBuffer buf) {

  switch (opcode) {
      case SimpleSgsProtocol.LOGIN_SUCCESS:
    reconnectKey = buf.getBytes(buf.limit() - buf.position());
    newReconnectKey(reconnectKey);
    synchronized (lock) {
        loginAck = true;
        loginSuccess = true;
        System.err.println("login succeeded: " + name);
        lock.notifyAll();
    }
    sendMessage(new byte[0], true);
    break;
       
      case SimpleSgsProtocol.LOGIN_FAILURE:
    String failureReason = buf.getString();
    synchronized (lock) {
        loginAck = true;
        loginSuccess = false;
        System.err.println("login failed: " + name +
               ", reason:" + failureReason);
        lock.notifyAll();
    }
    break;

      case SimpleSgsProtocol.LOGIN_REDIRECT:
    redirectHost = buf.getString();
    redirectPort = buf.getInt();
    synchronized (lock) {
        loginAck = true;
        loginRedirect = true;
        System.err.println("login redirected: " + name +
               ", host:" + redirectHost +
               ", port:" + redirectPort);
        lock.notifyAll();
    }
    break;

      case SimpleSgsProtocol.SUSPEND_MESSAGES:
    checkRelocateProtocolVersion();
    synchronized (lock) {
        if (suspendMessages) {
      break;
        }
        suspendMessages = true;
        if (waitForSuspendMessages) {
      waitForSuspendMessages = false;
      lock.notifyAll();
        } else {
      sendSuspendMessagesComplete();
        }
    }
    break;
   
       
      case SimpleSgsProtocol.RELOCATE_NOTIFICATION:
    checkRelocateProtocolVersion();
    relocateHost = buf.getString();
    relocatePort = buf.getInt();
    relocateKey = buf.getBytes(buf.limit() - buf.position());
    synchronized (lock) {
        relocateSession = true;
        System.err.println(
      "session to relocate: " + name +
      ", host:" + relocateHost +
      ", port:" + relocatePort +
      ", key:" + HexDumper.toHexString(relocateKey));
        lock.notifyAll();
    }
    break;

      case SimpleSgsProtocol.RELOCATE_SUCCESS:
    checkRelocateProtocolVersion();
    reconnectKey = buf.getBytes(buf.limit() - buf.position());
    newReconnectKey(reconnectKey);
    synchronized (lock) {
        relocateAck = true;
        relocateSuccess = true;
        System.err.println("relocate succeeded: " + name);
        lock.notifyAll();
    }
    sendMessage(new byte[0], true);
    break;
       
      case SimpleSgsProtocol.RELOCATE_FAILURE:
    checkRelocateProtocolVersion();
    String relocateFailureReason = buf.getString();
    synchronized (lock) {
        relocateAck = true;
        relocateSuccess = false;
        System.err.println("relocate failed: " + name +
               ", reason:" + relocateFailureReason);
        lock.notifyAll();
    }
    break;
       
      case SimpleSgsProtocol.LOGOUT_SUCCESS:
    synchronized (lock) {
        logoutAck = true;
        System.err.println("logout succeeded: " + name);
        lock.notifyAll();
    }
    break;

      default:
    System.err.println(
        "WARNING: [" + name + "] dropping unknown op code: " +
        String.format("%02x", opcode));
    break;
  }
    }

    /** {@inheritDoc} */
    public String toString() {
  return "[" + name + "]";
    }

    /**
     * Throws an {@code AssertionFailedError} if this client's protocol
     * version does not support suspend or relocate.
     */
    public void checkRelocateProtocolVersion() {
  if (protocolVersion < RELOCATE_PROTOCOL_VERSION) {
      String badVersion =
    toString() + " Protocol version: " + protocolVersion +
    " does not support suspend or relocate";
      System.err.println(badVersion);
      fail(badVersion);
  }
    }

    /**
     * ConnectionListener for connection I/O events.
     */
    private class Listener implements ConnectionListener {

  /** {@inheritDoc} */
  public void bytesReceived(Connection conn, byte[] buffer) {
      if (connection != conn) {
    System.err.println(
        toString() + "AbstractDummyClient.Listener.bytesReceived:" +
        " wrong handle, got:" + conn + ", expected:" + connection);
    return;
      }

      MessageBuffer buf = new MessageBuffer(buffer);
      byte opcode = buf.getByte();
     
      handleOpCode(opcode, buf);
  }

  /** {@inheritDoc} */
  public void connected(Connection conn) {
      System.err.println(
    AbstractDummyClient.this.toString() + " connected to port:" +
             connectPort);
      if (connection != null) {
    System.err.println(
        "DummyClient.Listener.already connected handle: " +
        connection);
    return;
      }
      connection = conn;
      synchronized (lock) {
    connected = true;
    lock.notifyAll();
      }
  }

  /** {@inheritDoc} */
  public void disconnected(Connection conn) {
      synchronized (lock) {
    reset();
    lock.notifyAll();
      }
  }
     
  /** {@inheritDoc} */
  public void exceptionThrown(Connection conn, Throwable exception) {
      System.err.println("DummyClient.Listener.exceptionThrown " +
             "exception:" + exception);
      exception.printStackTrace();
  }
    }
}
TOP

Related Classes of com.sun.sgs.test.util.AbstractDummyClient$Listener

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.