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

Source Code of com.sun.sgs.test.impl.service.session.TestClientSessionServiceImplv4$DummyClient

/*
* 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.impl.service.session;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.Delivery;
import com.sun.sgs.app.ExceptionRetryStatus;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.MessageRejectedException;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.util.ManagedSerializable;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.impl.kernel.StandardProperties;
import com.sun.sgs.impl.service.session.ClientSessionServer;
import com.sun.sgs.impl.service.session.ClientSessionWrapper;
import com.sun.sgs.impl.sharedutil.HexDumper;
import com.sun.sgs.impl.sharedutil.MessageBuffer;
import com.sun.sgs.kernel.TransactionScheduler;
import com.sun.sgs.protocol.simple.SimpleSgsProtocol;
import com.sun.sgs.service.DataService;
import com.sun.sgs.service.Node.Health;
import com.sun.sgs.service.WatchdogService;
import com.sun.sgs.test.util.AbstractDummyClient;
import com.sun.sgs.test.util.ConfigurableNodePolicy;
import com.sun.sgs.test.util.SgsTestNode;
import com.sun.sgs.test.util.SimpleTestIdentityAuthenticator;
import com.sun.sgs.test.util.TestAbstractKernelRunnable;
import com.sun.sgs.tools.test.FilteredNameRunner;
import com.sun.sgs.tools.test.IntegrationTest;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static com.sun.sgs.test.util.UtilProperties.createProperties;

@RunWith(FilteredNameRunner.class)
public class TestClientSessionServiceImplv4 extends Assert {


    private static final String APP_NAME = "TestClientSessionServiceImplv4";
   
    private static final byte PROTOCOL_v4 = 0x04;

    protected static final int WAIT_TIME = 5000;

    private static final String RETURN_NULL = "return null";

    private static final String NON_SERIALIZABLE = "non-serializable";

    private static final String THROW_RUNTIME_EXCEPTION =
  "throw RuntimeException";

    private static final String DISCONNECT_THROWS_NONRETRYABLE_EXCEPTION =
  "disconnect throws non-retryable exception";

    private static final String SESSION_PREFIX =
  "com.sun.sgs.impl.service.session.impl";

    private static final String SESSION_NODE_PREFIX =
  "com.sun.sgs.impl.service.session.node";

    private static final String LISTENER_PREFIX =
  "com.sun.sgs.impl.service.session.listener";

    private static final String NODE_PREFIX =
  "com.sun.sgs.impl.service.watchdog.node";

    private String appName;

    /** The node that creates the servers. */
    protected SgsTestNode serverNode;

    /** The protocol version. */
    private byte protocolVersion;

    /** Any additional nodes, keyed by node host name (for tests
     * needing more than one node). */
    protected Map<String,SgsTestNode> additionalNodes;

    protected boolean allowNewLogin = false;

    /** The transaction scheduler. */
    private TransactionScheduler txnScheduler;

    /** The owner for tasks I initiate. */
    private Identity taskOwner;

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

    /** The test clients, keyed by client session ID. */
    private static Map<BigInteger, DummyClient> dummyClients;

    private static volatile RuntimeException receivedMessageException = null;
   
    /** Constructs a test instance. */
    public TestClientSessionServiceImplv4() throws Exception {
    }

    @Before
    public void setUp() throws Exception {
        setUp(null, true, APP_NAME, PROTOCOL_v4);
    }

    /** Creates and configures the session service. */
    protected void setUp(Properties props, boolean clean, String appName,
       byte protocolVersion)
  throws Exception
    {
  if (props == null) {
      props =
                SgsTestNode.getDefaultProperties(appName, null,
             DummyAppListener.class);
  }
  this.protocolVersion = protocolVersion;
  this.appName = appName;
        props.setProperty(StandardProperties.AUTHENTICATORS,
                      "com.sun.sgs.test.util.SimpleTestIdentityAuthenticator");
  props.setProperty("com.sun.sgs.impl.service.nodemap.policy.class",
        ConfigurableNodePolicy.class.getName());
  props.setProperty(
      "com.sun.sgs.impl.service.watchdog.server.renew.interval", "1000");
  props.setProperty(
            "com.sun.sgs.impl.service.nodemap.remove.expire.time", "5000");
  props.setProperty(
      "com.sun.sgs.impl.protocol.simple.protocol.version",
     Byte.toString(protocolVersion));
  props.setProperty(
      StandardProperties.SESSION_RELOCATION_TIMEOUT_PROPERTY,
      "5000");
 
  serverNode =
      new SgsTestNode(appName, DummyAppListener.class, props, clean);

        txnScheduler =
            serverNode.getSystemRegistry().
            getComponent(TransactionScheduler.class);
        taskOwner = serverNode.getProxy().getCurrentOwner();

        dataService = serverNode.getDataService();
        dummyClients = new HashMap<BigInteger, DummyClient>();
  receivedMessageException = null;
    }

    /**
     * Add additional nodes.  We only do this as required by the tests.
     *
     * @param hosts contains a host name for each additional node
     */
    protected void addNodes(String... hosts) throws Exception {
        // Create the other nodes
  if (additionalNodes == null) {
      additionalNodes = new HashMap<String, SgsTestNode>();
  }

        for (String host : hosts) {
            Properties props = SgsTestNode.getDefaultProperties(
                appName, serverNode, DummyAppListener.class);
      props.setProperty(StandardProperties.AUTHENTICATORS,
                "com.sun.sgs.test.util.SimpleTestIdentityAuthenticator");
            props.put("com.sun.sgs.impl.service.watchdog.client.host", host);
      if (allowNewLogin) {
    props.setProperty(
        "com.sun.sgs.impl.service.session.allow.new.login", "true");
      }
      props.setProperty(
    "com.sun.sgs.impl.protocol.simple.protocol.version",
    Byte.toString(protocolVersion));
      props.setProperty(
          StandardProperties.SESSION_RELOCATION_TIMEOUT_PROPERTY,
    "5000");
            SgsTestNode node =
                    new SgsTestNode(serverNode, DummyAppListener.class, props);
            additionalNodes.put(host, node);
        }
    }

    @After
    public void tearDown() throws Exception {
        tearDown(true);
    }

    protected void tearDown(boolean clean) throws Exception {
        Thread.sleep(100);
  if (additionalNodes != null) {
            for (SgsTestNode node : additionalNodes.values()) {
                node.shutdown(false);
            }
            additionalNodes = null;
        }
        serverNode.shutdown(clean);
        serverNode = null;
    }

    // -- Test connecting, logging in, logging out with server --

    @Test
    public void testConnection() throws Exception {
  DummyClient client = createDummyClient("foo");
  try {
      client.connect(serverNode.getAppPort());
  } catch (Exception e) {
      System.err.println("Exception: " + e);
      Throwable t = e.getCause();
      System.err.println("caused by: " + t);
      System.err.println("detail message: " + t.getMessage());
      throw e;
     
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testLoginSuccess() throws Exception {
  DummyClient client = createDummyClient("success");
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
  } finally {
            client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoginRedirect() throws Exception {
  int serverAppPort = serverNode.getAppPort();
  String[] users = new String[] { "sleepy", "bashful", "dopey", "doc" };
  Set<DummyClient> clients = new HashSet<DummyClient>();
  addNodes(users);
  boolean failed = false;
  int redirectCount = 0;
  ConfigurableNodePolicy.setRoundRobinPolicy();
  try {
      for (String user : users) {
    DummyClient client = createDummyClient(user);
    client.connect(serverAppPort);
    if (client.login()) {
        if (client.getConnectPort() != serverAppPort) {
      // login redirected
      redirectCount++;
        }
    } else {
        failed = true;
        System.err.println("login for user: " + user + " failed");
    }
    clients.add(client);
      }
     
      int expectedRedirects = users.length - 1;
      if (redirectCount != expectedRedirects) {
    failed = true;
    System.err.println("Expected " + expectedRedirects +
           " redirects, got " + redirectCount);
      } else {
    System.err.println(
        "Number of redirected users: " + redirectCount);
      }
     
      if (failed) {
    fail("test failed (see output)");
      }
     
  } finally {
      for (DummyClient client : clients) {
    try {
        client.disconnect();
    } catch (Exception e) {
        System.err.println(
      "Exception disconnecting client: " + client);
    }
      }
  }
 
    }

    @Test
    @IntegrationTest
    public void testSendBeforeLoginComplete() throws Exception {
  DummyClient client = createDummyClient("dummy");
  try {
      client.connect(serverNode.getAppPort());
      client.login(false);
      client.sendMessagesFromClientInSequence(1, 0);
  } finally {
            client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testSendAfterLoginComplete() throws Exception {
  DummyClient client = createDummyClient("dummy");
  try {
      client.connect(serverNode.getAppPort());
      client.login(true);
      client.sendMessagesFromClientInSequence(1, 1);
  } finally {
            client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoginSuccessAndNotifyLoggedInCallback() throws Exception {
  String name = "success";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      if (SimpleTestIdentityAuthenticator.allIdentities.
                    getNotifyLoggedIn(name)) {
    System.err.println(
        "notifyLoggedIn invoked for identity: " + name);
      } else {
    fail("notifyLoggedIn not invoked for identity: " + name);
      }
  } finally {
            client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoggedInReturningNonSerializableClientSessionListener()
  throws Exception
    {
  DummyClient client = createDummyClient(NON_SERIALIZABLE);
  try {
      client.connect(serverNode.getAppPort());
      assertFalse(client.login());
      assertFalse(SimpleTestIdentityAuthenticator.allIdentities.
      getNotifyLoggedIn(NON_SERIALIZABLE));
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoggedInReturningNullClientSessionListener()
  throws Exception
    {
  DummyClient client = createDummyClient(RETURN_NULL);
  try {
      client.connect(serverNode.getAppPort());
      assertFalse(client.login());
      assertFalse(SimpleTestIdentityAuthenticator.allIdentities.
            getNotifyLoggedIn(RETURN_NULL));
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoggedInThrowingRuntimeException()
  throws Exception
    {
  DummyClient client = createDummyClient(THROW_RUNTIME_EXCEPTION);
  try {
      client.connect(serverNode.getAppPort());
      assertFalse(client.login());
      assertFalse(SimpleTestIdentityAuthenticator.allIdentities.
      getNotifyLoggedIn(THROW_RUNTIME_EXCEPTION));
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoginTwiceBlockUser() throws Exception {
  String name = "dummy";
  DummyClient client1 = createDummyClient(name);
  DummyClient client2 = createDummyClient(name);
  int port = serverNode.getAppPort();
  try {
      assertTrue(client1.connect(port).login());
      assertFalse(client2.connect(port).login());
  } finally {
      client1.disconnect();
      client2.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLoginTwicePreemptUser() throws Exception {
  // Set up ClientSessionService to preempt user if same user logs in
  tearDown(false);
  Properties props =
      SgsTestNode.getDefaultProperties(appName, null,
               DummyAppListener.class);
  props.setProperty(
       "com.sun.sgs.impl.service.session.allow.new.login", "true");
  setUp(props, false, appName, protocolVersion);
  String name = "dummy";
 
  DummyClient client1 = createDummyClient(name);
  DummyClient client2 = createDummyClient(name);
  int port = serverNode.getAppPort();
  try {
      assertTrue(client1.connect(port).login());
      Thread.sleep(100);
      assertTrue(client2.connect(port).login());
      client1.checkDisconnectedCallback(false);
      assertTrue(client2.isConnected());
     
  } finally {
      client1.disconnect();
      client2.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testHighWater() throws Exception {
  // Set up ClientSessionService with login high water set
  tearDown(false);
  Properties props =
      SgsTestNode.getDefaultProperties(appName, null, DummyAppListener.class);
  props.setProperty("com.sun.sgs.impl.service.session.login.high.water", "2");
  setUp(props, false, appName, protocolVersion);

  DummyClient client1 = createDummyClient("client1");
  DummyClient client2 = createDummyClient("client2");
  int port = serverNode.getAppPort();
        WatchdogService watchdog = serverNode.getWatchdogService();
  try {
      client1.connect(port).login();
      Thread.sleep(100);
            assertTrue(client1.isConnected());
            assertEquals(Health.GREEN, watchdog.getLocalNodeHealthNonTransactional());
      client2.connect(port).login();
      Thread.sleep(100);
            assertTrue(client2.isConnected());
            assertEquals(Health.YELLOW, watchdog.getLocalNodeHealthNonTransactional());
            client1.disconnect();
      Thread.sleep(100);
            assertFalse(client1.isConnected());
            assertEquals(Health.GREEN, watchdog.getLocalNodeHealthNonTransactional());
  } finally {
      client1.disconnect();
      client2.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testDisconnectFromServerAfterLogout() throws Exception {
  final String name = "logout";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      client.logout();
      assertTrue(client.isConnected());
      assertTrue(client.waitForDisconnect());
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testDisconnectedCallbackAfterClientDropsConnection()
  throws Exception
    {
  final String name = "dummy";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      checkBindings(1);
      client.disconnect();
      client.checkDisconnectedCallback(false);
      checkBindings(0);
      // check that client session was removed after disconnected callback
      // returned
            txnScheduler.runTask(new TestAbstractKernelRunnable() {
                public void run() {
        try {
      dataService.getBinding(name);
      fail("expected ObjectNotFoundException: " +
           "object not removed");
        } catch (ObjectNotFoundException e) {
        }
                }
             }, taskOwner);
  } catch (InterruptedException e) {
      e.printStackTrace();
      fail("testLogout interrupted");
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLogoutRequestAndDisconnectedCallback() throws Exception {
  final String name = "logout";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      checkBindings(1);
      client.logout();
      client.checkDisconnectedCallback(true);
      checkBindings(0);
      // check that client session was removed after disconnected callback
      // returned
            txnScheduler.runTask(new TestAbstractKernelRunnable() {
                public void run() {
        try {
      dataService.getBinding(name);
      fail("expected ObjectNotFoundException: " +
           "object not removed");
        } catch (ObjectNotFoundException e) {
        }
                }
             }, taskOwner);
  } catch (InterruptedException e) {
      e.printStackTrace();
      fail("testLogout interrupted");
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testDisconnectedCallbackThrowingNonRetryableException()
  throws Exception
    {
  DummyClient client =
      createDummyClient(DISCONNECT_THROWS_NONRETRYABLE_EXCEPTION);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      checkBindings(1);
      client.logout();
      client.checkDisconnectedCallback(true);
      // give scheduled task a chance to clean up...
      Thread.sleep(250);
      checkBindings(0);     
  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLogoutAndNotifyLoggedOutCallback() throws Exception {
  String name = "logout";
  DummyClient client = createDummyClient(name);
  try {
      assertTrue(client.connect(serverNode.getAppPort()).login());
      client.logout();
      if (SimpleTestIdentityAuthenticator.allIdentities.
                    getNotifyLoggedIn(name)) {
    System.err.println(
        "notifyLoggedIn invoked for identity: " + name);
      } else {
    fail("notifyLoggedIn not invoked for identity: " + name);
      }
      if (SimpleTestIdentityAuthenticator.allIdentities.
                    getNotifyLoggedOut(name)) {
    System.err.println(
        "notifyLoggedOut invoked for identity: " + name);
      } else {
    fail("notifyLoggedOut not invoked for identity: " + name);
      }
  } finally {
            client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testNotifyClientSessionListenerAfterCrash() throws Exception {
  int numClients = 4;
  try {
      List<String> nodeKeys = getServiceBindingKeys(NODE_PREFIX);
      System.err.println("Node keys: " + nodeKeys);
      if (nodeKeys.isEmpty()) {
    fail("no node keys");
      } else if (nodeKeys.size() > 1) {
    fail("more than one node key");
      }

      int appPort = serverNode.getAppPort();
      for (int i = 0; i < numClients; i++) {
     // Create half of the clients with a name that starts with
     // "badClient" which will cause the associated session's
     // ClientSessionListener's 'disconnected' method to throw a
     // non-retryable exception.  We want to make sure that all the
     // client sessions are cleaned up after a crash, even if
     // invoking a session's listener's 'disconnected' callback
     // throws a non-retryable exception.
    String name = (i % 2 == 0) ? "client" : "badClient";
    DummyClient client = createDummyClient(name + String.valueOf(i));
    assertTrue(client.connect(appPort).login());
      }
      checkBindings(numClients);

            // Simulate "crash"
            tearDown(false);
      String failedNodeKey = nodeKeys.get(0);
            setUp(null, false, appName, protocolVersion);

      for (DummyClient client : dummyClients.values()) {
    client.checkDisconnectedCallback(false);
      }
     
      // Wait to make sure that bindings and node key are cleaned up.
      // Some extra time is needed when a ClientSessionListener throws a
      // non-retryable exception because a separate task is scheduled to
      // clean up the client session and bindings.
      Thread.sleep(WAIT_TIME);
     
      System.err.println("check for session bindings being removed.");
      checkBindings(0);
      nodeKeys = getServiceBindingKeys(NODE_PREFIX);
      System.err.println("Node keys: " + nodeKeys);
      if (nodeKeys.contains(failedNodeKey)) {
    fail("failed node key not removed: " + failedNodeKey);
      }
     
  } finally {
      for (DummyClient client : dummyClients.values()) {
    try {
        client.disconnect();
    } catch (Exception e) {
        // ignore
    }
      }
  }
    }

    // -- test ClientSession --

    @Test
    public void testClientSessionIsConnected() throws Exception {
  DummyClient client = createDummyClient("clientname");
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
            txnScheduler.runTask(new TestAbstractKernelRunnable() {
                public void run() {
                    DummyAppListener appListener = getAppListener();
                    Set<ClientSession> sessions = appListener.getSessions();
                    if (sessions.isEmpty()) {
                        fail("appListener contains no client sessions!");
                    }
                    for (ClientSession session : appListener.getSessions()) {
                        if (session.isConnected() == true) {
                            System.err.println("session is connected");
                            return;
                        } else {
                            fail("Expected connected session: " + session);
                        }
                    }
                    fail("expected a connected session");
                }
            }, taskOwner);
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testClientSessionGetName() throws Exception {
  final String name = "clientname";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
            txnScheduler.runTask(new TestAbstractKernelRunnable() {
                public void run() {
                    DummyAppListener appListener = getAppListener();
                    Set<ClientSession> sessions = appListener.getSessions();
                    if (sessions.isEmpty()) {
                        fail("appListener contains no client sessions!");
                    }
                    for (ClientSession session : appListener.getSessions()) {
                        if (session.getName().equals(name)) {
                            System.err.println("names match");
                            return;
                        } else {
                            fail("Expected session name: " + name +
                                 ", got: " + session.getName());
                        }
                    }
                    fail("expected disconnected session");
                }
             }, taskOwner);
  } finally {
      client.disconnect();
  }
    }
   
    @Test
    public void testClientSessionRemoveDoesNotRemoveManagedListener()
  throws Exception
    {
  testClientSessionRemoveWithManagedListener(false);
    }
   
    @Test
    public void testClientSessionRemoveObjectAfterRemovingManagedListener()
  throws Exception
    {
  testClientSessionRemoveWithManagedListener(true);
    }

    private void testClientSessionRemoveWithManagedListener(
  final boolean removeListener)
  throws Exception
    {
  final String name = "testClient";
  int objectCount = getObjectCount();
  DummyClient client = createDummyClient(name);
  try {
      assertTrue(client.connect(serverNode.getAppPort()).login());
      System.err.println("Objects added: " +
             (getObjectCount() - objectCount));
      txnScheduler.runTask(new TestAbstractKernelRunnable() {
        public void run() {
      System.err.println(
          "forcing disconnection, session:" + name);
      ClientSession session = (ClientSession)
          dataService.getBinding(name);
      DummyClientSessionListener listener =
          (DummyClientSessionListener)
          dataService.getBinding(name + ".listener");
      if (removeListener) {
          dataService.removeObject(listener);
      }
      dataService.removeObject(session);
        }
    }, taskOwner);
      Thread.sleep(500);
      assertEquals(objectCount + (removeListener ? 0 : 1),
       getObjectCount());
  } finally {
      client.disconnect();
  }
    }
   
    @Test
    public void testClientSessionToString() throws Exception {
  final String name = "testClient";
  DummyClient client = createDummyClient(name);
  try {
      assertTrue(client.connect(serverNode.getAppPort()).login());
      txnScheduler.runTask(new TestAbstractKernelRunnable() {
        public void run() {
      ClientSession session = (ClientSession)
          dataService.getBinding(name);
      if (!(session instanceof ClientSessionWrapper)) {
          fail("session not instance of " +
         "ClientSessionWrapper");
      }
      System.err.println("session: " + session);
        }
    }, taskOwner);
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testClientSessionToStringNoTransaction() throws Exception {
  final String name = "testClient";
  DummyClient client = createDummyClient(name);
  try {
      assertTrue(client.connect(serverNode.getAppPort()).login());
      GetClientSessionTask task = new GetClientSessionTask(name);
      txnScheduler.runTask(task, taskOwner);
      try {
    System.err.println("session: " + task.session.toString());
    return;
      } catch (Exception e) {
    e.printStackTrace();
    fail("unexpected exception in ClientSessionWrapper.toString");
      }
  } finally {
      client.disconnect();
  }
    }

    private class GetClientSessionTask extends TestAbstractKernelRunnable {
  private final String name;
  volatile ClientSession session;

  GetClientSessionTask(String name) {
      this.name = name;
  }

  public void run() {
      session = (ClientSession) dataService.getBinding(name);
      if (!(session instanceof ClientSessionWrapper)) {
    fail("session not instance of ClientSessionWrapper");
      }
  }
    }

    @Test
    public void testClientSessionSendUnreliableMessages() throws Exception {
  DummyClient client = createDummyClient("dummy");
  try {
      int iterations = 3;
      int numAdditionalNodes = 2;
      sendMessagesFromNodesToClient(
    client, numAdditionalNodes, iterations, Delivery.UNRELIABLE,
    false);
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testClientSessionSendUnreliableMessagesWithFailure()
  throws Exception
    {
  DummyClient client = createDummyClient("dummy");
  try {
      int iterations = 3;
      int numAdditionalNodes = 2;
      sendMessagesFromNodesToClient(
    client, numAdditionalNodes, iterations, Delivery.UNRELIABLE,
    true);
  } finally {
      client.disconnect();
  }
 
    }

    @Test
    public void testClientSessionSendSequence() throws Exception {
  DummyClient client = createDummyClient("dummy");
  try {
      int iterations = 3;
      int numAdditionalNodes = 2;
      sendMessagesFromNodesToClient(
    client, numAdditionalNodes, iterations, Delivery.RELIABLE,
    false);
      int numExpectedMessages = (1 + numAdditionalNodes) * iterations;
      client.validateMessageSequence(
    client.clientReceivedMessages, numExpectedMessages, 0);
  } finally {
      client.disconnect();
  }
    }
   
    protected void sendMessagesFromNodesToClient(
      final DummyClient client, int numAdditionalNodes, int iterations,
      final Delivery delivery, final boolean oneUnreliableServer)
  throws Exception
    {
      final String counterName = "counter";
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      for (int i = 0; i < numAdditionalNodes; i++) {
    addNodes(Integer.toString(i));
      }
     
      final List<SgsTestNode> nodes = new ArrayList<SgsTestNode>();
      nodes.add(serverNode);
      nodes.addAll(additionalNodes.values());
      int numExpectedMessages =
    oneUnreliableServer ?
    iterations :
    nodes.size() * iterations;
     
      // Replace each node's ClientSessionServer, bound in the data
      // service, with a wrapped server that delays before sending
      // the message.
      txnScheduler.runTask(new TestAbstractKernelRunnable() {
    @SuppressWarnings("unchecked")
    public void run() {
        boolean setUnreliableServer = oneUnreliableServer;
        for (SgsTestNode node : nodes) {
      String key = "com.sun.sgs.impl.service.session.server." +
          node.getNodeId();
      ClientSessionServer sessionServer =
          ((ManagedSerializable<ClientSessionServer>)
           dataService.getServiceBinding(key)).get();
      InvocationHandler handler;
      if (setUnreliableServer) {
          handler = new HungryInvocationHandler(sessionServer);
          setUnreliableServer = false;
      } else {
          handler =
        new DelayingInvocationHandler(sessionServer);
      }
      ClientSessionServer delayingServer =
          (ClientSessionServer)
          Proxy.newProxyInstance(
        ClientSessionServer.class.getClassLoader(),
        new Class[] { ClientSessionServer.class },
        handler);
      dataService.setServiceBinding(
          key, new ManagedSerializable(delayingServer));
        }
    }}, taskOwner);

      for (int i = 0; i < iterations; i++) {
    for (SgsTestNode node : nodes) {
        TransactionScheduler localTxnScheduler =
      node.getSystemRegistry().
          getComponent(TransactionScheduler.class);
        Identity identity = node.getProxy().getCurrentOwner();
        localTxnScheduler.scheduleTask(
            new TestAbstractKernelRunnable() {
          public void run() {
        DataManager dataManager =
            AppContext.getDataManager();
        Counter counter;
        try {
            counter = (Counter)
          dataManager.getBinding(counterName);
        } catch (NameNotBoundException e) {
            throw new MaybeRetryException("retry", true);
        }
        ClientSession session = (ClientSession)
            dataManager.getBinding(client.name);
        MessageBuffer buf = new MessageBuffer(4);
        buf.putInt(counter.getAndIncrement());
        session.send(ByteBuffer.wrap(buf.getBuffer()),
               delivery);
          }},
     
      identity);
    }
      }
      txnScheduler.runTask(new TestAbstractKernelRunnable() {
    public void run() {
        AppContext.getDataManager().
      setBinding(counterName, new Counter());
    }}, taskOwner);
     
      client.waitForClientToReceiveExpectedMessages(numExpectedMessages);

    }

    @Test
    public void testClientSessionSendNullMessage() throws Exception {
  try {
      sendBufferToClient(null, null);
      fail("Expected NullPointerException");
  } catch (NullPointerException e) {
      System.err.println(e);
  }
    }

    @Test
    public void testClientSessionSendNullDelivery() throws Exception {
  try {
      sendBufferToClient(ByteBuffer.wrap(new byte[0]), "", null);
      fail("Expected NullPointerException");
  } catch (NullPointerException e) {
      System.err.println(e);
  }
    }

    @Test
    public void testClientSessionSendSameBuffer() throws Exception {
  String msgString = "buffer";
  MessageBuffer msg =
      new MessageBuffer(MessageBuffer.getSize(msgString));
  msg.putString(msgString);
  ByteBuffer buf = ByteBuffer.wrap(msg.getBuffer());
  sendBufferToClient(buf, msgString);
    }

    @Test
    public void testClientSessionSendSameBufferWithOffset()
  throws Exception
    {
  String msgString = "offset buffer";
  MessageBuffer msg =
      new MessageBuffer(MessageBuffer.getSize(msgString) + 1);
  msg.putByte(0);
  msg.putString(msgString);
  ByteBuffer buf = ByteBuffer.wrap(msg.getBuffer());
  buf.position(1);
  sendBufferToClient(buf, msgString);
    }

    private void sendBufferToClient(final ByteBuffer buf,
            final String expectedMsgString)
  throws Exception
    {
  sendBufferToClient(buf, expectedMsgString, Delivery.RELIABLE);
    }
 
    private void sendBufferToClient(final ByteBuffer buf,
            final String expectedMsgString,
            final Delivery delivery)
  throws Exception
   
  final String name = "dummy";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      final int numMessages = 3;
      for (int i = 0; i < numMessages; i++) {
    txnScheduler.runTask(new TestAbstractKernelRunnable() {
      public void run() {
          ClientSession session = (ClientSession)
        AppContext.getDataManager().getBinding(name);
          System.err.println("Sending messages");
          session.send(buf, delivery);
      } }, taskOwner);
      }
 
      System.err.println("waiting for client to receive messages");
      client.waitForClientToReceiveExpectedMessages(numMessages);
      for (byte[] message : client.clientReceivedMessages) {
    if (message.length == 0) {
        fail("message buffer emtpy");
    }
    String msgString = (new MessageBuffer(message)).getString();
    if (!msgString.equals(expectedMsgString)) {
        fail("expected: " + expectedMsgString + ", received: " +
       msgString);
    } else {
        System.err.println("received expected message: " +
               msgString);
    }
      }
  } finally {
      client.disconnect();
  }
    }
   
    private static class Counter implements ManagedObject, Serializable {
  private static final long serialVersionUID = 1L;

  private int value = 0;

  int getAndIncrement() {
      AppContext.getDataManager().markForUpdate(this);
      return value++;
  }
    }

    // Test sending from the server to the client session in a transaction that
    // aborts with a retryable exception to make sure that message buffers are
    // reclaimed.  Try sending 4K bytes, and have the task abort 100 times with
    // a retryable exception so the task is retried.  If the buffers are not
    // being reclaimed then the sends will eventually fail because the buffer
    // space is used up.  Note that this test assumes that sending 400 KB of
    // data will surpass the I/O throttling limit.
    @Test
    public void testClientSessionSendAbortRetryable() throws Exception {
  DummyClient client = createDummyClient("clientname");
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      txnScheduler.runTask(
    new TestAbstractKernelRunnable() {
        int tryCount = 0;
        public void run() {
      Set<ClientSession> sessions =
          getAppListener().getSessions();
      ClientSession session = sessions.iterator().next();
      try {
          session.send(ByteBuffer.wrap(new byte[4096]));
      } catch (MessageRejectedException e) {
          fail("Should not run out of buffer space: " + e);
      }
      if (++tryCount < 100) {
          throw new MaybeRetryException("Retryable"true);
      }
        }
    }, taskOwner);
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testClientSend() throws Exception {
  String name = "clientname";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      client.sendMessagesFromClientInSequence(5, 5);
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testClientSendWithListenerThrowingRetryableException()
  throws Exception
    {
  String name = "clientname";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      receivedMessageException =
    new MaybeRetryException("retryable", true);
      client.sendMessagesFromClientInSequence(5, 5);
  } finally {
      client.disconnect();
  }
    }

    @Test
    public void testClientSendWithListenerThrowingNonRetryableException()
  throws Exception
    {
  String name = "clientname";
  DummyClient client = createDummyClient(name);
  try {
      client.connect(serverNode.getAppPort());
      assertTrue(client.login());
      receivedMessageException =
    new MaybeRetryException("non-retryable", false);
      int numMessages = 5;
      for (int i = 0; i < numMessages; i++) {
    ByteBuffer buf = ByteBuffer.allocate(4);
    buf.putInt(i).flip();
    client.sendMessage(buf.array(), true);
      }
      client.waitForSessionListenerToReceiveExpectedMessages(
    numMessages - 1);
      client.validateMessageSequence(
    client.sessionListenerReceivedMessages, numMessages - 1,
    1);

  } finally {
      client.disconnect();
  }
    }

    @Test
    @IntegrationTest
    public void testLocalSendPerformance() throws Exception {
  final String user = "dummy";
  DummyClient client = createDummyClient(user);
  assertTrue(client.connect(serverNode.getAppPort()).login());

  int numIterations = 10;
  final ByteBuffer msg = ByteBuffer.allocate(0);
  long startTime = System.currentTimeMillis();
  for (int i = 0; i < numIterations; i++) {
      txnScheduler.runTask(new TestAbstractKernelRunnable() {
    public void run() {
        DataManager dataManager = AppContext.getDataManager();
        ClientSession session = (ClientSession)
      dataManager.getBinding(user);
        session.send(msg);
    }}, taskOwner);
  }
  long endTime = System.currentTimeMillis();
  System.err.println("send, iterations: " + numIterations +
         ", elapsed time: " + (endTime - startTime) +
         " ms.");
    }

    @Test
    public void testRemoveSessionWhileSessionDisconnects() throws Exception {
  final String user = "foo";
  DummyClient client = createDummyClient(user);
  assertTrue(client.connect(serverNode.getAppPort()).login());
  client.sendMessage(new byte[0], true);
  client.logout();
  client.checkDisconnectedCallback(true);
    }

    /* -- other methods -- */

    /**
     * Creates a new {@code DummyClient} with the specified {@code name}
     * and this test instance's protocol version.
     */
    protected DummyClient createDummyClient(String name) {
  return new DummyClient(name, protocolVersion);
    }

    /**
     * Sends the number of specified messages from the specified node to
     * the specified client.  The content of each message is a consecutive
     * integer starting at the specified offset.
     */
    protected  void sendMessagesFromNode(
  SgsTestNode node, final DummyClient client, final int numMessages,
  final int offset)
  throws Exception
    {
  System.err.println("sending messages to client [" + client.name + "]");
        TransactionScheduler transactionScheduler =
            node.getSystemRegistry(). getComponent(TransactionScheduler.class);
  for (int i = 0; i < numMessages; i++) {
      final int x = i + offset;
      transactionScheduler.runTask(new TestAbstractKernelRunnable() {
    public void run() {
        ClientSession session = (ClientSession)
      AppContext.getDataManager().getBinding(client.name);
      ByteBuffer buf = ByteBuffer.allocate(4);
      buf.putInt(x).flip();
      session.send(buf, Delivery.RELIABLE);
        } }, taskOwner);
  }
    }

    /**
     * Sends the number of specified messages from the specified node to
     * the specified client and waits for the client to receive the
     * expected messages.  The content of each message is a consecutive
     * integer starting at the specified offset.
     */
    protected void sendMessagesFromNodeToClient(
  SgsTestNode node,  DummyClient client, int numMessages, int offset)
  throws Exception
    {
        sendMessagesFromNode(node, client, numMessages, offset);
  synchronized (client.clientReceivedMessages) {
      client.waitForClientToReceiveExpectedMessages(numMessages);
      client.validateMessageSequence(
    client.clientReceivedMessages, numMessages, offset);
      client.clientReceivedMessages.clear();
  }
    }
   
    /**
     * Check that the session bindings are the expected number and throw an
     * exception if they aren't.
     */
    protected void checkBindings(int numExpected) throws Exception {
 
  List<String> listenerKeys = getServiceBindingKeys(LISTENER_PREFIX);
  System.err.println("Listener keys: " + listenerKeys);
  if (listenerKeys.size() != numExpected) {
      fail("expected " + numExpected + " listener keys, got " +
     listenerKeys.size());
  }
     
  List<String> sessionKeys = getServiceBindingKeys(SESSION_PREFIX);
  System.err.println("Session keys: " + sessionKeys);
  if (sessionKeys.size() != numExpected) {
      fail("expected " + numExpected + " session keys, got " +
     sessionKeys.size());
  }
     
  List<String> sessionNodeKeys =
      getServiceBindingKeys(SESSION_NODE_PREFIX);
  System.err.println("Session node keys: " + sessionNodeKeys);
  if (sessionNodeKeys.size() != numExpected) {
      fail("expected " + numExpected + " session node keys, got " +
     sessionNodeKeys.size());
  }
    }
   
    private List<String> getServiceBindingKeys(String prefix)
  throws Exception
    {
        GetKeysTask task = new GetKeysTask(prefix);
        txnScheduler.runTask(task, taskOwner);
        return task.getKeys();
    }

    private class GetKeysTask extends TestAbstractKernelRunnable {
        private List<String> keys = new ArrayList<String>();
        private final String prefix;
        GetKeysTask(String prefix) {
            this.prefix = prefix;
        }
        public void run() throws Exception {
            String key = prefix;
            for (;;) {
                key = dataService.nextServiceBoundName(key);
                if (key == null ||
                    ! key.regionMatches(
                          0, prefix, 0, prefix.length()))
                {
                    break;
                }
                keys.add(key);
            }
        }
        public List<String> getKeys() { return keys;}
    }

    /** Find the app listener */
    private DummyAppListener getAppListener() {
  return (DummyAppListener) dataService.getServiceBinding(
      StandardProperties.APP_LISTENER);
    }

    public static class DummyAppListener implements AppListener,
                                                    ManagedObject,
                                                    Serializable {

  private final static long serialVersionUID = 1L;
 
  private final Set<ManagedReference<ClientSession>>
      sessions = Collections.synchronizedSet(
    new HashSet<ManagedReference<ClientSession>>());

        /** {@inheritDoc} */
  public ClientSessionListener loggedIn(ClientSession session) {

      if (!(session instanceof ClientSessionWrapper)) {
    throw new IllegalArgumentException(
        "session not instance of ClientSessionWrapper:" +
        session);
      }

      String name = session.getName();
      DummyClientSessionListener listener;
     
      if (name.equals(RETURN_NULL)) {
    return null;
      } else if (name.equals(NON_SERIALIZABLE)) {
    return new NonSerializableClientSessionListener();
      } else if (name.equals(THROW_RUNTIME_EXCEPTION)) {
    throw new RuntimeException("loggedIn throwing an exception");
      } else if (name.equals(DISCONNECT_THROWS_NONRETRYABLE_EXCEPTION) ||
           name.startsWith("badClient")) {
    listener = new DummyClientSessionListener(name, session, true);
      } else {
    listener = new DummyClientSessionListener(name, session, false);
      }
      DataManager dataManager = AppContext.getDataManager();
      dataManager.markForUpdate(this);
      ManagedReference<ClientSession> sessionRef =
    dataManager.createReference(session);
      sessions.add(sessionRef);
      String sessionName = session.getName();
      dataManager.setBinding(sessionName, session);
      dataManager.setBinding(sessionName + ".listener", listener);
      System.err.println("DummyAppListener.loggedIn: session:" + session);
      return listener;
  }

        /** {@inheritDoc} */
  public void initialize(Properties props) {
  }

  private Set<ClientSession> getSessions() {
      Set<ClientSession> sessionSet = new HashSet<ClientSession>();
      for (ManagedReference<ClientSession> sessionRef : sessions) {
    sessionSet.add(sessionRef.get());
      }
      return sessionSet;
  }
    }

    private static class NonSerializableClientSessionListener
  implements ClientSessionListener
    {
        /** {@inheritDoc} */
  public void disconnected(boolean graceful) {
  }

        /** {@inheritDoc} */
  public void receivedMessage(ByteBuffer message) {
  }
    }

    private static class DummyClientSessionListener
  implements ClientSessionListener, Serializable, ManagedObject
    {
  private final static long serialVersionUID = 1L;
  private final String name;
  private final ManagedReference<ClientSession> sessionRef;
  private BigInteger reconnectKey = null;
  private final boolean disconnectedThrowsException;


  DummyClientSessionListener(
      String name, ClientSession session,
      boolean disconnectedThrowsException)
  {
      this.name = name;
      this.sessionRef =
    AppContext.getDataManager().createReference(session);
      this.disconnectedThrowsException = disconnectedThrowsException;
  }

        /** {@inheritDoc} */
  public void disconnected(boolean graceful) {
      System.err.println("DummyClientSessionListener[" + name +
             "] disconnected invoked with " + graceful);
      DataManager dataManager = AppContext.getDataManager();
      try {
    dataManager.removeObject(sessionRef.get());
      } catch (ObjectNotFoundException e) {
    // session already removed
      }
      /*
      try {
    dataManager.removeObject(
        dataManager.getBinding(name + ".listener"));
      } catch (ObjectNotFoundException e) {
    // listener already removed
      }
      */
      DummyClient client =
                    reconnectKey == null ? null :
                                           dummyClients.get(reconnectKey);
      if (client != null) {
    client.setDisconnectedCallbackInvoked(graceful);
      }
      if (disconnectedThrowsException) {
    throw new RuntimeException(
        "disconnected throws non-retryable exception");
      }
  }

        /** {@inheritDoc} */
  public void receivedMessage(ByteBuffer message) {
            byte[] messageBytes = new byte[message.remaining()];
      message.get(messageBytes);
      MessageBuffer buf = new MessageBuffer(messageBytes);
      AppContext.getDataManager().markForUpdate(this);
      reconnectKey = new BigInteger(1, buf.getByteArray());
      byte[] bytes = buf.getByteArray();
      if (bytes.length == 0) {
    return;
      }
      DummyClient client = dummyClients.get(reconnectKey);
      System.err.println(
    "DummyClientSessionListener[" + name + "] " +
    "receivedMessage: " + HexDumper.toHexString(bytes) +
    "\nthrow exception: " + receivedMessageException);
      if (receivedMessageException != null) {
    RuntimeException re = receivedMessageException;
    receivedMessageException = null;
    throw re;
      }
      synchronized (client.sessionListenerReceivedMessages) {
    client.sessionListenerReceivedMessages.add(bytes);
    if (client.sessionListenerReceivedMessages.size() ==
        client.numSessionListenerExpectedMessages)
    {
        client.sessionListenerReceivedMessages.notifyAll();
    }
      }
      // Echo the received message back to the client
      ByteBuffer bbuf = ByteBuffer.allocate(bytes.length);
      bbuf.put(bytes).flip();
      sessionRef.get().send(bbuf);
  }
    }

    private static class MaybeRetryException
  extends RuntimeException implements ExceptionRetryStatus
    {
  private static final long serialVersionUID = 1L;
  private boolean retry;

  public MaybeRetryException(String s, boolean retry) {
      super(s);
      this.retry = retry;
  }

  public boolean shouldRetry() {
      return retry;
  }
    }

    /**
     * This invocation handler adds a 100 ms delay before invoking any
     * method on the underlying instance.
     */
    private static class DelayingInvocationHandler
  implements InvocationHandler, Serializable
    {
  private final static long serialVersionUID = 1L;
  private Object obj;
 
  DelayingInvocationHandler(Object obj) {
      this.obj = obj;
  }
 
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Exception
  {
      Thread.sleep(100);
      try {
    return method.invoke(obj, args);
      } catch (InvocationTargetException e) {
    Throwable cause = e.getCause();
    if (cause instanceof Exception) {
        throw (Exception) cause;
    } else if (cause instanceof Error) {
        throw (Error) cause;
    } else {
        throw new RuntimeException(
      "Unexpected exception:" + cause, cause);
    }
      }
  }
    }

    /**
     * This invocation handler prevents forwarding the {@code send} and
     * {@code serviceEventQueue} methods to the underlying instance.
     */
    private static class HungryInvocationHandler
  implements InvocationHandler, Serializable
    {
  private final static long serialVersionUID = 1L;
  private Object obj;
 
  HungryInvocationHandler(Object obj) {
      this.obj = obj;
  }
 
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Exception
  {
      String name = method.getName();
      if (name.equals("send") || name.equals("serviceEventQueue")) {
    return null;
      }
      try {
    return method.invoke(obj, args);
      } catch (InvocationTargetException e) {
    Throwable cause = e.getCause();
    if (cause instanceof Exception) {
        throw (Exception) cause;
    } else if (cause instanceof Error) {
        throw (Error) cause;
    } else {
        throw new RuntimeException(
      "Unexpected exception:" + cause, cause);
    }
      }
  }
    }

    protected int getObjectCount() throws Exception {
  GetObjectCountTask task = new GetObjectCountTask();
  txnScheduler.runTask(task, taskOwner);
  return task.count;
    }
   
    private class GetObjectCountTask extends TestAbstractKernelRunnable {

  volatile int count = 0;
 
  GetObjectCountTask() {
  }

  public void run() {
      count = 0;
      BigInteger last = null;
      while (true) {
    BigInteger next = dataService.nextObjectId(last);
    if (next == null) {
        break;
    }
                // NOTE: this count is used at the end of the test to make sure
                // that no objects were leaked in stressing the structure but
                // any given service (e.g., the task service) may accumulate
                // managed objects, so a more general way to exclude these from
                // the count would be nice but for now the specific types that
                // are accumulated get excluded from the count.
    ManagedReference ref =
        dataService.createReferenceForId(next);
    Object obj = ref.get();
                String name = obj.getClass().getName();
                if (!name.equals("com.sun.sgs.impl.service.task.PendingTask") &&
        !name.equals("com.sun.sgs.impl.service.nodemap.IdentityMO"))
    {
        /*
        System.err.print(
            count + "[" + obj.getClass().getName() + "]:");
        try {
      System.err.println(obj.toString());
        } catch (ObjectNotFoundException e) {
      System.err.println(
          "<< caught ObjectNotFoundException >>");
        }
        */
                    count++;
    }
                last = next;
      }
  }
    }

    static class DummyClient extends AbstractDummyClient {

  private final Object disconnectedCallbackLock = new Object();
  private boolean receivedDisconnectedCallback = false;
  private boolean graceful = false;

  private volatile int numClientExpectedMessages;
  private volatile int numSessionListenerExpectedMessages;
  // Messages received by this client's associated ClientSessionListener
  public Queue<byte[]> sessionListenerReceivedMessages =
      new ConcurrentLinkedQueue<byte[]>();
  public final Queue<byte[]> clientReceivedMessages =
      new ConcurrentLinkedQueue<byte[]>();

 

  /** Constructs an instance with the given {@code name}. */
  DummyClient(String name) {
      super(name);
  }

  DummyClient(String name, byte protocolVersion) {
      super(name, protocolVersion);
  }

  /**
   * Records this client in the global map, keyed by client
   * reconnectKey.
   */
  protected void newReconnectKey(byte[] reconnectKey) {
      dummyClients.put(new BigInteger(1, reconnectKey), this);
  }

  /**
   * Handles an {@code opcode}.
   */
  @Override
  protected void handleOpCode(byte opcode, MessageBuffer buf) {
      switch (opcode) {
   
      case SimpleSgsProtocol.SESSION_MESSAGE:
    byte[] message = buf.getBytes(buf.limit() - buf.position());
    synchronized (clientReceivedMessages) {
        clientReceivedMessages.add(message);
        System.err.println(
          toString() + " received SESSION_MESSAGE: " +
      HexDumper.toHexString(message));
        if (clientReceivedMessages.size() ==
      numClientExpectedMessages)
        {
      clientReceivedMessages.notifyAll();
        }
    }
    break;
     
      default:
    super.handleOpCode(opcode, buf);
    break;
      }
  }

  /**
   * Sends a SESSION_MESSAGE with the specified content, prefixing
   * the message with the reconnect key.  The {@code receivedMessage}
   * method of this client's associated ClientSessionListener must
   * expect that each message is prefixed with the reconnect key.
   */
  @Override
        public void sendMessage(byte[] message, boolean checkSuspend) {
      MessageBuffer buf =
    new MessageBuffer(5 + reconnectKey.length + message.length);
      buf.putByte(SimpleSgsProtocol.SESSION_MESSAGE).
    putByteArray(reconnectKey).
    putByteArray(message);
      sendRaw(buf.getBuffer(), checkSuspend);
  }

  /**
   * Records that this client's associated ClientSessionListener's
   * {@code disconnected} method was invoked with the specified value
   * for {@code graceful}.
   */
  public void setDisconnectedCallbackInvoked(boolean graceful) {
      synchronized (disconnectedCallbackLock) {
    receivedDisconnectedCallback = true;
    this.graceful = graceful;
    disconnectedCallbackLock.notifyAll();
      }
  }

  /**
   * Verifies that the {@code disconnected} method of this client's
   * associated ClientSessionListener was invoked with the specified
   * value for {@code graceful}, and if not, this method throws
   * {@code AssertionError}.
   */
  public void assertDisconnectedCallbackInvoked(boolean graceful) {
      synchronized (disconnectedCallbackLock) {
    assertTrue(receivedDisconnectedCallback);
    assertEquals(this.graceful, graceful);
      }
  }

  /**
   * Verifies that the {@code disconnected} method of this client's
   * associated ClientSessionListener was NOT invoked, otherwise this
   * method throws {@code AssertionError}.
   */
  public void assertDisconnectedCallbackNotInvoked() {
      synchronized (disconnectedCallbackLock) {
    assertFalse(receivedDisconnectedCallback);
      }
  }

  /**
   * Waits for the {@code disconnected} method of this client's
   * associated ClientSessionListener to be invoked with the
   * specified value for {@code graceful} (within a timeout
   * period), and if not, this method throws {@code AssertionError}.
   */
  public void checkDisconnectedCallback(boolean graceful)
      throws Exception
  {
      synchronized (disconnectedCallbackLock) {
    if (!receivedDisconnectedCallback) {
        disconnectedCallbackLock.wait(WAIT_TIME);
    }
    assertDisconnectedCallbackInvoked(graceful);
    System.err.println(toString() + " disconnect successful");
      }
  }

  /**
   * From this client, sends the number of messages (each containing a
   * monotonically increasing sequence number), then waits for all the
   * messages to be received by this client's associated {@code
   * ClientSessionListener}, and validates the sequence of messages
   * received by the listener. 
   */
  public void sendMessagesFromClientInSequence(
      int numMessages, int numExpectedMessages)
  {
      for (int i = 0; i < numMessages; i++) {
    MessageBuffer buf = new MessageBuffer(4);
    buf.putInt(i);
    sendMessage(buf.getBuffer(), true);
      }
      waitForSessionListenerToReceiveExpectedMessages(
    numExpectedMessages);
      validateMessageSequence(
    sessionListenerReceivedMessages, numExpectedMessages, 0);
  }
 
  /**
   * Waits for this client to receive the number of session messages
   * sent from the application.
   */
  public void waitForClientToReceiveExpectedMessages(
      int numExpectedMessages)
  {
      numClientExpectedMessages = numExpectedMessages;
      synchronized (clientReceivedMessages) {
    if (clientReceivedMessages.size() != numExpectedMessages)
    {
        try {
      clientReceivedMessages.wait(WAIT_TIME);
        } catch (InterruptedException e) {
        }
    }
      }
      assertEquals(
    "Client " + toString() +
    " did not receive expected number of messages",
    numExpectedMessages, clientReceivedMessages.size());
      System.err.println(
    toString() + " received expected number of messages: " +
    numExpectedMessages);
  }
 
  /**
   * Waits for this client's ClientSessionListener to receive the
   * specified number of messages.
   */
  public void waitForSessionListenerToReceiveExpectedMessages(
      int numExpectedMessages)
  {
      numSessionListenerExpectedMessages = numExpectedMessages;
      synchronized (sessionListenerReceivedMessages) {
    if (sessionListenerReceivedMessages.size() !=
        numExpectedMessages)
    {
        try {
      sessionListenerReceivedMessages.wait(WAIT_TIME);
        } catch (InterruptedException e) {
        }
    }
      }
      assertEquals(
    "ClientSessionListener for " + toString() +
    " did not receive expected number of messages",
    numExpectedMessages, sessionListenerReceivedMessages.size());
      System.err.println(
    "ClientSessionListener for " + toString() +
    " received expected number of messages: " +
    numExpectedMessages);
  }

  /**
   * Waits for the number of expected messages to be recorded in the
   * specified 'list', and validates that the expected number of messages
   * were received by the ClientSessionListener in the correct sequence.
   */
  public void validateMessageSequence(
      Queue<byte[]> messageQueue, int numExpectedMessages, int offset)
  {
      assertEquals(
    "validateMessageSequence: unexpected number of messages",
    numExpectedMessages, messageQueue.size());
      if (numExpectedMessages != 0) {
    int expectedValue = offset;
    for (byte[] message : messageQueue) {
        MessageBuffer buf = new MessageBuffer(message);
        int value = buf.getInt();
        System.err.println(
      toString() + " validating message sequence: " + value);
        assertEquals("unexpected value", expectedValue, value);
        expectedValue++;
    }
      }
  }
 
  /**
   * {@inheritDoc}
   *
   * <p>This implementation verifies that no messages were received
   * by this client's associated ClientSessionListener during relocation.
   */
  public void relocate(int newPort, boolean useValidKey,
           boolean shouldSucceed)
  {
      super.relocate(newPort, useValidKey, shouldSucceed);
      // Verify that no messages were received by this client's
      // associated ClientSessionListener during relocation.
      waitForSessionListenerToReceiveExpectedMessages(0);
  }

  /**
   * {@inheritDoc}
   *
   * <p>This implementation clears the clientReceivedMessages and
   * sessionListenerReceivedMessages queue.
   */
  public void disconnect() {
      super.disconnect();
      clientReceivedMessages.clear();
      sessionListenerReceivedMessages.clear();
  }
    }
}
TOP

Related Classes of com.sun.sgs.test.impl.service.session.TestClientSessionServiceImplv4$DummyClient

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.