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

Source Code of com.sun.sgs.test.impl.service.channel.AbstractChannelServiceTest

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

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelListener;
import com.sun.sgs.app.ChannelManager;
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.ManagedObject;
import com.sun.sgs.app.ManagedReference;
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.channel.ChannelServer;
import com.sun.sgs.impl.service.channel.ChannelServiceImpl;
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.impl.util.BoundNamesUtil;
import com.sun.sgs.impl.util.KernelCallable;
import com.sun.sgs.kernel.TransactionScheduler;
import com.sun.sgs.protocol.simple.SimpleSgsProtocol;
import com.sun.sgs.service.DataService;
import com.sun.sgs.test.util.AbstractDummyClient;
import com.sun.sgs.test.util.ConfigurableNodePolicy;
import com.sun.sgs.test.util.IdentityAssigner;
import com.sun.sgs.test.util.SgsTestNode;
import com.sun.sgs.test.util.TestAbstractKernelRunnable;
import com.sun.sgs.tools.test.FilteredNameRunner;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

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

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

@RunWith(FilteredNameRunner.class)
public abstract class AbstractChannelServiceTest extends Assert {
   
    private static final String APP_NAME = "TestChannelServiceImpl";
   
    protected static final byte PROTOCOL_v4 = 0x04;

    protected static final int WAIT_TIME = 2000;
   
    /** A list of users for test purposes. */
    protected static final String MOE = "moe";
    protected static final String LARRY = "larry";
    protected static final String CURLY = "curly";
    protected static final String[] someUsers = new String[] { MOE, LARRY, CURLY };

    /** A longer list of users for test purposes. */
    protected static final String[] sevenDwarfs =
  new String[] {"bashful", "doc", "dopey", "grumpy",
          "happy", "sleepy", "sneezy"};

    /** No users. */
    protected static final String[] noUsers = new String[0];

    /** The Channel service properties. */
    protected static final Properties serviceProps =
  createProperties(StandardProperties.APP_NAME, APP_NAME);

    private static Map<Long, MethodInfo> holdMethodMap =
  new HashMap<Long, MethodInfo>();
   
    /** The number for creating host names. */
    private int hostNum = 1;
   
    /** The node that creates the servers. */
    protected SgsTestNode serverNode;

    /** Any additional nodes created by a test. */
    private Set<SgsTestNode> additionalNodes = new HashSet<SgsTestNode>();

    /** Version information from ChannelServiceImpl class. */
    protected final String VERSION_KEY;
    protected final int MAJOR_VERSION;
    protected int MINOR_VERSION;

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

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

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

    /** The channel service on the server node. */
    protected ChannelManager channelService;

    /** The identity assigner, for moving identities. */
    protected IdentityAssigner identityAssigner;

    /** The listen port for the client session service. */
    protected int port;

    /** The SimpleSgsProtocol version for clients. */
    private byte protocolVersion;

    protected boolean isPerformanceTest = false;

    private static Field getField(Class cl, String name) throws Exception {
  Field field = cl.getDeclaredField(name);
  field.setAccessible(true);
  return field;
    }
   
    /** Constructs a test instance. */
    public AbstractChannelServiceTest() throws Exception  {
  Class cl = ChannelServiceImpl.class;
  VERSION_KEY = (String) getField(cl, "VERSION_KEY").get(null);
  MAJOR_VERSION = getField(cl, "MAJOR_VERSION").getInt(null);
  MINOR_VERSION = getField(cl, "MINOR_VERSION").getInt(null);
    }

    /** Creates and configures the channel service. */
    @Before
    public void setUp() throws Exception {
        setUp(true);
  holdMethodMap.clear();
    }

    protected void setUp(boolean clean) throws Exception {
        Properties props =
            SgsTestNode.getDefaultProperties(APP_NAME, null,
                                             DummyAppListener.class);
  this.protocolVersion = SimpleSgsProtocol.VERSION;
        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.protocol.simple.protocol.version",
     Byte.toString(protocolVersion));
  props.setProperty(
      StandardProperties.SESSION_RELOCATION_TIMEOUT_PROPERTY,
      "5000");
       
