/**
   * @param destination Member[] - destination.length > 0
   * @param msg Serializable - the message to send
   * @param options 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 UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler)
      throws ChannelException {
    if (msg == null) throw new ChannelException(sm.getString("groupChannel.nullMessage"));
    XByteBuffer buffer = null;
    try {
      if (destination == null || destination.length == 0) {
        throw new ChannelException(sm.getString("groupChannel.noDestination"));
      }
      ChannelData data = new ChannelData(true); // generates a unique Id
      data.setAddress(getLocalMember(false));
      data.setTimestamp(System.currentTimeMillis());
      byte[] b = null;
      if (msg instanceof ByteMessage) {
        b = ((ByteMessage) msg).getMessage();
        options = options | SEND_OPTIONS_BYTE_MESSAGE;
      } else {
        b = XByteBuffer.serialize(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);
      InterceptorPayload payload = null;
      if (handler != null) {
        payload = new InterceptorPayload();
        payload.setErrorHandler(handler);
      }
      getFirstInterceptor().sendMessage(destination, data, payload);
      if (Logs.MESSAGES.isTraceEnabled()) {
        Logs.MESSAGES.trace(
            "GroupChannel - Sent msg:"
                + new UniqueId(data.getUniqueId())
                + " at "
                + new java.sql.Timestamp(System.currentTimeMillis())
                + " to "
                + Arrays.toNameString(destination));
        Logs.MESSAGES.trace(
            "GroupChannel - Send Message:" + new UniqueId(data.getUniqueId()) + " is " + msg);
      }

      return new UniqueId(data.getUniqueId());
    } catch (Exception x) {
      if (x instanceof ChannelException) throw (ChannelException) x;
      throw new ChannelException(x);
    } finally {
      if (buffer != null) BufferPool.getBufferPool().returnBuffer(buffer);
    }
  }
  protected static boolean memberAlive(
      Member mbr,
      byte[] msgData,
      boolean sendTest,
      boolean readTest,
      long readTimeout,
      long conTimeout,
      int optionFlag) {
    // could be a shutdown notification
    if (Arrays.equals(mbr.getCommand(), Member.SHUTDOWN_PAYLOAD)) return false;

    Socket socket = new Socket();
    try {
      InetAddress ia = InetAddress.getByAddress(mbr.getHost());
      InetSocketAddress addr = new InetSocketAddress(ia, mbr.getPort());
      socket.setSoTimeout((int) readTimeout);
      socket.connect(addr, (int) conTimeout);
      if (sendTest) {
        ChannelData data = new ChannelData(true);
        data.setAddress(mbr);
        data.setMessage(new XByteBuffer(msgData, false));
        data.setTimestamp(System.currentTimeMillis());
        int options = optionFlag | Channel.SEND_OPTIONS_BYTE_MESSAGE;
        if (readTest) options = (options | Channel.SEND_OPTIONS_USE_ACK);
        else options = (options & (~Channel.SEND_OPTIONS_USE_ACK));
        data.setOptions(options);
        byte[] message = XByteBuffer.createDataPackage(data);
        socket.getOutputStream().write(message);
        if (readTest) {
          int length = socket.getInputStream().read(message);
          return length > 0;
        }
      } // end if
      return true;
    } catch (SocketTimeoutException sx) {
      // do nothing, we couldn't connect
    } catch (ConnectException cx) {
      // do nothing, we couldn't connect
    } catch (Exception x) {
      log.error("Unable to perform failure detection check, assuming member down.", x);
    } finally {
      try {
        socket.close();
      } catch (Exception ignore) {
      }
    }
    return false;
  }