/**
   * @param destination Member[] - destination.length > 0
   * @param msg Serializable - the message to send
   * @param options int - sender options, options can trigger guarantee levels and different
   *     interceptors to react to the message see class documentation for the <code>Channel</code>
   *     object.<br>
   * @param handler - callback object for error handling and completion notification, used when a
   *     message is sent asynchronously using the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code>
   *     flag enabled.
   * @return UniqueId - the unique Id that was assigned to this message
   * @throws ChannelException - if an error occurs processing the message
   * @see org.apache.catalina.tribes.Channel
   */
  @Override
  public UniqueIdRemoteInterface send(
      Member[] destination, Serializable msg, int options, ErrorHandler handler)
      throws ChannelException, RemoteException, RemoteException {
    if (msg == null) throw new ChannelException("Cant send a NULL message");
    XByteBufferRemoteInterface buffer = null;
    try {
      if (destination == null || destination.length == 0)
        throw new ChannelException("No destination given");
      ChannelDataRemoteInterface data =
          gerenciadornuvem1.getChannelData(true); // generates a unique Id
      data.setAddress(getLocalMember(false));
      data.setTimestamp(System.currentTimeMillis());
      byte[] b = null;
      if (msg instanceof ByteMessageRemoteInterface) {
        b = ((ByteMessageRemoteInterface) msg).getMessage();
        options = options | SEND_OPTIONS_BYTE_MESSAGE;
      } else {
        b = gerenciadornuvem1.XByteBufferserialize(msg);
        options = options & (~SEND_OPTIONS_BYTE_MESSAGE);
      }
      data.setOptions(options);
      // XByteBuffer buffer = new XByteBuffer(b.length+128,false);
      buffer = BufferPool.getBufferPool().getBuffer(b.length + 128, false);
      buffer.append(b, 0, b.length);
      data.setMessage(buffer);
      InterceptorPayloadRemoteInterface payload = null;
      if (handler != null) {
        payload = gerenciadornuvem0.getInterceptorPayload();
        payload.setErrorHandler(handler);
      }
      getFirstInterceptor().sendMessage(destination, data, payload);
      if (Logs.getMessages().isTraceEnabled()) {
        Logs.getMessages()
            .trace(
                "GroupChannel - Sent msg:"
                    + new UniqueId(data.getUniqueId())
                    + " at "
                    + new java.sql.Timestamp(System.currentTimeMillis())
                    + " to "
                    + Arrays.toNameString(destination));
        Logs.getMessages()
            .trace(
                "GroupChannel - Send Message:" + new UniqueId(data.getUniqueId()) + " is " + msg);
      }

      return gerenciadornuvem1.getUniqueId(data.getUniqueId());
    } catch (Exception x) {
      if (x instanceof ChannelException) throw (ChannelException) x;
      throw new ChannelException(x);
    } finally {
      if (buffer != null) BufferPool.getBufferPool().returnBuffer(buffer);
    }
  }
  /**
   * Callback from the interceptor stack. <br>
   * When a message is received from a remote node, this method will be invoked by the previous
   * interceptor.<br>
   * This method can also be used to send a message to other components within the same application,
   * but its an extreme case, and you're probably better off doing that logic between the
   * applications itself.
   *
   * @param msg ChannelMessage
   */
  @Override
  public void messageReceived(ChannelMessage msg) throws RemoteException, RemoteException {
    if (msg == null) return;
    try {
      if (Logs.getMessages().isTraceEnabled()) {
        Logs.getMessages()
            .trace(
                "GroupChannel - Received msg:"
                    + new UniqueId(msg.getUniqueId())
                    + " at "
                    + new java.sql.Timestamp(System.currentTimeMillis())
                    + " from "
                    + msg.getAddress().getName());
      }

      Serializable fwd = null;
      if ((msg.getOptions() & SEND_OPTIONS_BYTE_MESSAGE) == SEND_OPTIONS_BYTE_MESSAGE) {
        fwd = gerenciadornuvem0.getByteMessage(msg.getMessage().getBytes());
      } else {
        try {
          fwd =
              gerenciadornuvem1.XByteBufferdeserialize(
                  msg.getMessage().getBytesDirect(), 0, msg.getMessage().getLength());
        } catch (Exception sx) {
          log.error("Unable to deserialize message:" + msg, sx);
          return;
        }
      }
      if (Logs.getMessages().isTraceEnabled()) {
        Logs.getMessages()
            .trace(
                "GroupChannel - Receive Message:" + new UniqueId(msg.getUniqueId()) + " is " + fwd);
      }

      // get the actual member with the correct alive time
      Member source = msg.getAddress();
      boolean rx = false;
      boolean delivered = false;
      for (int i = 0; i < channelListeners.size(); i++) {
        ChannelListener channelListener = (ChannelListener) channelListeners.get(i);
        if (channelListener != null && channelListener.accept(fwd, source)) {
          channelListener.messageReceived(fwd, source);
          delivered = true;
          // if the message was accepted by an RPC channel, that channel
          // is responsible for returning the reply, otherwise we send an absence reply
          if (channelListener instanceof RpcChannelRemoteInterface) rx = true;
        }
      } // for
      if ((!rx) && (fwd instanceof RpcMessageRemoteInterface)) {
        // if we have a message that requires a response,
        // but none was given, send back an immediate one
        sendNoRpcChannelReply((RpcMessageRemoteInterface) fwd, source);
      }
      if (Logs.getMessages().isTraceEnabled()) {
        Logs.getMessages()
            .trace(
                "GroupChannel delivered[" + delivered + "] id:" + new UniqueId(msg.getUniqueId()));
      }

    } catch (Exception x) {
      // this could be the channel listener throwing an exception, we should log it
      // as a warning.
      if (log.isWarnEnabled()) log.warn("Error receiving message:", x);
      throw new RemoteProcessException("Exception:" + x.getMessage(), x);
    }
  }
  public int doLoop(long selectTimeOut, int maxAttempts, boolean waitForAck, ChannelMessage msg)
      throws IOException, ChannelException, RemoteException {
    try {
      int completed = 0;
      int selectedKeys = selector.select(selectTimeOut);

      if (selectedKeys == 0) {
        return 0;
      }

      Iterator<SelectionKey> it = selector.selectedKeys().iterator();
      while (it.hasNext()) {
        SelectionKey sk = it.next();
        it.remove();
        int readyOps = sk.readyOps();
        sk.interestOps(sk.interestOps() & ~readyOps);
        org.apache.catalina.tribes.transport.nio.NioSenderRemoteInterface sender =
            (org.apache.catalina.tribes.transport.nio.NioSenderRemoteInterface) sk.attachment();
        try {
          if (sender.process(sk, waitForAck)) {
            completed++;
            sender.setComplete(true);
            if (Logs.getMessages().isTraceEnabled()) {
              Logs.getMessages()
                  .trace(
                      "ParallelNioSender - Sent msg:"
                          + new UniqueId(msg.getUniqueId())
                          + " at "
                          + new java.sql.Timestamp(System.currentTimeMillis())
                          + " to "
                          + sender.getDestination().getName());
            }
            SenderState.getSenderState(sender.getDestination()).setReady();
          } // end if
        } catch (Exception x) {
          if (log.isTraceEnabled()) {
            log.trace("Error while processing send to " + sender.getDestination().getName(), x);
          }
          org.apache.catalina.tribes.transport.SenderStateRemoteInterface state =
              gerenciadornuvem0.SenderStategetSenderState(sender.getDestination());
          int attempt = sender.getAttempt() + 1;
          boolean retry = (sender.getAttempt() <= maxAttempts && maxAttempts > 0);
          synchronized (state) {

            // sk.cancel();
            if (state.isSuspect()) state.setFailing();
            if (state.isReady()) {
              state.setSuspect();
              if (retry)
                log.warn(
                    "Member send is failing for:"
                        + sender.getDestination().getName()
                        + " ; Setting to suspect and retrying.");
              else
                log.warn(
                    "Member send is failing for:"
                        + sender.getDestination().getName()
                        + " ; Setting to suspect.",
                    x);
            }
          }
          if (!isConnected()) {
            log.warn(
                "Not retrying send for:"
                    + sender.getDestination().getName()
                    + "; Sender is disconnected.");
            ChannelException cx =
                gerenciadornuvem1.getChannelException(
                    "Send failed, and sender is disconnected. Not retrying.", x);
            cx.addFaultyMember(sender.getDestination(), x);
            throw cx;
          }

          byte[] data = sender.getMessage();
          if (retry) {
            try {
              sender.disconnect();
              sender.connect();
              sender.setAttempt(attempt);
              sender.setMessage(data);
            } catch (Exception ignore) {
              state.setFailing();
            }
          } else {
            ChannelException cx =
                gerenciadornuvem1.getChannelException(
                    "Send failed, attempt:" + sender.getAttempt() + " max:" + maxAttempts, x);
            cx.addFaultyMember(sender.getDestination(), x);
            throw cx;
          } // end if
        }
      }
      return completed;

    } catch (Exception excp) {
      excp.printStackTrace();
    }
    return 0;
  }