  serverNode =
                new SgsTestNode(APP_NAME, DummyAppListener.class, props, clean);
  port = serverNode.getAppPort();

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

  identityAssigner = new IdentityAssigner(serverNode);
        dataService = serverNode.getDataService();
  channelService = serverNode.getChannelService();
  wrapChannelServerProxy(serverNode);
    }

    /** Cleans up the transaction. */
    @After
    public void tearDown() throws Exception {
        tearDown(true);
    }

    protected void tearDown(boolean clean) throws Exception {
  // This sleep cuts down on the exceptions output due to shutdown.
  Thread.sleep(500);
  for (SgsTestNode node : additionalNodes) {
      node.shutdown(false);
  }
  additionalNodes.clear();
        serverNode.shutdown(clean);
        serverNode = null;
    }
   
    /**
     * Creates a new test node, and returns it.
     */
    protected SgsTestNode addNode() throws Exception {
  String host = "node" + hostNum++;
  Properties props = SgsTestNode.getDefaultProperties(
      APP_NAME, serverNode, DummyAppListener.class);
  props.setProperty(StandardProperties.AUTHENTICATORS,
      "com.sun.sgs.test.util.SimpleTestIdentityAuthenticator");
  props.put("com.sun.sgs.impl.service.watchdog.client.host", host);
  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);
  wrapChannelServerProxy(node);
  additionalNodes.add(node);
  return node;
    }

    /**
     * Creates the specified number of test nodes, and returns a set
     * containing the constructed nodes.
     */
    protected Set<SgsTestNode> addNodes(int numNodes) throws Exception {
  if (additionalNodes == null) {
  }

  Set<SgsTestNode> nodes = new HashSet<SgsTestNode>();
        for (int i = 0; i < numNodes; i++) {
      nodes.add(addNode());
        }
  return nodes;
    }

    protected class ClientGroup {

  Map<String, DummyClient> clients;

  ClientGroup(String... users) {
      this(port, users);
  }

  ClientGroup(int connectPort, String... users) {
      clients = new HashMap<String, DummyClient>();
      for (String user : users) {
    DummyClient client = new DummyClient(user);
    clients.put(user, client);
    client.connect(connectPort);
    client.login();
      }
  }

  private ClientGroup(Map<String, DummyClient> clients) {
      this.clients = clients;
  }

  void join(String channelName) {
      for (DummyClient client : clients.values()) {
    client.join(channelName);
      }
  }

  void leave(String channelName) {
      for (DummyClient client : clients.values()) {
    client.leave(channelName);
      }
  }

  // Removes the client sessions on the given host from this group
  // and returns a ClientGroup with the removed sessions.
  ClientGroup removeSessionsFromGroup(int port) {
      Iterator<String> iter = clients.keySet().iterator();
      Map<String, DummyClient> removedClients =
    new HashMap<String, DummyClient>();
      while (iter.hasNext()) {
    String user = iter.next();
    DummyClient client = clients.get(user);
                // Note that the redirectPort can sometimes be zero,
                // as it won't be assigned if the initial login request
                // was successful.
    int redirectPort = client.getRedirectPort();
    if ((redirectPort != 0 && port == redirectPort) ||
        (redirectPort == 0 && port == client.getConnectPort()))
    {
        iter.remove();
        removedClients.put(user, client);
        client.disconnect();
    }
      }
      return new ClientGroup(removedClients);
  }

  boolean isDisconnectedGroup() {
      boolean allSessionsDisconnected = true;
      for (DummyClient client : clients.values()) {
    if (client.isConnected()) {
        System.err.println(client.name + " is still connected!");
        allSessionsDisconnected = false;
    }
      }
      return allSessionsDisconnected;
  }

  void checkMembership(final String name, final boolean isMember)
      throws Exception
  {
      txnScheduler.runTask(new TestAbstractKernelRunnable() {
    public void run() {
        Channel channel = getChannel(name);
        Set<ClientSession> sessions = getSessions(channel);
        for (DummyClient client : clients.values()) {

      ClientSession session = getClientSession(client.name);

      if (session != null && sessions.contains(session)) {
          if (!isMember) {
        fail("ClientGroup.checkMembership session: " +
             session.getName() + " is a member of " +
             name);
          }
      } else if (isMember) {
          String sessionName =
        (session == null) ? "null" : session.getName();
          fail("ClientGroup.checkMembership session: " +
         sessionName + " is not a member of " + name);
      }
        }
    }
      }, taskOwner);
  }

  DummyClient getClient(String name) {
      return clients.get(name);
  }

  Collection<DummyClient> getClients() {
      return clients.values();
  }

  void disconnect(boolean graceful) {
      for (DummyClient client : clients.values()) {
    if (graceful) {
        client.logout();
    } else {
        client.disconnect();
    }
      }
  }
    }

    // -- other methods --

    protected void printServiceBindings(final String message) throws Exception {
  txnScheduler.runTask(new TestAbstractKernelRunnable() {
      public void run() {
    System.err.println("Service bindings <<" + message +
           ">>----------");
    Iterator<String> iter =
        BoundNamesUtil.getServiceBoundNamesIterator(
      dataService, "com.sun.sgs.impl.service.channel.");
    while (iter.hasNext()) {
        System.err.println(iter.next());
    }
    System.err.println("--------------------------");
      }
  }, taskOwner);
    }

    // Returns a newly created channel
    protected Channel createChannel() throws Exception {
  return createChannel("test");
    }

    protected Channel createChannel(String name) throws Exception {
  return createChannel(name, null, null);
    }

    protected Channel createChannel(String name, ChannelListener listener)
  throws Exception
    {
  return createChannel(name, listener, null);

    }
   
    protected Channel createChannel(
  String name, ChannelListener listener, SgsTestNode node)
  throws Exception
    {
  CreateChannelTask createChannelTask =
      new CreateChannelTask(name, listener);
  runTransactionalTask(createChannelTask, node);
  return createChannelTask.getChannel();
    }

    private void runTransactionalTask(
  TestAbstractKernelRunnable task, SgsTestNode node)
  throws Exception
    {
  if (node == null) {
      node = serverNode;
  }
  TransactionScheduler nodeTxnScheduler =
      node.getSystemRegistry().getComponent(TransactionScheduler.class);
  Identity nodeTaskOwner =
      node.getProxy().getCurrentOwner();
  nodeTxnScheduler.runTask(task, nodeTaskOwner);
    }

    private static class CreateChannelTask extends TestAbstractKernelRunnable {
  private final String name;
  private final ChannelListener listener;
  private Channel channel;
 
  CreateChannelTask(String name, ChannelListener listener) {
      this.name = name;
      this.listener = listener;
  }
 
  public void run() throws Exception {
      channel = AppContext.getChannelManager().
    createChannel(name, listener, Delivery.RELIABLE);
      AppContext.getDataManager().setBinding(name, channel);
  }

  Channel getChannel() {
      return channel;
  }
    }

    protected ClientSession getSession(String name) {
  try {
      return (ClientSession) dataService.getBinding(name);
  } catch (ObjectNotFoundException e) {
      return null;
  }
    }

    // FIXME: use the ChannelManager instead...
    protected Channel getChannel(String name) {
  try {
      return (Channel) dataService.getBinding(name);
  } catch (ObjectNotFoundException e) {
      return null;
  }
    }

    protected Set<ClientSession> getSessions(Channel channel) {
  Set<ClientSession> sessions = new HashSet<ClientSession>();
  Iterator<ClientSession> iter = channel.getSessions();
  while (iter.hasNext()) {
      sessions.add(iter.next());
  }
  return sessions;
    }
   
    protected void joinUsers(
  final String channelName, final String... users)
  throws Exception
    {
  txnScheduler.runTask(new TestAbstractKernelRunnable() {
      public void run() {
    Channel channel = getChannel(channelName);
    for (String user : users) {
        ClientSession session =
      (ClientSession) dataService.getBinding(user);
        channel.join(session);
    }
      }
  }, taskOwner);
    }

    protected void leaveUsers(
   final String channelName, final String... users)
  throws Exception
    {
  txnScheduler.runTask(new TestAbstractKernelRunnable() {
      public void run() {
    Channel channel = getChannel(channelName);
    for (String user : users) {
        ClientSession session =
      (ClientSession) dataService.getBinding(user);
        channel.leave(session);
    }
      }
  }, taskOwner);
    }

    protected void leaveAll(final String channelName) throws Exception {
  txnScheduler.runTask(new TestAbstractKernelRunnable() {
      public void run() {
    Channel channel = getChannel(channelName);
    channel.leaveAll();
      }
  }, taskOwner);
    }

    protected void checkUsersJoined(
  final String channelName, final String... users)
  throws Exception
    {
  // Need to wait for possible cache entry to expire so that a true
  // picture of the channel membership is reflected.
  Thread.sleep(1000);
  checkUsersJoined0(channelName, users);
    }
   
    private void checkUsersJoined0(
  final String channelName, final String... users)
  throws Exception
    {
  txnScheduler.runTask(new TestAbstractKernelRunnable() {
      public void run() {
    Channel channel = getChannel(channelName);
    Set<ClientSession> sessions = getSessions(channel);
    System.err.println("Sessions joined:" + sessions);
    if (sessions.size() != users.length) {
        fail("Expected " + users.length + " sessions, got " +
       sessions.size());
    }
    List<String> userList = Arrays.asList(users);
    for (ClientSession session : sessions) {
        if (!userList.contains(session.getName())) {
      fail("Expected session: " + session);
        }
    }
      }
  }, taskOwner);
    }

    private void printIt(String line) {
  if (! isPerformanceTest) {
      System.err.println(line);
  }
    }
   
    // Shuts down the node with the specified host.
    protected void shutdownNode(SgsTestNode node) throws Exception {
  node.shutdown(false);
  additionalNodes.remove(node);
    }
   
    protected void sendMessagesToChannel(
  final String channelName, int numMessages)
  throws Exception
    {
  for (int i = 0; i < numMessages; i++) {
      final MessageBuffer buf = (new MessageBuffer(4)).putInt(i);
      System.err.println("Sending message: " +
             HexDumper.format(buf.getBuffer()));
     
      txnScheduler.runTask(
    new TestAbstractKernelRunnable() {
        public void run() {
      Channel channel = getChannel(channelName);
      channel.send(null, ByteBuffer.wrap(buf.getBuffer()));
        }
    }, taskOwner);
  }
    }

    protected void checkChannelMessagesReceived(
  DummyClient client, String channelName, int numMessages)
    {
  for (int i = 0; i < numMessages; i++) {
      checkNextChannelMessage(client, channelName, i);
  }
    }

    protected void checkChannelMessagesReceived(
  ClientGroup group, String channelName, int numMessages)
  throws InterruptedException
    {
  Thread.sleep(3000);
  for (DummyClient client : group.getClients()) {
      checkChannelMessagesReceived(client, channelName, numMessages);
  }
    }

    protected void checkNextChannelMessage(
  DummyClient client, String channelName, int value)
    {
  MessageInfo info = client.nextChannelMessage();
  if (info == null) {
      fail("FAILURE: " + client.name +
     " did not get any message for channel: " + channelName);
  }
  assertEquals("Mismatched channel names", channelName, info.channelName);
  assertEquals("Unexpected channel message sequence", value, info.seq);
    }
   

    // -- other classes --

    /**
     * Creates a dummy client with the specified {@code name} and logs it
     * into the specified {@code node}.
     */
    protected DummyClient createDummyClient(String name, SgsTestNode node) {
  DummyClient client = new DummyClient(name);
  client.connect(node.getAppPort());
  assertTrue(client.login());
  return client;
    }

    // Dummy client code for testing purposes.
    public class DummyClient extends AbstractDummyClient {

  private final Object lock = new Object();
  private Set<String> channelNames = new HashSet<String>();
  private Map<BigInteger, String> channelIdToName =
      new HashMap<BigInteger, String>();
  Map<String, BigInteger> channelNameToId =
      new HashMap<String, BigInteger>();
  private String reason;
  private final List<MessageInfo> channelMessages =
      new ArrayList<MessageInfo>();

  /** Constructs an instance with the given {@code name}. */
  DummyClient(String name) {
      super(name, SimpleSgsProtocol.VERSION);
  }
 
  ClientSession getSession() throws Exception {
      GetSessionTask task = new GetSessionTask(name);
      txnScheduler.runTask(task, taskOwner);
      return task.getSession();
  }

  /** {@inheritDoc} */
  @Override
  public void sendMessage(byte[] message, boolean checkSuspend) {
      checkLoggedIn();

      // A zero-length message is sent when the superclass processes
      // a LOGIN_SUCCESS or RELOCATE_SUCCESS message, so eat it here.
      if (message.length == 0) {
    return;
      }

      MessageBuffer buf =
    new MessageBuffer(1 + message.length);
      buf.putByte(SimpleSgsProtocol.SESSION_MESSAGE).
    putBytes(message);
      sendRaw(buf.getBuffer(), checkSuspend);
  }

  // Sends a CHANNEL_MESSAGE.
  void sendChannelMessage(String channelName, int seq)
      throws Exception
  {
      checkLoggedIn();
      BigInteger channelRefId = channelNameToId.get(channelName);
      if (channelRefId == null) {
    channelRefId = getChannelId(channelName);
      }
      System.err.println(toString() + " sending message:" + seq +
             " to channel:" + channelName);
      byte[] channelId = channelRefId.toByteArray();
      MessageBuffer buf =
    new MessageBuffer(3 + channelId.length + 4);
      buf.putByte(SimpleSgsProtocol.CHANNEL_MESSAGE).
    putShort(channelId.length).
    putBytes(channelId).
    putInt(seq);
      sendRaw(buf.getBuffer(), true);
  }
 
  MessageInfo nextChannelMessage() {
      int totalWaitTime = WAIT_TIME * 2;
      int waitInterval = totalWaitTime / 10;
      synchronized (lock) {
    while (channelMessages.isEmpty()) {
        try {
      lock.wait(waitInterval);
        } catch (InterruptedException e) {
        }
        totalWaitTime -= waitInterval;
        if (totalWaitTime <= 0) {
      break;
        }
    }
    return
        channelMessages.isEmpty() ?
        null :
        channelMessages.remove(0);
      }
  }

  void join(String channelToJoin) {
      String action = "join";
      MessageBuffer buf =
    new MessageBuffer(MessageBuffer.getSize(action) +
          MessageBuffer.getSize(channelToJoin));
      buf.putString(action).putString(channelToJoin);
      sendMessage(buf.getBuffer(), true);
      assertJoinedChannel(channelToJoin);
  }
 
  void assertJoinedChannel(String channelName) {
      synchronized (lock) {
    if (!channelNameToId.containsKey(channelName)) {
        try {
      lock.wait(WAIT_TIME);
        } catch (InterruptedException e) {
        }
        assertTrue(
      toString() + " did not receive CHANNEL_JOIN, " +
      "channel: " + channelName,
      channelNameToId.containsKey(channelName));
    }
      }
  }
     
  void leave(String channelToLeave) {
      String action = "leave";
      MessageBuffer buf =
    new MessageBuffer(MessageBuffer.getSize(action) +
          MessageBuffer.getSize(channelToLeave));
      buf.putString(action).putString(channelToLeave);
      sendMessage(buf.getBuffer(), true);
      assertLeftChannel(channelToLeave);
  }

  void assertLeftChannel(String channelName) {
      synchronized (lock) {
    if (channelNameToId.containsKey(channelName)) {
        try {
      lock.wait(WAIT_TIME);
        } catch (InterruptedException e) {
        }
        assertFalse(
      toString() + " did not receive CHANNEL_LEAVE, " +
      "channel: " + channelName,
      channelNameToId.containsKey(channelName));
    }
      }
  }
 
  /**
   * Handles session and channel messages and channel joins and
   * leaves, then delegates to the super class to handle those
   * opcodes it doesn't handle.
   */
  @Override
  protected void handleOpCode(byte opcode, MessageBuffer buf) {
      switch (opcode) {
      case SimpleSgsProtocol.SESSION_MESSAGE: {
    String action = buf.getString();
    if (action.equals("join")) {
        String channelName = buf.getString();
        synchronized (lock) {
      channelNames.add(channelName);
      System.err.println(
          name + ": got join ack, channel: " +
          channelName);
      lock.notifyAll();
        }
    } else if (action.equals("leave")) {
        String channelName = buf.getString();
        synchronized (lock) {
      channelNames.remove(channelName);
      System.err.println(
          name + ": got leave ack, channel: " +
          channelName);
      lock.notifyAll();
        }
    } else if (action.equals("message")) {
        String channelName = buf.getString();
        int seq = buf.getInt();
        synchronized (lock) {
      channelMessages.add(new MessageInfo(channelName, seq));
      System.err.println(name + ": message received: " + seq);
      lock.notifyAll();
        }
    } else {
        System.err.println(
      name + ": received message with unknown action: " +
      action);
    }
    break;
      }
   
      case SimpleSgsProtocol.CHANNEL_JOIN: {
    String channelName = buf.getString();
    BigInteger channelId =
        new BigInteger(1,
           buf.getBytes(buf.limit() - buf.position()));
    synchronized (lock) {
        channelIdToName.put(channelId, channelName);
        channelNameToId.put(channelName, channelId);
        printIt("[" + name + "] join succeeded: " +
          channelName);
        lock.notifyAll();
    }
    break;
      }
   
      case SimpleSgsProtocol.CHANNEL_LEAVE: {
    BigInteger channelId =
        new BigInteger(1,
           buf.getBytes(buf.limit() - buf.position()));
    synchronized (lock) {
        String channelName = channelIdToName.remove(channelId);
        channelNameToId.remove(channelName);
        printIt("[" + name + "] leave succeeded: " +
          channelName);
        lock.notifyAll();
    }
    break;
   
      }
      case SimpleSgsProtocol.CHANNEL_MESSAGE: {
    BigInteger channelId =
        new BigInteger(1, buf.getBytes(buf.getShort()));
    int seq = buf.getInt();
    synchronized (lock) {
        String channelName = channelIdToName.get(channelId);
        if (channelName != null) {
      System.err.println(
          "[" + name + "] received message: " +
          seq + ", channel: " + channelName);
      channelMessages.add(new MessageInfo(channelName, seq));
        } else {
      System.err.println(
          "[" + name + "] received message: " +
          seq + ", but not joined to channel: " +
          HexDumper.toHexString(channelId.toByteArray()));
        }
        lock.notifyAll();
    }
    break;
      }
   
      default:
    super.handleOpCode(opcode, buf);
    break;
      }
  }
    }

    protected static class MessageInfo {
  final String channelName;
  final int seq;

  MessageInfo(String channelName, int seq) {
      this.channelName = channelName;
      this.seq = seq;
  }
    }

    public static class DummyAppListener implements AppListener, Serializable {

  private static final long serialVersionUID = 1L;

  public DummyAppListener() { }

  public ClientSessionListener loggedIn(ClientSession session) {

      if (!(session instanceof ClientSessionWrapper)) {
    fail("session not ClientSessionWrapper instance: " +
         session);
      }
      DummyClientSessionListener listener =
    new DummyClientSessionListener(session);
      DataManager dataManager = AppContext.getDataManager();
      dataManager.setBinding(session.getName(), session);
      System.err.println("DummyAppListener.loggedIn: session:" + session);
      return listener;
  }

  public void initialize(Properties props) {
  }
    }

    private static class DummyClientSessionListener
  implements ClientSessionListener, Serializable, ManagedObject
    {
  private static final long serialVersionUID = 1L;
  private final String name;
 
  private final ManagedReference<ClientSession> sessionRef;
 
  DummyClientSessionListener(ClientSession session) {
      DataManager dataManager = AppContext.getDataManager();
      this.sessionRef = dataManager.createReference(session);
      this.name = session.getName();
  }

  public void disconnected(boolean graceful) {
      AppContext.getDataManager().removeObject(this);
  }

  public void receivedMessage(ByteBuffer message) {
            byte[] bytes = new byte[message.remaining()];
            message.asReadOnlyBuffer().get(bytes);
      MessageBuffer buf = new MessageBuffer(bytes);
      String action = buf.getString();
      DataManager dataManager = AppContext.getDataManager();
      ClientSession session = sessionRef.get();
      if (action.equals("join")) {
    String channelName = buf.getString();
    System.err.println("DummyClientSessionListener: join request, " +
           "channel name: " + channelName +
           ", user: " + name);
    Channel channel =
        (Channel) dataManager.getBinding(channelName);
    channel.join(session);
    session.send(message.asReadOnlyBuffer());
      } else if (action.equals("leave")) {
    String channelName = buf.getString();
    System.err.println("DummyClientSessionListener: leave request, " +
           "channel name: " + channelName +
           ", user: " + name);
    Channel channel =
        (Channel) dataManager.getBinding(channelName);
    channel.leave(session);
    session.send(message.asReadOnlyBuffer());
      } else {
    System.err.println("DummyClientSessionListener: UNKNOWN request, " +
           "action: " +  action +
           ", user: " + name);
      }
  }
    }

    private class GetSessionTask extends TestAbstractKernelRunnable {

  private final String name;
  private ClientSession session = null;
 
  GetSessionTask(String name) {
      this.name = name;
  }

  public void run() {
      try {
    session = (ClientSession) dataService.getBinding(name);
      } catch (ObjectNotFoundException e) {
    session = null;
      }
  }

  ClientSession getSession() {
      return session;
  }
    }

    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;
      }
  }
    }

    /**
     * Returns the count of channel service bindings, i,e. bindings that
     * have the following prefix:
     *
     * com.sun.sgs.impl.service.channel
     */
    protected int getChannelServiceBindingCount() throws Exception {
  GetChannelServiceBindingCountTask task =
      new GetChannelServiceBindingCountTask();
  txnScheduler.runTask(task, taskOwner);
  return task.count;
    }
   
    private class GetChannelServiceBindingCountTask
  extends TestAbstractKernelRunnable
    {
  volatile int count = 0;
 
  GetChannelServiceBindingCountTask() {
  }

  public void run() {
      count = 0;
      Iterator<String> iter =
    BoundNamesUtil.getServiceBoundNamesIterator(
        dataService, "com.sun.sgs.impl.service.channel.");
      while (iter.hasNext()) {
    iter.next();
    count++;
      }
  }
    }

    /**
     * Returns the client session with the specified {@code name} by
     * looking it up in the data service.
     */
    private ClientSession getClientSession(String name) {
  try {
      return (ClientSession) dataService.getBinding(name);
  } catch (ObjectNotFoundException e) {
      return null;
  }
    }

    /**
     * Holds the next invocation of the specified {@code ChannelServer}
     * {@code methodName} from a remote node to the specified {@code node},
     * until the "{@link #releaseChannelServerMethodHeld
     * releaseChannelServerMethodHeld} method is invoke with the specified
     * {@code node}.  Invocations to a {@code ChannelServer} on a local
     * node will not be held because such invocations are made directly on
     * the server implementation and not through a proxy.
     */
    protected void holdChannelServerMethodToNode(
  SgsTestNode node, final String methodName)
  throws Exception
    {
  long nodeId = node.getNodeId();
  MethodInfo info = holdMethodMap.get(nodeId);
  if (info != null) {
      // just update method info and return.
      info.name = methodName;
      info.hold = true;
      info.isHeld = false;
  } else {
      // Store method info in map, and wrap channel server to hold methods
      holdMethodMap.put(nodeId, new MethodInfo(methodName));
  }
    }

    /**
     * Waits for a notification of the method specified by a previous
     * invocation to {@link #holdChannelServerMethodToNode} to the
     * specified node.
     */
    protected void waitForHeldChannelServerMethodToNode(SgsTestNode node)
  throws Exception
    {
  long nodeId = node.getNodeId();
  MethodInfo info = holdMethodMap.get(nodeId);
  if (info != null) {
      synchronized (info) {
    if (!info.isHeld) {
        try {
      info.wait(WAIT_TIME);
        } catch (InterruptedException e) {
        }
    }
    if (!info.isHeld) {
        fail("Timeout waiting for held method: " + info.name +
       " to node:" + nodeId);
    }
      }
     
  } else {
      fail("No method to node:" + nodeId + " held");
  }
    }

    /**
     * Releases the {@code ChannelServer} method to the specified {@code
     * node} that is currently being held.
     */
    protected void releaseChannelServerMethodHeld(SgsTestNode node) {
  MethodInfo info = holdMethodMap.get(node.getNodeId());
  // Release "join" to oldNode.
  synchronized (info) {
      info.hold = false;
      info.notifyAll();
  }
    }

    /**
     * Wraps the {@code ChannelServer} proxy to the specified {@code node}
     * with a proxy that enables tests to hold and release specified method
     * invocations through the proxy.
     */
    private void wrapChannelServerProxy(SgsTestNode node)
  throws Exception
    {
  final long nodeId = node.getNodeId();
  txnScheduler.runTask(new TestAbstractKernelRunnable() {
      @SuppressWarnings("unchecked")
      public void run() throws Exception {
    String key =
        "com.sun.sgs.impl.service.channel.server." + nodeId;
    ManagedSerializable<ChannelServer> managedServer =
        (ManagedSerializable<ChannelServer>)
        dataService.getServiceBinding(key);
    Constructor constr =
         managedServer.getClass().getDeclaredConstructors()[0];
    constr.setAccessible(true);
    ChannelServer channelServer = managedServer.get();
    ControllableInvocationHandler handler =
        new ControllableInvocationHandler(channelServer, nodeId);
    ChannelServer newServer = (ChannelServer)
        Proxy.newProxyInstance(
      ChannelServer.class.getClassLoader(),
      new Class[] { ChannelServer.class },
      handler);
    dataService.setServiceBinding(
         key, constr.newInstance(newServer));
    dataService.removeObject(managedServer);
   
      }}, taskOwner);
    }

    /**
     * Returns the channel ID for the channel with the specified name.
     */
    protected BigInteger getChannelId(final String channelName)
  throws Exception
    {
  return KernelCallable.call(
      new KernelCallable<BigInteger>("getChannelId") {
    public BigInteger call() throws Exception {
    Channel channel =
        AppContext.getChannelManager().getChannel(channelName);
    Field field = getField(channel.getClass(), "channelRef");
    ManagedReference channelRef =
        (ManagedReference) field.get(channel);
    return channelRef.getId();
    }
      }, txnScheduler, taskOwner);
    }
   
    /**
     * This invocation handler waits to be notified before invoking a
     * method (on the underlying instance) if the method being invoked
     * matches the method information currently on record for the node and
     * the method's "hold" status is true.
     */
    private static class ControllableInvocationHandler
  implements InvocationHandler, Serializable
    {
  private static final long serialVersionUID = 1L;
  private final Object obj;
  private final long nodeId;
 
  ControllableInvocationHandler(Object obj, long nodeId) {
      this.obj = obj;
      this.nodeId = nodeId;
  }
 
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Exception
  {
      MethodInfo info = holdMethodMap.get(nodeId);
      if (info != null) {
    synchronized (info) {
        if (method.getName().equals(info.name)) {
      if (info.hold) {
          System.err.println(
        ">>HOLD ChannelServer method: " + info.name +
        " to node: " + nodeId);
          info.isHeld = true;
          info.notifyAll();
          while (info.hold) {
        try {
            info.wait();
        } catch (InterruptedException e) {
        }
          }
          info.isHeld = false;
          System.err.println(
        ">>RELEASE ChannelServer method: " + info.name +
        " to node: " + nodeId);
      }
        }
    }
      }
       
      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);
    }
      }
  }
    }

    /**
     * Method information used to hold and release methods invoked on a
     * ChannelServer. If a method is being held, the instance can be used
     * as the lock to wait for notification. The instance is notified when
     * the method is released.
     */
    private static class MethodInfo {
  volatile String name;
  volatile boolean hold = true;
  volatile boolean isHeld = false;

  MethodInfo(String name) {
      this.name = name;
  }
    }
}
TOP

Related Classes of com.sun.sgs.test.impl.service.channel.AbstractChannelServiceTest

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.