Beispiel #1
0
/**
 * Takes care of properly storing and retrieving file chunks from/to cache. Each chunk's key is
 * composed of the file path and the chunk's number. The value is a byte array, which is either
 * chunkSize bytes long or less than that in the case of the last chunk.
 *
 * @author Marko Luksa
 */
class FileChunkMapper {

  private static final Log log = LogFactory.getLog(FileChunkMapper.class);

  private final GridFile file;
  private final Cache<String, byte[]> cache;

  public FileChunkMapper(GridFile file, Cache<String, byte[]> cache) {
    this.file = file;
    this.cache = cache;
  }

  public int getChunkSize() {
    return file.getChunkSize();
  }

  public byte[] fetchChunk(int chunkNumber) {
    String key = getChunkKey(chunkNumber);
    byte[] val = cache.get(key);
    if (log.isTraceEnabled())
      log.trace("fetching key=" + key + ": " + (val != null ? val.length + " bytes" : "null"));
    return val;
  }

  public void storeChunk(int chunkNumber, byte[] buffer, int length) {
    String key = getChunkKey(chunkNumber);
    byte[] val = trim(buffer, length);
    cache.put(key, val);
    if (log.isTraceEnabled()) log.trace("put(): key=" + key + ": " + val.length + " bytes");
  }

  public void removeChunk(int chunkNumber) {
    cache.remove(getChunkKey(chunkNumber));
  }

  private byte[] trim(byte[] buffer, int length) {
    byte[] val = new byte[length];
    System.arraycopy(buffer, 0, val, 0, length);
    return val;
  }

  private String getChunkKey(int chunkNumber) {
    return getChunkKey(file.getAbsolutePath(), chunkNumber);
  }

  static String getChunkKey(String absoluteFilePath, int chunkNumber) {
    return absoluteFilePath + ".#" + chunkNumber;
  }
}
Beispiel #2
0
  @BeforeClass
  @Parameters(value = {"channel.conf", "use_blocking"})
  protected void initializeBase(
      @Optional("udp.xml") String chconf, @Optional("false") String use_blocking) throws Exception {
    log = LogFactory.getLog(this.getClass());
    Test annotation = this.getClass().getAnnotation(Test.class);
    // this should never ever happen!
    if (annotation == null) throw new Exception("Test is not marked with @Test annotation");

    StackType type = Util.getIpStackType();
    bind_addr = type == StackType.IPv6 ? "::1" : "127.0.0.1";
    this.channel_conf = chconf;
    bind_addr = Util.getProperty(new String[] {Global.BIND_ADDR}, null, "bind_addr", bind_addr);
    System.setProperty(Global.BIND_ADDR, bind_addr);
  }
/**
 * Discovery protocol based on Rackspace Cloud Files storage solution
 *
 * @author Gustavo Fernandes
 */
public class RACKSPACE_PING extends FILE_PING {

  protected static final Log log = LogFactory.getLog(RACKSPACE_PING.class);

  private static final String UKService = "https://lon.auth.api.rackspacecloud.com/v1.0";
  private static final String USService = "https://auth.api.rackspacecloud.com/v1.0";

  protected RackspaceClient rackspaceClient = null;

  @Property(description = "Rackspace username")
  protected String username = null;

  @Property(description = "Rackspace API access key", exposeAsManagedAttribute = false)
  protected String apiKey = null;

  @Property(description = "Rackspace region, either UK or US")
  protected String region = null;

  @Property(description = "Name of the root container")
  protected String container = "jgroups";

  @Override
  public void init() throws Exception {
    if (username == null) {
      throw new IllegalArgumentException("Rackspace 'username' must not be null");
    }
    if (apiKey == null) {
      throw new IllegalArgumentException("Rackspace 'apiKey' must not be null");
    }
    if (region == null || (!region.equals("UK") && !region.equals("US"))) {
      throw new IllegalArgumentException("Invalid 'region', must be UK or US");
    }

    URL authURL = new URL(region.equals("UK") ? UKService : USService);
    rackspaceClient = new RackspaceClient(authURL, username, apiKey);

    super.init();
  }

  @Override
  protected void createRootDir() {
    rackspaceClient.authenticate();
    rackspaceClient.createContainer(container);
  }

  @Override
  protected void readAll(List<Address> members, String clustername, Responses responses) {
    try {
      List<String> objects = rackspaceClient.listObjects(container);
      for (String object : objects) {
        List<PingData> list = null;
        byte[] bytes = rackspaceClient.readObject(container, object);
        if ((list = read(new ByteArrayInputStream(bytes))) == null) {
          log.warn("failed reading " + object);
          continue;
        }
        for (PingData data : list) {
          if (members == null || members.contains(data.getAddress()))
            responses.addResponse(data, data.isCoord());
          if (local_addr != null && !local_addr.equals(data.getAddress()))
            addDiscoveryResponseToCaches(
                data.getAddress(), data.getLogicalName(), data.getPhysicalAddr());
        }
      }

    } catch (Exception e) {
      log.error("Error unmarshalling object", e);
    }
  }

  @Override
  protected void write(List<PingData> list, String clustername) {
    try {
      String filename = clustername + "/" + addressToFilename(local_addr);
      ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
      write(list, out);
      byte[] data = out.toByteArray();
      rackspaceClient.createObject(container, filename, data);
    } catch (Exception e) {
      log.error("Error marshalling object", e);
    }
  }

  @Override
  protected void remove(String clustername, Address addr) {
    String fileName = clustername + "/" + addressToFilename(addr);
    rackspaceClient.deleteObject(container, fileName);
  }

  @Override
  protected void removeAll(String clustername) {
    List<String> objects = rackspaceClient.listObjects(container);
    for (String objName : objects) {
      rackspaceClient.deleteObject(container, objName);
    }
  }

  /** A thread safe Rackspace ReST client */
  static class RackspaceClient {

    private static final String ACCEPT_HEADER = "Accept";
    private static final String AUTH_HEADER = "X-Auth-User";
    private static final String AUTH_KEY_HEADER = "X-Auth-Key";
    private static final String STORAGE_TOKEN_HEADER = "X-Storage-Token";
    private static final String STORAGE_URL_HEADER = "X-Storage-Url";
    private static final String CONTENT_LENGTH_HEADER = "Content-Length";
    private final URL apiEndpoint;
    private final String username;
    private final String apiKey;

    private volatile Credentials credentials = null;

    /**
     * Constructor
     *
     * @param apiEndpoint UK or US authentication endpoint
     * @param username Rackspace username
     * @param apiKey Rackspace apiKey
     */
    public RackspaceClient(URL apiEndpoint, String username, String apiKey) {
      this.apiEndpoint = apiEndpoint;
      this.username = username;
      this.apiKey = apiKey;
    }

    /** Authenticate */
    public void authenticate() {

      HttpURLConnection urlConnection =
          new ConnBuilder(apiEndpoint)
              .addHeader(AUTH_HEADER, username)
              .addHeader(AUTH_KEY_HEADER, apiKey)
              .getConnection();

      Response response = doAuthOperation(urlConnection);

      if (response.isSuccessCode()) {
        credentials =
            new Credentials(
                response.getHeader(STORAGE_TOKEN_HEADER), response.getHeader(STORAGE_URL_HEADER));

        log.trace("Authentication successful");
      } else {
        throw new IllegalStateException(
            "Error authenticating to the service. Please check your credentials. Code = "
                + response.code);
      }
    }

    /**
     * Delete a object (=file) from the storage
     *
     * @param containerName Folder name
     * @param objectName File name
     */
    public void deleteObject(String containerName, String objectName) {
      HttpURLConnection urlConnection =
          new ConnBuilder(credentials, containerName, objectName).method("DELETE").getConnection();

      Response response = doVoidOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          deleteObject(containerName, objectName);
        } else {
          log.error(
              "Error deleting object "
                  + objectName
                  + " from container "
                  + containerName
                  + ",code = "
                  + response.code);
        }
      }
    }

    /**
     * Create a container, which is equivalent to a bucket
     *
     * @param containerName Name of the container
     */
    public void createContainer(String containerName) {
      HttpURLConnection urlConnection =
          new ConnBuilder(credentials, containerName, null).method("PUT").getConnection();

      Response response = doVoidOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          createContainer(containerName);
        } else {
          log.error("Error creating container " + containerName + " ,code = " + response.code);
        }
      }
    }

    /**
     * Create an object (=file)
     *
     * @param containerName Name of the container
     * @param objectName Name of the file
     * @param contents Binary content of the file
     */
    public void createObject(String containerName, String objectName, byte[] contents) {
      HttpURLConnection conn =
          new ConnBuilder(credentials, containerName, objectName)
              .method("PUT")
              .addHeader(CONTENT_LENGTH_HEADER, String.valueOf(contents.length))
              .getConnection();

      Response response = doSendOperation(conn, contents);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          createObject(containerName, objectName, contents);
        } else {
          log.error(
              "Error creating object "
                  + objectName
                  + " in container "
                  + containerName
                  + ",code = "
                  + response.code);
        }
      }
    }

    /**
     * Read the content of a file
     *
     * @param containerName Name of the folder
     * @param objectName name of the file
     * @return Content of the files
     */
    public byte[] readObject(String containerName, String objectName) {
      HttpURLConnection urlConnection =
          new ConnBuilder(credentials, containerName, objectName).getConnection();

      Response response = doReadOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          return readObject(containerName, objectName);
        } else {
          log.error(
              "Error reading object "
                  + objectName
                  + " from container "
                  + containerName
                  + ", code = "
                  + response.code);
        }
      }
      return response.payload;
    }

    /**
     * List files in a folder
     *
     * @param containerName Folder name
     * @return List of file names
     */
    public List<String> listObjects(String containerName) {
      HttpURLConnection urlConnection =
          new ConnBuilder(credentials, containerName, null).getConnection();

      Response response = doReadOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          return listObjects(containerName);
        } else {
          log.error("Error listing container " + containerName + ", code = " + response.code);
        }
      }
      return response.payloadAsLines();
    }

    /**
     * Do a http operation
     *
     * @param urlConnection the HttpURLConnection to be used
     * @param inputData if not null,will be written to the urlconnection.
     * @param hasOutput if true, read content back from the urlconnection
     * @return Response
     */
    private Response doOperation(
        HttpURLConnection urlConnection, byte[] inputData, boolean hasOutput) {
      Response response = null;
      InputStream inputStream = null;
      OutputStream outputStream = null;
      byte[] payload = null;
      try {
        if (inputData != null) {
          urlConnection.setDoOutput(true);
          outputStream = urlConnection.getOutputStream();
          outputStream.write(inputData);
        }
        if (hasOutput) {
          inputStream = urlConnection.getInputStream();
          payload = Util.readFileContents(urlConnection.getInputStream());
        }
        response =
            new Response(urlConnection.getHeaderFields(), urlConnection.getResponseCode(), payload);

      } catch (IOException e) {
        log.error("Error calling service", e);
      } finally {
        Util.close(inputStream);
        Util.close(outputStream);
      }
      return response;
    }

    /**
     * Do a http auth operation, will not handle 401 permission denied errors
     *
     * @param urlConnection the HttpURLConnection to be used
     * @return Response Response
     */
    private Response doAuthOperation(HttpURLConnection urlConnection) {
      return doOperation(urlConnection, null, false);
    }

    /**
     * Do a operation that does not write or read from HttpURLConnection, except for the headers
     *
     * @param urlConnection the connection
     * @return Response
     */
    private Response doVoidOperation(HttpURLConnection urlConnection) {
      return doOperation(urlConnection, null, false);
    }

    /**
     * Do a operation that writes content to the HttpURLConnection
     *
     * @param urlConnection the connection
     * @param content The content to send
     * @return Response
     */
    private Response doSendOperation(HttpURLConnection urlConnection, byte[] content) {
      return doOperation(urlConnection, content, false);
    }

    /**
     * Do a operation that reads from the httpconnection
     *
     * @param urlConnection The connections
     * @return Response
     */
    private Response doReadOperation(HttpURLConnection urlConnection) {
      return doOperation(urlConnection, null, true);
    }

    /** Build HttpURLConnections with adequate headers and method */
    private class ConnBuilder {

      private HttpURLConnection con;

      public ConnBuilder(URL url) {
        try {
          con = (HttpURLConnection) url.openConnection();
        } catch (IOException e) {
          log.error("Error building URL", e);
        }
      }

      public ConnBuilder(Credentials credentials, String container, String object) {
        try {
          String url = credentials.storageURL + "/" + container;
          if (object != null) {
            url = url + "/" + object;
          }
          con = (HttpURLConnection) new URL(url).openConnection();
          con.addRequestProperty(STORAGE_TOKEN_HEADER, credentials.authToken);
          con.addRequestProperty(ACCEPT_HEADER, "*/*");
        } catch (IOException e) {
          log.error("Error creating connection", e);
        }
      }

      public ConnBuilder method(String method) {
        try {
          con.setRequestMethod(method);
        } catch (ProtocolException e) {
          log.error("Protocol error", e);
        }
        return this;
      }

      public ConnBuilder addHeader(String key, String value) {
        con.setRequestProperty(key, value);
        return this;
      }

      public HttpURLConnection getConnection() {
        return con;
      }
    }

    /** Result of an successfully authenticated session */
    private class Credentials {
      private final String authToken;
      private final String storageURL;

      public Credentials(String authToken, String storageURL) {
        this.authToken = authToken;
        this.storageURL = storageURL;
      }
    }

    /** Response for a Rackspace API call */
    private class Response {
      private final Map<String, List<String>> headers;
      private final int code;
      private final byte[] payload;

      Response(Map<String, List<String>> headers, int code, byte[] payload) {
        this.headers = headers;
        this.code = code;
        this.payload = payload;
      }

      private String getHeader(String name) {
        return headers.get(name).get(0);
      }

      public boolean isSuccessCode() {
        return code >= 200 && code < 300;
      }

      public boolean isAuthDenied() {
        return code == 401;
      }

      public List<String> payloadAsLines() {
        List<String> lines = new ArrayList<>();
        BufferedReader in;
        try {
          String line;
          in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(payload)));

          while ((line = in.readLine()) != null) {
            lines.add(line);
          }
          in.close();
        } catch (IOException e) {
          log.error("Error reading objects", e);
        }
        return lines;
      }
    }
  }
}
Beispiel #4
0
/**
 * A Message encapsulates data sent to members of a group. It contains among other things the
 * address of the sender, the destination address, a payload (byte buffer) and a list of headers.
 * Headers are added by protocols on the sender side and removed by protocols on the receiver's
 * side.
 *
 * <p>The byte buffer can point to a reference, and we can subset it using index and length.
 * However, when the message is serialized, we only write the bytes between index and length.
 *
 * @since 2.0
 * @author Bela Ban
 */
public class Message implements Streamable {
  protected Address dest_addr;
  protected Address src_addr;

  /** The payload */
  protected byte[] buf;

  /** The index into the payload (usually 0) */
  protected int offset;

  /** The number of bytes in the buffer (usually buf.length is buf not equal to null). */
  protected int length;

  /** All headers are placed here */
  protected Headers headers;

  protected volatile short flags;

  protected volatile byte transient_flags; // transient_flags is neither marshalled nor copied

  protected static final Log log = LogFactory.getLog(Message.class);

  static final byte DEST_SET = 1;
  static final byte SRC_SET = 1 << 1;
  static final byte BUF_SET = 1 << 2;

  // =============================== Flags ====================================
  public static enum Flag {
    OOB((short) 1), // message is out-of-band
    DONT_BUNDLE((short) (1 << 1)), // don't bundle message at the transport
    NO_FC((short) (1 << 2)), // bypass flow control
    SCOPED((short) (1 << 3)), // when a message has a scope
    NO_RELIABILITY((short) (1 << 4)), // bypass UNICAST(2) and NAKACK
    NO_TOTAL_ORDER((short) (1 << 5)), // bypass total order (e.g. SEQUENCER)
    NO_RELAY((short) (1 << 6)), // bypass relaying (RELAY)
    RSVP((short) (1 << 7)); // ack of a multicast (https://issues.jboss.org/browse/JGRP-1389)

    final short value;

    Flag(short value) {
      this.value = value;
    }

    public short value() {
      return value;
    }
  }

  public static final Flag OOB = Flag.OOB;
  public static final Flag DONT_BUNDLE = Flag.DONT_BUNDLE;
  public static final Flag NO_FC = Flag.NO_FC;
  public static final Flag SCOPED = Flag.SCOPED;
  public static final Flag NO_RELIABILITY = Flag.NO_RELIABILITY;
  public static final Flag NO_TOTAL_ORDER = Flag.NO_TOTAL_ORDER;
  public static final Flag NO_RELAY = Flag.NO_RELAY;
  public static final Flag RSVP = Flag.RSVP;

  // =========================== Transient flags ==============================
  public static enum TransientFlag {
    OOB_DELIVERED((short) 1);

    final short value;

    TransientFlag(short flag) {
      value = flag;
    }

    public short value() {
      return value;
    }
  }

  public static final TransientFlag OOB_DELIVERED =
      TransientFlag.OOB_DELIVERED; // OOB which has already been delivered up the stack

  /**
   * Constructs a Message given a destination Address
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   */
  public Message(Address dest) {
    setDest(dest);
    headers = createHeaders(3);
  }

  /**
   * Constructs a Message given a destination Address, a source Address and the payload byte buffer
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   * @param src Address of sender
   * @param buf Message to be sent. Note that this buffer must not be modified (e.g. buf[0]=0 is not
   *     allowed), since we don't copy the contents on clopy() or clone().
   */
  public Message(Address dest, Address src, byte[] buf) {
    this(dest);
    setSrc(src);
    setBuffer(buf);
  }

  public Message(Address dest, byte[] buf) {
    this(dest, null, buf);
  }

  /**
   * Constructs a message. The index and length parameters allow to provide a <em>reference</em> to
   * a byte buffer, rather than a copy, and refer to a subset of the buffer. This is important when
   * we want to avoid copying. When the message is serialized, only the subset is serialized.<br>
   * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we
   * retransmit the message, it would still have a ref to the original byte[] buffer passed in as
   * argument, and so we would retransmit a changed byte[] buffer ! </em>
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   * @param src Address of sender
   * @param buf A reference to a byte buffer
   * @param offset The index into the byte buffer
   * @param length The number of bytes to be used from <tt>buf</tt>. Both index and length are
   *     checked for array index violations and an ArrayIndexOutOfBoundsException will be thrown if
   *     invalid
   */
  public Message(Address dest, Address src, byte[] buf, int offset, int length) {
    this(dest);
    setSrc(src);
    setBuffer(buf, offset, length);
  }

  public Message(Address dest, byte[] buf, int offset, int length) {
    this(dest, null, buf, offset, length);
  }

  /**
   * Constructs a Message given a destination Address, a source Address and the payload Object
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   * @param src Address of sender
   * @param obj The object will be marshalled into the byte buffer. <em>Obj has to be serializable
   *     (e.g. implementing Serializable, Externalizable or Streamable, or be a basic type (e.g.
   *     Integer, Short etc)).</em> ! The resulting buffer must not be modified (e.g. buf[0]=0 is
   *     not allowed), since we don't copy the contents on clopy() or clone().
   *     <p>
   */
  public Message(Address dest, Address src, Object obj) {
    this(dest);
    setSrc(src);
    setObject(obj);
  }

  public Message(Address dest, Object obj) {
    this(dest, null, obj);
  }

  public Message() {
    headers = createHeaders(3);
  }

  public Message(boolean create_headers) {
    if (create_headers) headers = createHeaders(3);
  }

  public Address getDest() {
    return dest_addr;
  }

  public Address dest() {
    return dest_addr;
  }

  public void setDest(Address new_dest) {
    dest_addr = new_dest;
  }

  public Message dest(Address new_dest) {
    dest_addr = new_dest;
    return this;
  }

  public Address getSrc() {
    return src_addr;
  }

  public Address src() {
    return src_addr;
  }

  public void setSrc(Address new_src) {
    src_addr = new_src;
  }

  public Message src(Address new_src) {
    src_addr = new_src;
    return this;
  }

  /**
   * Returns a <em>reference</em> to the payload (byte buffer). Note that this buffer should not be
   * modified as we do not copy the buffer on copy() or clone(): the buffer of the copied message is
   * simply a reference to the old buffer.<br>
   * Even if offset and length are used: we return the <em>entire</em> buffer, not a subset.
   */
  public byte[] getRawBuffer() {
    return buf;
  }

  /**
   * Returns a copy of the buffer if offset and length are used, otherwise a reference.
   *
   * @return byte array with a copy of the buffer.
   */
  public final byte[] getBuffer() {
    if (buf == null) return null;
    if (offset == 0 && length == buf.length) return buf;
    else {
      byte[] retval = new byte[length];
      System.arraycopy(buf, offset, retval, 0, length);
      return retval;
    }
  }

  public final Message setBuffer(byte[] b) {
    buf = b;
    if (buf != null) {
      offset = 0;
      length = buf.length;
    } else offset = length = 0;
    return this;
  }

  /**
   * Set the internal buffer to point to a subset of a given buffer
   *
   * @param b The reference to a given buffer. If null, we'll reset the buffer to null
   * @param offset The initial position
   * @param length The number of bytes
   */
  public final Message setBuffer(byte[] b, int offset, int length) {
    buf = b;
    if (buf != null) {
      if (offset < 0 || offset > buf.length) throw new ArrayIndexOutOfBoundsException(offset);
      if ((offset + length) > buf.length)
        throw new ArrayIndexOutOfBoundsException((offset + length));
      this.offset = offset;
      this.length = length;
    } else this.offset = this.length = 0;
    return this;
  }

  /**
   * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we
   * retransmit the message, it would still have a ref to the original byte[] buffer passed in as
   * argument, and so we would retransmit a changed byte[] buffer ! </em>
   */
  public final Message setBuffer(Buffer buf) {
    if (buf != null) {
      this.buf = buf.getBuf();
      this.offset = buf.getOffset();
      this.length = buf.getLength();
    }
    return this;
  }

  /** Returns the offset into the buffer at which the data starts */
  public int getOffset() {
    return offset;
  }

  /** Returns the number of bytes in the buffer */
  public int getLength() {
    return length;
  }

  /**
   * Returns a reference to the headers hashmap, which is <em>immutable</em>. Any attempt to modify
   * the returned map will cause a runtime exception
   */
  public Map<Short, Header> getHeaders() {
    return headers.getHeaders();
  }

  public String printHeaders() {
    return headers.printHeaders();
  }

  public int getNumHeaders() {
    return headers != null ? headers.size() : 0;
  }

  /**
   * Takes an object and uses Java serialization to generate the byte[] buffer which is set in the
   * message. Parameter 'obj' has to be serializable (e.g. implementing Serializable, Externalizable
   * or Streamable, or be a basic type (e.g. Integer, Short etc)).
   */
  public final Message setObject(Object obj) {
    if (obj == null) return this;
    if (obj instanceof byte[]) return setBuffer((byte[]) obj);
    if (obj instanceof Buffer) return setBuffer((Buffer) obj);
    try {
      return setBuffer(Util.objectToByteBuffer(obj));
    } catch (Exception ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  /**
   * Uses custom serialization to create an object from the buffer of the message. Note that this is
   * dangerous when using your own classloader, e.g. inside of an application server ! Most likely,
   * JGroups will use the system classloader to deserialize the buffer into an object, whereas (for
   * example) a web application will want to use the webapp's classloader, resulting in a
   * ClassCastException. The recommended way is for the application to use their own serialization
   * and only pass byte[] buffer to JGroups.
   *
   * @return
   */
  public final Object getObject() {
    try {
      return Util.objectFromByteBuffer(buf, offset, length);
    } catch (Exception ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  /**
   * Sets a number of flags in a message
   *
   * @param flags The flag or flags
   * @return A reference to the message
   */
  public Message setFlag(Flag... flags) {
    if (flags != null) for (Flag flag : flags) if (flag != null) this.flags |= flag.value();
    return this;
  }

  /**
   * Sets the flags from a short. <em>Not recommended</em> (use {@link
   * #setFlag(org.jgroups.Message.Flag...)} instead), as the internal representation of flags might
   * change anytime.
   *
   * @param flag
   * @return
   */
  public Message setFlag(short flag) {
    flags |= flag;
    return this;
  }

  /**
   * Returns the internal representation of flags. Don't use this, as the internal format might
   * change at any time ! This is only used by unit test code
   *
   * @return
   */
  public short getFlags() {
    return flags;
  }

  /**
   * Clears a number of flags in a message
   *
   * @param flags The flags
   * @return A reference to the message
   */
  public Message clearFlag(Flag... flags) {
    if (flags != null) for (Flag flag : flags) if (flag != null) this.flags &= ~flag.value();
    return this;
  }

  public static boolean isFlagSet(short flags, Flag flag) {
    return flag != null && ((flags & flag.value()) == flag.value());
  }

  /**
   * Checks if a given flag is set
   *
   * @param flag The flag
   * @return Whether of not the flag is curently set
   */
  public boolean isFlagSet(Flag flag) {
    return isFlagSet(flags, flag);
  }

  /**
   * Same as {@link #setFlag(Flag...)} except that transient flags are not marshalled
   *
   * @param flag The flag
   */
  public Message setTransientFlag(TransientFlag... flags) {
    if (flags != null)
      for (TransientFlag flag : flags) if (flag != null) transient_flags |= flag.value();
    return this;
  }

  /**
   * Atomically checks if a given flag is set and - if not - sets it. When multiple threads
   * concurrently call this method with the same flag, only one of them will be able to set the flag
   *
   * @param flag
   * @return True if the flag could be set, false if not (was already set)
   */
  public synchronized boolean setTransientFlagIfAbsent(TransientFlag flag) {
    if (isTransientFlagSet(flag)) return false;
    setTransientFlag(flag);
    return true;
  }

  public Message clearTransientFlag(TransientFlag... flags) {
    if (flags != null)
      for (TransientFlag flag : flags) if (flag != null) transient_flags &= ~flag.value();
    return this;
  }

  public boolean isTransientFlagSet(TransientFlag flag) {
    return flag != null && (transient_flags & flag.value()) == flag.value();
  }

  public short getTransientFlags() {
    return transient_flags;
  }

  public Message setScope(short scope) {
    Util.setScope(this, scope);
    return this;
  }

  public short getScope() {
    return Util.getScope(this);
  }

  /*---------------------- Used by protocol layers ----------------------*/

  /** Puts a header given an ID into the hashmap. Overwrites potential existing entry. */
  public Message putHeader(short id, Header hdr) {
    if (id < 0) throw new IllegalArgumentException("An ID of " + id + " is invalid");
    headers.putHeader(id, hdr);
    return this;
  }

  /**
   * Puts a header given a key into the map, only if the key doesn't exist yet
   *
   * @param id
   * @param hdr
   * @return the previous value associated with the specified key, or <tt>null</tt> if there was no
   *     mapping for the key. (A <tt>null</tt> return can also indicate that the map previously
   *     associated <tt>null</tt> with the key, if the implementation supports null values.)
   */
  public Header putHeaderIfAbsent(short id, Header hdr) {
    if (id <= 0) throw new IllegalArgumentException("An ID of " + id + " is invalid");
    return headers.putHeaderIfAbsent(id, hdr);
  }

  public Header getHeader(short id) {
    if (id <= 0)
      throw new IllegalArgumentException(
          "An ID of "
              + id
              + " is invalid. Add the protocol which calls "
              + "getHeader() to jg-protocol-ids.xml");
    return headers.getHeader(id);
  }
  /*---------------------------------------------------------------------*/

  public Message copy() {
    return copy(true);
  }

  /**
   * Create a copy of the message. If offset and length are used (to refer to another buffer), the
   * copy will contain only the subset offset and length point to, copying the subset into the new
   * copy.
   *
   * @param copy_buffer
   * @return Message with specified data
   */
  public Message copy(boolean copy_buffer) {
    return copy(copy_buffer, true);
  }

  /**
   * Create a copy of the message. If offset and length are used (to refer to another buffer), the
   * copy will contain only the subset offset and length point to, copying the subset into the new
   * copy.
   *
   * <p>Note that for headers, only the arrays holding references to the headers are copied, not the
   * headers themselves ! The consequence is that the headers array of the copy hold the *same*
   * references as the original, so do *not* modify the headers ! If you want to change a header,
   * copy it and call {@link Message#putHeader(short,Header)} again.
   *
   * @param copy_buffer
   * @param copy_headers Copy the headers
   * @return Message with specified data
   */
  public Message copy(boolean copy_buffer, boolean copy_headers) {
    Message retval = new Message(false);
    retval.dest_addr = dest_addr;
    retval.src_addr = src_addr;
    retval.flags = flags;
    retval.transient_flags = transient_flags;

    if (copy_buffer && buf != null) {

      // change bela Feb 26 2004: we don't resolve the reference
      retval.setBuffer(buf, offset, length);
    }

    retval.headers = copy_headers ? createHeaders(headers) : createHeaders(3);
    return retval;
  }

  /**
   * Doesn't copy any headers except for those with ID >= copy_headers_above
   *
   * @param copy_buffer
   * @param starting_id
   * @return A message with headers whose ID are >= starting_id
   */
  public Message copy(boolean copy_buffer, short starting_id) {
    return copy(copy_buffer, starting_id, (short[]) null);
  }

  /**
   * Copies a message. Copies only headers with IDs >= starting_id or IDs which are in the
   * copy_only_ids list
   *
   * @param copy_buffer
   * @param starting_id
   * @param copy_only_ids
   * @return
   */
  public Message copy(boolean copy_buffer, short starting_id, short... copy_only_ids) {
    Message retval = copy(copy_buffer, false);
    for (Map.Entry<Short, Header> entry : getHeaders().entrySet()) {
      short id = entry.getKey();
      if (id >= starting_id || containsId(id, copy_only_ids))
        retval.putHeader(id, entry.getValue());
    }
    return retval;
  }

  public Message makeReply() {
    Message retval = new Message(src_addr);
    if (dest_addr != null) retval.setSrc(dest_addr);
    return retval;
  }

  public String toString() {
    StringBuilder ret = new StringBuilder(64);
    ret.append("[dst: ");
    if (dest_addr == null) ret.append("<null>");
    else ret.append(dest_addr);
    ret.append(", src: ");
    if (src_addr == null) ret.append("<null>");
    else ret.append(src_addr);

    int size;
    if ((size = getNumHeaders()) > 0) ret.append(" (").append(size).append(" headers)");

    ret.append(", size=");
    if (buf != null && length > 0) ret.append(length);
    else ret.append('0');
    ret.append(" bytes");
    if (flags > 0) ret.append(", flags=").append(flagsToString(flags));
    if (transient_flags > 0) ret.append(", transient_flags=" + transientFlagsToString());
    ret.append(']');
    return ret.toString();
  }

  /** Tries to read an object from the message's buffer and prints it */
  public String toStringAsObject() {
    if (buf == null) return null;
    try {
      Object obj = getObject();
      return obj != null ? obj.toString() : "";
    } catch (Exception e) { // it is not an object
      return "";
    }
  }

  public String printObjectHeaders() {
    return headers.printObjectHeaders();
  }

  /* ----------------------------------- Interface Streamable  ------------------------------- */

  /**
   * Streams all members (dest and src addresses, buffer and headers) to the output stream.
   *
   * @param out
   * @throws Exception
   */
  public void writeTo(DataOutput out) throws Exception {
    byte leading = 0;

    if (dest_addr != null) leading = Util.setFlag(leading, DEST_SET);

    if (src_addr != null) leading = Util.setFlag(leading, SRC_SET);

    if (buf != null) leading = Util.setFlag(leading, BUF_SET);

    // 1. write the leading byte first
    out.write(leading);

    // 2. the flags (e.g. OOB, LOW_PRIO), skip the transient flags
    out.writeShort(flags);

    // 3. dest_addr
    if (dest_addr != null) Util.writeAddress(dest_addr, out);

    // 4. src_addr
    if (src_addr != null) Util.writeAddress(src_addr, out);

    // 5. buf
    if (buf != null) {
      out.writeInt(length);
      out.write(buf, offset, length);
    }

    // 6. headers
    int size = headers.size();
    out.writeShort(size);
    final short[] ids = headers.getRawIDs();
    final Header[] hdrs = headers.getRawHeaders();
    for (int i = 0; i < ids.length; i++) {
      if (ids[i] > 0) {
        out.writeShort(ids[i]);
        writeHeader(hdrs[i], out);
      }
    }
  }

  /**
   * Writes the message to the output stream, but excludes the dest and src addresses unless the src
   * address given as argument is different from the message's src address
   *
   * @param src
   * @param out
   * @throws Exception
   */
  public void writeToNoAddrs(Address src, DataOutputStream out) throws Exception {
    byte leading = 0;

    boolean write_src_addr = src == null || src_addr != null && !src_addr.equals(src);

    if (write_src_addr) leading = Util.setFlag(leading, SRC_SET);

    if (buf != null) leading = Util.setFlag(leading, BUF_SET);

    // 1. write the leading byte first
    out.write(leading);

    // 2. the flags (e.g. OOB, LOW_PRIO)
    out.writeShort(flags);

    // 4. src_addr
    if (write_src_addr) Util.writeAddress(src_addr, out);

    // 5. buf
    if (buf != null) {
      out.writeInt(length);
      out.write(buf, offset, length);
    }

    // 6. headers
    int size = headers.size();
    out.writeShort(size);
    final short[] ids = headers.getRawIDs();
    final Header[] hdrs = headers.getRawHeaders();
    for (int i = 0; i < ids.length; i++) {
      if (ids[i] > 0) {
        out.writeShort(ids[i]);
        writeHeader(hdrs[i], out);
      }
    }
  }

  public void readFrom(DataInput in) throws Exception {

    // 1. read the leading byte first
    byte leading = in.readByte();

    // 2. the flags
    flags = in.readShort();

    // 3. dest_addr
    if (Util.isFlagSet(leading, DEST_SET)) dest_addr = Util.readAddress(in);

    // 4. src_addr
    if (Util.isFlagSet(leading, SRC_SET)) src_addr = Util.readAddress(in);

    // 5. buf
    if (Util.isFlagSet(leading, BUF_SET)) {
      int len = in.readInt();
      buf = new byte[len];
      in.readFully(buf, 0, len);
      length = len;
    }

    // 6. headers
    int len = in.readShort();
    headers = createHeaders(len);

    short[] ids = headers.getRawIDs();
    Header[] hdrs = headers.getRawHeaders();

    for (int i = 0; i < len; i++) {
      short id = in.readShort();
      Header hdr = readHeader(in);
      ids[i] = id;
      hdrs[i] = hdr;
    }
  }

  /* --------------------------------- End of Interface Streamable ----------------------------- */

  /**
   * Returns the exact size of the marshalled message. Uses method size() of each header to compute
   * the size, so if a Header subclass doesn't implement size() we will use an approximation.
   * However, most relevant header subclasses have size() implemented correctly. (See
   * org.jgroups.tests.SizeTest).
   *
   * @return The number of bytes for the marshalled message
   */
  public long size() {
    long retval =
        Global.BYTE_SIZE // leading byte
            + Global.SHORT_SIZE; // flags
    if (dest_addr != null) retval += Util.size(dest_addr);
    if (src_addr != null) retval += Util.size(src_addr);
    if (buf != null)
      retval +=
          Global.INT_SIZE // length (integer)
              + length; // number of bytes in the buffer

    retval += Global.SHORT_SIZE; // number of headers
    retval += headers.marshalledSize();
    return retval;
  }

  /* ----------------------------------- Private methods ------------------------------- */

  public static String flagsToString(short flags) {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    if (isFlagSet(flags, Flag.OOB)) {
      first = false;
      sb.append("OOB");
    }
    if (isFlagSet(flags, Flag.DONT_BUNDLE)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("DONT_BUNDLE");
    }
    if (isFlagSet(flags, Flag.NO_FC)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_FC");
    }
    if (isFlagSet(flags, Flag.SCOPED)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("SCOPED");
    }
    if (isFlagSet(flags, Flag.NO_RELIABILITY)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_RELIABILITY");
    }
    if (isFlagSet(flags, Flag.NO_TOTAL_ORDER)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_TOTAL_ORDER");
    }
    if (isFlagSet(flags, Flag.NO_RELAY)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_RELAY");
    }
    if (isFlagSet(flags, Flag.RSVP)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("RSVP");
    }
    return sb.toString();
  }

  public String transientFlagsToString() {
    StringBuilder sb = new StringBuilder();
    if (isTransientFlagSet(TransientFlag.OOB_DELIVERED)) sb.append("OOB_DELIVERED");
    return sb.toString();
  }

  protected static void writeHeader(Header hdr, DataOutput out) throws Exception {
    short magic_number = ClassConfigurator.getMagicNumber(hdr.getClass());
    out.writeShort(magic_number);
    hdr.writeTo(out);
  }

  protected static boolean containsId(short id, short[] ids) {
    if (ids == null) return false;
    for (short tmp : ids) if (tmp == id) return true;
    return false;
  }

  protected static Header readHeader(DataInput in) throws Exception {
    short magic_number = in.readShort();
    Class clazz = ClassConfigurator.get(magic_number);
    if (clazz == null)
      throw new IllegalArgumentException(
          "magic number " + magic_number + " is not available in magic map");

    Header hdr = (Header) clazz.newInstance();
    hdr.readFrom(in);
    return hdr;
  }

  protected static Headers createHeaders(int size) {
    return size > 0 ? new Headers(size) : new Headers(3);
  }

  protected static Headers createHeaders(Headers m) {
    return new Headers(m);
  }

  /* ------------------------------- End of Private methods ---------------------------- */

}
Beispiel #5
0
/**
 * Implementation of {@link TimeScheduler}. Uses a hashed timing wheel [1].
 *
 * <p>[1] http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt
 *
 * @author Bela Ban
 */
@Experimental
@Unsupported
public class HashedTimingWheel implements TimeScheduler, Runnable {
  private final ThreadPoolExecutor pool;

  private Thread runner = null;

  private final Lock lock = new ReentrantLock();

  protected volatile boolean running;

  protected static final Log log = LogFactory.getLog(HashedTimingWheel.class);

  protected ThreadFactory timer_thread_factory = null;

  protected int wheel_size = 200; // number of ticks on the timing wheel

  protected long tick_time = 50L; // number of milliseconds a tick has

  protected final long ROTATION_TIME; // time for 1 lap

  protected final List<MyTask>[] wheel;

  protected int wheel_position =
      0; // current position of the wheel, run() advances it by one (every TICK_TIME ms)

  /** Create a scheduler that executes tasks in dynamically adjustable intervals */
  @SuppressWarnings("unchecked")
  public HashedTimingWheel() {
    ROTATION_TIME = wheel_size * tick_time;
    wheel = new List[wheel_size];
    pool =
        new ThreadPoolExecutor(
            4,
            10,
            5000,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(5000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    init();
  }

  @SuppressWarnings("unchecked")
  public HashedTimingWheel(
      ThreadFactory factory,
      int min_threads,
      int max_threads,
      long keep_alive_time,
      int max_queue_size,
      int wheel_size,
      long tick_time) {
    this.wheel_size = wheel_size;
    this.tick_time = tick_time;
    ROTATION_TIME = wheel_size * tick_time;
    wheel = new List[this.wheel_size];
    timer_thread_factory = factory;
    pool =
        new ThreadPoolExecutor(
            min_threads,
            max_threads,
            keep_alive_time,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(max_queue_size),
            factory,
            new ThreadPoolExecutor.CallerRunsPolicy());
    init();
  }

  @SuppressWarnings("unchecked")
  public HashedTimingWheel(int corePoolSize) {
    ROTATION_TIME = wheel_size * tick_time;
    wheel = (List<MyTask>[]) new List[wheel_size];
    pool =
        new ThreadPoolExecutor(
            corePoolSize,
            corePoolSize * 2,
            5000,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(5000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    init();
  }

  public void setThreadFactory(ThreadFactory factory) {
    pool.setThreadFactory(factory);
  }

  public int getMinThreads() {
    return pool.getCorePoolSize();
  }

  public void setMinThreads(int size) {
    pool.setCorePoolSize(size);
  }

  public int getMaxThreads() {
    return pool.getMaximumPoolSize();
  }

  public void setMaxThreads(int size) {
    pool.setMaximumPoolSize(size);
  }

  public long getKeepAliveTime() {
    return pool.getKeepAliveTime(TimeUnit.MILLISECONDS);
  }

  public void setKeepAliveTime(long time) {
    pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS);
  }

  public int getCurrentThreads() {
    return pool.getPoolSize();
  }

  public int getQueueSize() {
    return pool.getQueue().size();
  }

  public String dumpTimerTasks() {
    StringBuilder sb = new StringBuilder();

    lock.lock();
    try {
      for (List<MyTask> list : wheel) {
        if (!list.isEmpty()) {
          sb.append(list).append("\n");
        }
      }
    } finally {
      lock.unlock();
    }

    return sb.toString();
  }

  public void execute(Runnable task) {
    schedule(task, 0, TimeUnit.MILLISECONDS);
  }

  public Future<?> schedule(Runnable work, long delay, TimeUnit unit) {
    if (work == null) throw new NullPointerException();
    if (isShutdown() || !running) return null;

    MyTask retval = null;
    long time = TimeUnit.MILLISECONDS.convert(delay, unit); // execution time

    lock.lock();
    try {
      int num_ticks = (int) Math.max(1, ((time % ROTATION_TIME) / tick_time));
      int position = (wheel_position + num_ticks) % wheel_size;
      int rounds = (int) (time / ROTATION_TIME);
      List<MyTask> list = wheel[position];
      retval = new MyTask(work, rounds);
      list.add(retval);
    } finally {
      lock.unlock();
    }

    return retval;
  }

  public Future<?> scheduleWithFixedDelay(
      Runnable task, long initial_delay, long delay, TimeUnit unit) {
    if (task == null) throw new NullPointerException();
    if (isShutdown() || !running) return null;
    RecurringTask wrapper = new FixedIntervalTask(task, delay);
    wrapper.doSchedule(initial_delay);
    return wrapper;
  }

  public Future<?> scheduleAtFixedRate(
      Runnable task, long initial_delay, long delay, TimeUnit unit) {
    if (task == null) throw new NullPointerException();
    if (isShutdown() || !running) return null;
    RecurringTask wrapper = new FixedRateTask(task, delay);
    wrapper.doSchedule(initial_delay);
    return wrapper;
  }

  /**
   * Schedule a task for execution at varying intervals. After execution, the task will get
   * rescheduled after {@link org.jgroups.util.HashedTimingWheel.Task#nextInterval()} milliseconds.
   * The task is neve done until nextInterval() return a value <= 0 or the task is cancelled.
   *
   * @param task the task to execute Task is rescheduled relative to the last time it
   *     <i>actually</i> started execution
   *     <p><tt>false</tt>:<br>
   *     Task is scheduled relative to its <i>last</i> execution schedule. This has the effect that
   *     the time between two consecutive executions of the task remains the same.
   *     <p>Note that relative is always true; we always schedule the next execution relative to the
   *     last *actual*
   */
  public Future<?> scheduleWithDynamicInterval(Task task) {
    if (task == null) throw new NullPointerException();
    if (isShutdown() || !running) return null;
    RecurringTask task_wrapper = new DynamicIntervalTask(task);
    task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor
    return task_wrapper;
  }

  /**
   * Returns the number of tasks currently in the timer
   *
   * @return The number of tasks currently in the timer
   */
  public int size() {
    int retval = 0;

    lock.lock();
    try {
      for (List<MyTask> list : wheel) retval += list.size();
      return retval;
    } finally {
      lock.unlock();
    }
  }

  public String toString() {
    return getClass().getSimpleName();
  }

  /**
   * Stops the timer, cancelling all tasks
   *
   * @throws InterruptedException if interrupted while waiting for thread to return
   */
  public void stop() {
    stopRunner();

    List<Runnable> remaining_tasks = pool.shutdownNow();
    for (Runnable task : remaining_tasks) {
      if (task instanceof Future) {
        Future future = (Future) task;
        future.cancel(true);
      }
    }
    pool.getQueue().clear();
    try {
      pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
    }
  }

  public boolean isShutdown() {
    return pool.isShutdown();
  }

  public void run() {
    final long base_time = System.currentTimeMillis();
    long next_time, sleep_time;
    long cnt = 0;

    while (running) {
      try {
        _run();
        next_time = base_time + (++cnt * tick_time);
        sleep_time = Math.max(0, next_time - System.currentTimeMillis());
        Util.sleep(sleep_time);
      } catch (Throwable t) {
        log.error(Util.getMessage("FailedExecutingTasksS"), t);
      }
    }
  }

  protected void _run() {
    lock.lock();
    try {
      wheel_position = (wheel_position + 1) % wheel_size;
      List<MyTask> list = wheel[wheel_position];
      if (list.isEmpty()) return;
      for (Iterator<MyTask> it = list.iterator(); it.hasNext(); ) {
        MyTask tmp = it.next();
        if (tmp.getAndDecrementRound() <= 0) {
          try {
            pool.execute(tmp);
          } catch (Throwable t) {
            log.error(Util.getMessage("FailureSubmittingTaskToThreadPool"), t);
          }
          it.remove();
        }
      }
    } finally {
      lock.unlock();
    }
  }

  protected void init() {
    for (int i = 0; i < wheel.length; i++) wheel[i] = new LinkedList<>();
    startRunner();
  }

  protected void startRunner() {
    running = true;
    runner =
        timer_thread_factory != null
            ? timer_thread_factory.newThread(this, "Timer runner")
            : new Thread(this, "Timer runner");
    runner.start();
  }

  protected void stopRunner() {
    lock.lock();
    try {
      running = false;
      for (List<MyTask> list : wheel) {
        if (!list.isEmpty()) {
          for (MyTask task : list) task.cancel(true);
          list.clear();
        }
      }
    } finally {
      lock.unlock();
    }
  }

  /** Simple task wrapper, always executed by at most 1 thread. */
  protected static class MyTask implements Future, Runnable {
    protected final Runnable task;
    protected volatile boolean cancelled = false;
    protected volatile boolean done = false;
    protected MyTask next;
    protected int round;

    public MyTask(Runnable task, int round) {
      this.task = task;
      this.round = round;
    }

    public int getRound() {
      return round;
    }

    public int getAndDecrementRound() {
      return round--;
    }

    public void setRound(int round) {
      this.round = round;
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
      boolean retval = !isDone();
      cancelled = true;
      return retval;
    }

    public boolean isCancelled() {
      return cancelled;
    }

    public boolean isDone() {
      return done || cancelled;
    }

    public Object get() throws InterruptedException, ExecutionException {
      return null;
    }

    public Object get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
      return null;
    }

    public void run() {
      if (isDone()) return;
      try {
        task.run();
      } catch (Throwable t) {
        log.error(Util.getMessage("FailedExecutingTask") + task, t);
      } finally {
        done = true;
      }
    }

    public String toString() {
      return task.toString();
    }
  }

  /**
   * Task which executes multiple times. An instance of this class wraps the real task and
   * intercepts run(): when called, it forwards the call to task.run() and then schedules another
   * execution (until cancelled). The {@link #nextInterval()} method determines the time to wait
   * until the next execution.
   *
   * @param <V>
   */
  private abstract class RecurringTask<V> implements Runnable, Future<V> {
    protected final Runnable task;
    protected volatile Future<?> future; // cannot be null !
    protected volatile boolean cancelled = false;

    public RecurringTask(Runnable task) {
      this.task = task;
    }

    /**
     * The time to wait until the next execution
     *
     * @return Number of milliseconds to wait until the next execution is scheduled
     */
    protected abstract long nextInterval();

    protected boolean rescheduleOnZeroDelay() {
      return false;
    }

    public void doSchedule() {
      long next_interval = nextInterval();
      if (next_interval <= 0 && !rescheduleOnZeroDelay()) {
        if (log.isTraceEnabled())
          log.trace("task will not get rescheduled as interval is " + next_interval);
        return;
      }

      future = schedule(this, next_interval, TimeUnit.MILLISECONDS);
      if (cancelled) future.cancel(true);
    }

    public void doSchedule(long next_interval) {
      future = schedule(this, next_interval, TimeUnit.MILLISECONDS);
      if (cancelled) future.cancel(true);
    }

    public void run() {
      if (cancelled) {
        if (future != null) future.cancel(true);
        return;
      }

      try {
        task.run();
      } catch (Throwable t) {
        log.error(Util.getMessage("FailedRunningTask") + task, t);
      }
      if (!cancelled) doSchedule();
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
      boolean retval = !isDone();
      cancelled = true;
      if (future != null) future.cancel(mayInterruptIfRunning);
      return retval;
    }

    public boolean isCancelled() {
      return cancelled;
    }

    public boolean isDone() {
      return cancelled || (future == null || future.isDone());
    }

    public V get() throws InterruptedException, ExecutionException {
      return null;
    }

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
      return null;
    }

    public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled());
      return sb.toString();
    }
  }

  private class FixedIntervalTask<V> extends RecurringTask<V> {
    final long interval;

    public FixedIntervalTask(Runnable task, long interval) {
      super(task);
      this.interval = interval;
    }

    protected long nextInterval() {
      return interval;
    }
  }

  private class FixedRateTask<V> extends RecurringTask<V> {
    final long interval;
    final long first_execution;
    int num_executions = 0;

    public FixedRateTask(Runnable task, long interval) {
      super(task);
      this.interval = interval;
      this.first_execution = System.currentTimeMillis();
    }

    protected long nextInterval() {
      long target_time = first_execution + (interval * ++num_executions);
      return target_time - System.currentTimeMillis();
    }

    protected boolean rescheduleOnZeroDelay() {
      return true;
    }
  }

  private class DynamicIntervalTask<V> extends RecurringTask<V> {

    public DynamicIntervalTask(Task task) {
      super(task);
    }

    protected long nextInterval() {
      if (task instanceof Task) return ((Task) task).nextInterval();
      return 0;
    }
  }
}
Beispiel #6
0
/**
 * Discovery protocol based on Openstack Swift (object storage).
 *
 * <p>This implementation is derived from Gustavo Fernandes work on RACKSPACE_PING
 *
 * @author tsegismont
 * @since 3.1
 */
@Experimental
public class SWIFT_PING extends FILE_PING {

  private static final Log log = LogFactory.getLog(SWIFT_PING.class);

  protected SwiftClient swiftClient = null;

  @Property(description = "Authentication url")
  protected String auth_url = null;

  @Property(description = "Authentication type")
  protected String auth_type = "keystone_v_2_0";

  @Property(description = "Openstack Keystone tenant name")
  protected String tenant = null;

  @Property(description = "Username")
  protected String username = null;

  @Property(description = "Password", exposeAsManagedAttribute = false)
  protected String password = null;

  @Property(description = "Name of the root container")
  protected String container = "jgroups";

  @Override
  public void init() throws Exception {
    Utils.validateNotEmpty(auth_url, "auth_url");
    Utils.validateNotEmpty(auth_type, "auth_type");
    Utils.validateNotEmpty(username, "username");
    Utils.validateNotEmpty(password, "password");
    Utils.validateNotEmpty(container, "container");

    Authenticator authenticator = createAuthenticator();
    authenticator.validateParams();

    swiftClient = new SwiftClient(authenticator);
    // Authenticate now to record credential
    swiftClient.authenticate();

    super.init();
  }

  private Authenticator createAuthenticator() throws Exception {

    AUTH_TYPE authType = AUTH_TYPE.getByConfigName(auth_type);
    if (authType == null) {
      throw new IllegalArgumentException("Invalid 'auth_type' : " + auth_type);
    }

    URL authUrl = new URL(auth_url);
    Authenticator authenticator = null;
    switch (authType) {
      case KEYSTONE_V_2_0:
        authenticator = new Keystone_V_2_0_Auth(tenant, authUrl, username, password);
        break;

      default:
        // We shouldn't come here since we checked auth_type
        throw new IllegalStateException("Could not select authenticator");
    }
    return authenticator;
  }

  @Override
  protected void createRootDir() {
    try {
      swiftClient.createContainer(container);
    } catch (Exception e) {
      log.error("failure creating container", e);
    }
  }

  @Override
  protected void readAll(List<Address> members, String clustername, Responses responses) {
    try {
      List<String> objects = swiftClient.listObjects(container);
      for (String object : objects) {
        List<PingData> list = null;
        byte[] bytes = swiftClient.readObject(container, object);
        if ((list = read(new ByteArrayInputStream(bytes))) == null) {
          log.warn("failed reading " + object);
          continue;
        }
        for (PingData data : list) {
          if (members == null || members.contains(data.getAddress()))
            responses.addResponse(data, data.isCoord());
          if (local_addr != null && !local_addr.equals(data.getAddress()))
            addDiscoveryResponseToCaches(
                data.getAddress(), data.getLogicalName(), data.getPhysicalAddr());
        }
      }

    } catch (Exception e) {
      log.error("Error unmarshalling object", e);
    }
  }

  @Override
  protected void write(List<PingData> list, String clustername) {
    try {
      String filename = clustername + "/" + addressToFilename(local_addr);
      ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
      write(list, out);
      byte[] data = out.toByteArray();
      swiftClient.createObject(container, filename, data);
    } catch (Exception e) {
      log.error("Error marshalling object", e);
    }
  }

  @Override
  protected void remove(String clustername, Address addr) {
    String fileName = clustername + "/" + addressToFilename(addr);
    try {
      swiftClient.deleteObject(container, fileName);
    } catch (Exception e) {
      log.error("failure removing data", e);
    }
  }

  private static class HttpHeaders {

    private static final String CONTENT_TYPE_HEADER = "Content-type";

    private static final String ACCEPT_HEADER = "Accept";

    //
    // private static final String AUTH_HEADER = "X-Auth-User";
    //
    // private static final String AUTH_KEY_HEADER = "X-Auth-Key";
    //
    private static final String STORAGE_TOKEN_HEADER = "X-Storage-Token";

    //
    // private static final String STORAGE_URL_HEADER = "X-Storage-Url";

    private static final String CONTENT_LENGTH_HEADER = "Content-Length";
  }

  /** Supported Swift authentication providers */
  private static enum AUTH_TYPE {
    KEYSTONE_V_2_0("keystone_v_2_0");

    private static final Map<String, AUTH_TYPE> LOOKUP = new HashMap<String, AUTH_TYPE>();

    static {
      for (AUTH_TYPE type : EnumSet.allOf(AUTH_TYPE.class)) LOOKUP.put(type.configName, type);
    }

    private String configName;

    private AUTH_TYPE(String externalName) {
      this.configName = externalName;
    }

    public static AUTH_TYPE getByConfigName(String configName) {
      return LOOKUP.get(configName);
    }
  }

  /** Result of a successfully authenticated session */
  private static class Credentials {

    private final String authToken;

    private final String storageUrl;

    public Credentials(String authToken, String storageUrl) {
      this.authToken = authToken;
      this.storageUrl = storageUrl;
    }
  }

  /** Contract for Swift authentication providers */
  private static interface Authenticator {

    /** Validate SWIFT_PING config parameters */
    void validateParams();

    Credentials authenticate() throws Exception;
  }

  /** Openstack Keytsone v2.0 authentication provider. Thread safe implementation */
  private static class Keystone_V_2_0_Auth implements Authenticator {

    private static XPathExpression tokenIdExpression;

    private static XPathExpression publicUrlExpression;

    static {
      XPathFactory xPathFactory = XPathFactory.newInstance();
      XPath xpath = xPathFactory.newXPath();
      try {
        tokenIdExpression = xpath.compile("/access/token/@id");
        publicUrlExpression =
            xpath.compile(
                "/access/serviceCatalog/service[@type='object-store']/endpoint/@publicURL");
      } catch (XPathExpressionException e) {
        // Do nothing
      }
    }

    private String tenant;

    private URL authUrl;

    private String username;

    private String password;

    public Keystone_V_2_0_Auth(String tenant, URL authUrl, String username, String password) {
      this.tenant = tenant;
      this.authUrl = authUrl;
      this.username = username;
      this.password = password;
    }

    public void validateParams() {
      // All others params already validated
      Utils.validateNotEmpty(tenant, "tenant");
    }

    public Credentials authenticate() throws Exception {
      HttpURLConnection urlConnection =
          new ConnBuilder(authUrl)
              .addHeader(HttpHeaders.CONTENT_TYPE_HEADER, "application/json")
              .addHeader(HttpHeaders.ACCEPT_HEADER, "application/xml")
              .getConnection();

      StringBuilder jsonBuilder = new StringBuilder();
      jsonBuilder
          .append("{\"auth\": {\"tenantName\": \"")
          .append(tenant)
          .append("\", \"passwordCredentials\": {\"username\": \"")
          .append(username)
          .append("\", \"password\": \"")
          .append(password)
          .append("\"}}}");

      HttpResponse response =
          Utils.doOperation(urlConnection, jsonBuilder.toString().getBytes(), true);

      if (response.isSuccessCode()) {

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
        Document doc = builder.parse(new ByteArrayInputStream(response.payload));

        String authToken = (String) tokenIdExpression.evaluate(doc, XPathConstants.STRING);
        String storageUrl = (String) publicUrlExpression.evaluate(doc, XPathConstants.STRING);

        log.trace("Authentication successful");

        return new Credentials(authToken, storageUrl);
      } else {
        throw new IllegalStateException(
            "Error authenticating to the service. Please check your credentials. Code = "
                + response.code);
      }
    }
  }

  /** Build HttpURLConnections with adequate headers and method */
  private static class ConnBuilder {

    private HttpURLConnection con;

    public ConnBuilder(URL url) {
      try {
        con = (HttpURLConnection) url.openConnection();
      } catch (IOException e) {
        log.error("Error building URL", e);
      }
    }

    public ConnBuilder(Credentials credentials, String container, String object) {
      try {
        String url = credentials.storageUrl + "/" + container;
        if (object != null) {
          url = url + "/" + object;
        }
        con = (HttpURLConnection) new URL(url).openConnection();
      } catch (IOException e) {
        log.error("Error creating connection", e);
      }
    }

    public ConnBuilder method(String method) {
      try {
        con.setRequestMethod(method);
      } catch (ProtocolException e) {
        log.error("Protocol error", e);
      }
      return this;
    }

    public ConnBuilder addHeader(String key, String value) {
      con.setRequestProperty(key, value);
      return this;
    }

    public HttpURLConnection getConnection() {
      return con;
    }
  }

  /** Response for a Swift API call */
  private static class HttpResponse {

    // For later use
    private final Map<String, List<String>> headers;

    private final int code;

    private final byte[] payload;

    HttpResponse(Map<String, List<String>> headers, int code, byte[] payload) {
      this.headers = headers;
      this.code = code;
      this.payload = payload;
    }

    public List<String> payloadAsLines() {
      List<String> lines = new ArrayList<String>();
      BufferedReader in;
      try {
        String line;
        in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(payload)));

        while ((line = in.readLine()) != null) {
          lines.add(line);
        }
        in.close();
      } catch (IOException e) {
        log.error("Error reading objects", e);
      }
      return lines;
    }

    public boolean isSuccessCode() {
      return Utils.isSuccessCode(code);
    }

    public boolean isAuthDenied() {
      return Utils.isAuthDenied(code);
    }
  }

  /** A thread safe Swift client */
  protected static class SwiftClient {

    private Authenticator authenticator;

    private volatile Credentials credentials = null;

    /**
     * Constructor
     *
     * @param authenticator Swift auth provider
     */
    public SwiftClient(Authenticator authenticator) {
      this.authenticator = authenticator;
    }

    /**
     * Authenticate
     *
     * @throws Exception
     */
    public void authenticate() throws Exception {
      credentials = authenticator.authenticate();
    }

    /**
     * Delete a object (=file) from the storage
     *
     * @param containerName Folder name
     * @param objectName File name
     * @throws IOException
     */
    public void deleteObject(String containerName, String objectName) throws Exception {
      HttpURLConnection urlConnection =
          getConnBuilder(containerName, objectName).method("DELETE").getConnection();

      HttpResponse response = Utils.doVoidOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          deleteObject(containerName, objectName);
        } else {
          log.error(
              "Error deleting object "
                  + objectName
                  + " from container "
                  + containerName
                  + ",code = "
                  + response.code);
        }
      }
    }

    /**
     * Create a container, which is equivalent to a bucket
     *
     * @param containerName Name of the container
     * @throws IOException
     */
    public void createContainer(String containerName) throws Exception {
      HttpURLConnection urlConnection =
          getConnBuilder(containerName, null).method("PUT").getConnection();

      HttpResponse response = Utils.doVoidOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          createContainer(containerName);
        } else {
          log.error("Error creating container " + containerName + " ,code = " + response.code);
        }
      }
    }

    /**
     * Create an object (=file)
     *
     * @param containerName Name of the container
     * @param objectName Name of the file
     * @param contents Binary content of the file
     * @throws IOException
     */
    public void createObject(String containerName, String objectName, byte[] contents)
        throws Exception {
      HttpURLConnection conn =
          getConnBuilder(containerName, objectName)
              .method("PUT")
              .addHeader(HttpHeaders.CONTENT_LENGTH_HEADER, String.valueOf(contents.length))
              .getConnection();

      HttpResponse response = Utils.doSendOperation(conn, contents);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          createObject(containerName, objectName, contents);
        } else {
          log.error(
              "Error creating object "
                  + objectName
                  + " in container "
                  + containerName
                  + ",code = "
                  + response.code);
        }
      }
    }

    /**
     * Read the content of a file
     *
     * @param containerName Name of the folder
     * @param objectName name of the file
     * @return Content of the files
     * @throws IOException
     */
    public byte[] readObject(String containerName, String objectName) throws Exception {
      HttpURLConnection urlConnection = getConnBuilder(containerName, objectName).getConnection();

      HttpResponse response = Utils.doReadOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          return readObject(containerName, objectName);
        } else {
          log.error(
              "Error reading object "
                  + objectName
                  + " from container "
                  + containerName
                  + ", code = "
                  + response.code);
        }
      }
      return response.payload;
    }

    /**
     * List files in a folder
     *
     * @param containerName Folder name
     * @return List of file names
     * @throws IOException
     */
    public List<String> listObjects(String containerName) throws Exception {
      HttpURLConnection urlConnection = getConnBuilder(containerName, null).getConnection();

      HttpResponse response = Utils.doReadOperation(urlConnection);

      if (!response.isSuccessCode()) {
        if (response.isAuthDenied()) {
          log.warn("Refreshing credentials and retrying");
          authenticate();
          return listObjects(containerName);
        } else {
          log.error("Error listing container " + containerName + ", code = " + response.code);
        }
      }
      return response.payloadAsLines();
    }

    private ConnBuilder getConnBuilder(String container, String object) {
      ConnBuilder connBuilder = new ConnBuilder(credentials, container, object);
      connBuilder.addHeader(HttpHeaders.STORAGE_TOKEN_HEADER, credentials.authToken);
      connBuilder.addHeader(HttpHeaders.ACCEPT_HEADER, "*/*");
      return connBuilder;
    }
  }

  private static class Utils {

    public static void validateNotEmpty(String arg, String argname) {
      if (arg == null || arg.trim().length() == 0) {
        throw new IllegalArgumentException("'" + argname + "' cannot be empty");
      }
    }

    /**
     * Is http response code in success range ?
     *
     * @param code
     * @return
     */
    public static boolean isSuccessCode(int code) {
      return code >= 200 && code < 300;
    }

    /**
     * Is http Unauthorized response code ?
     *
     * @param code
     * @return
     */
    public static boolean isAuthDenied(int code) {
      return code == 401;
    }

    /**
     * Do a http operation
     *
     * @param urlConnection the HttpURLConnection to be used
     * @param inputData if not null,will be written to the urlconnection.
     * @param hasOutput if true, read content back from the urlconnection
     * @return Response
     * @throws IOException
     */
    public static HttpResponse doOperation(
        HttpURLConnection urlConnection, byte[] inputData, boolean hasOutput) throws IOException {
      HttpResponse response = null;
      InputStream inputStream = null;
      OutputStream outputStream = null;
      byte[] payload = null;
      try {
        if (inputData != null) {
          urlConnection.setDoOutput(true);
          outputStream = urlConnection.getOutputStream();
          outputStream.write(inputData);
        }
        /*
         * Get response code first. HttpURLConnection does not allow to
         * read inputstream if response code is not success code
         */
        int responseCode = urlConnection.getResponseCode();
        if (hasOutput && isSuccessCode(responseCode)) {
          payload = getBytes(urlConnection.getInputStream());
        }
        response = new HttpResponse(urlConnection.getHeaderFields(), responseCode, payload);
      } finally {
        Util.close(inputStream);
        Util.close(outputStream);
      }
      return response;
    }

    /**
     * Get bytes of this {@link InputStream}
     *
     * @param inputStream
     * @return
     * @throws IOException
     */
    public static byte[] getBytes(InputStream inputStream) throws IOException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      byte[] buffer = new byte[4096];
      int len;
      for (; ; ) {
        len = inputStream.read(buffer);
        if (len == -1) {
          break;
        }
        baos.write(buffer, 0, len);
      }
      return baos.toByteArray();
    }

    /**
     * Do a operation that does not write or read from HttpURLConnection, except for the headers
     *
     * @param urlConnection the connection
     * @return Response
     * @throws IOException
     */
    public static HttpResponse doVoidOperation(HttpURLConnection urlConnection) throws IOException {
      return doOperation(urlConnection, null, false);
    }

    /**
     * Do a operation that writes content to the HttpURLConnection
     *
     * @param urlConnection the connection
     * @param content The content to send
     * @return Response
     * @throws IOException
     */
    public static HttpResponse doSendOperation(HttpURLConnection urlConnection, byte[] content)
        throws IOException {
      return doOperation(urlConnection, content, false);
    }

    /**
     * Do a operation that reads from the httpconnection
     *
     * @param urlConnection The connections
     * @return Response
     * @throws IOException
     */
    public static HttpResponse doReadOperation(HttpURLConnection urlConnection) throws IOException {
      return doOperation(urlConnection, null, true);
    }
  }
}
/**
 * Generic class to test one or more protocol layers directly. Sets up a protocol stack consisting
 * of the top layer (which is essentially given by the user and is the test harness), the specified
 * protocol(s) and a bottom layer (which is automatically added), which sends all received messages
 * immediately back up the stack (swapping sender and receiver address of the message).
 *
 * @author Bela Ban
 * @author March 23 2001
 */
public class ProtocolTester {
  Protocol harness = null, top, bottom;
  String props = null;
  Configurator config = null;

  protected final Log log = LogFactory.getLog(this.getClass());

  public ProtocolTester(String prot_spec, Protocol harness) throws Exception {
    if (prot_spec == null || harness == null)
      throw new Exception("ProtocolTester(): prot_spec or harness is null");

    props = prot_spec;
    this.harness = harness;
    props = "LOOPBACK:" + props; // add a loopback layer at the bottom of the stack

    config = new Configurator();
    JChannel mock_channel = new JChannel() {};
    ProtocolStack stack = new ProtocolStack(mock_channel);
    stack.setup(Configurator.parseConfigurations(props));
    stack.insertProtocol(harness, ProtocolStack.ABOVE, stack.getTopProtocol().getClass());

    bottom = stack.getBottomProtocol();

    // has to be set after StartProtocolStack, otherwise the up and down handler threads in the
    // harness
    // will be started as well (we don't want that) !
    // top.setUpProtocol(harness);
  }

  public Vector<Protocol> getProtocols() {
    Vector<Protocol> retval = new Vector<Protocol>();
    Protocol tmp = top;
    while (tmp != null) {
      retval.add(tmp);
      tmp = tmp.getDownProtocol();
    }
    return retval;
  }

  public String getProtocolSpec() {
    return props;
  }

  public Protocol getBottom() {
    return bottom;
  }

  public Protocol getTop() {
    return top;
  }

  public void start() throws Exception {
    Protocol p;
    if (harness != null) {
      p = harness;
      while (p != null) {
        p.start();
        p = p.getDownProtocol();
      }
    } else if (top != null) {
      p = top;
      while (p != null) {
        p.start();
        p = p.getDownProtocol();
      }
    }
  }

  public void stop() {
    Protocol p;
    if (harness != null) {
      List<Protocol> protocols = new LinkedList<Protocol>();
      p = harness;
      while (p != null) {
        protocols.add(p);
        p.stop();
        p = p.getDownProtocol();
      }
      p = harness;
      while (p != null) {
        p.destroy();
        p = p.getDownProtocol();
      }
    } else if (top != null) {
      p = top;
      List<Protocol> protocols = new LinkedList<Protocol>();
      while (p != null) {
        protocols.add(p);
        p.stop();
        p = p.getDownProtocol();
      }
      p = top;
      while (p != null) {
        p.destroy();
        p = p.getDownProtocol();
      }
    }
  }

  private final Protocol getBottomProtocol(Protocol top) {
    Protocol tmp;

    if (top == null) return null;

    tmp = top;
    while (tmp.getDownProtocol() != null) tmp = tmp.getDownProtocol();
    return tmp;
  }

  public static void main(String[] args) {
    String props;
    ProtocolTester t;
    Harness h;

    if (args.length < 1 || args.length > 2) {
      System.out.println("ProtocolTester <protocol stack spec> [-trace]");
      return;
    }
    props = args[0];

    try {
      h = new Harness();
      t = new ProtocolTester(props, h);
      System.out.println("protocol specification is " + t.getProtocolSpec());
      h.down(new Event(Event.BECOME_SERVER));
      for (int i = 0; i < 5; i++) {
        System.out.println("Sending msg #" + i);
        h.down(new Event(Event.MSG, new Message(null, null, "Hello world #" + i)));
      }
      Util.sleep(500);
      t.stop();
    } catch (Exception ex) {
      System.err.println(ex);
    }
  }

  private static class Harness extends Protocol {

    public String getName() {
      return "Harness";
    }

    public Object up(Event evt) {
      System.out.println("Harness.up(): " + evt);
      return null;
    }
  }
}
Beispiel #8
0
/** @author Bela Ban */
public class GridInputStream extends InputStream {
  final Cache<String, byte[]> cache;
  final int chunk_size;
  final String name;
  protected final GridFile file; // file representing this input stream
  int index = 0; // index into the file for writing
  int local_index = 0;
  byte[] current_buffer = null;
  boolean end_reached = false;
  static final Log log = LogFactory.getLog(GridInputStream.class);

  GridInputStream(GridFile file, Cache<String, byte[]> cache, int chunk_size)
      throws FileNotFoundException {
    this.file = file;
    this.name = file.getPath();
    this.cache = cache;
    this.chunk_size = chunk_size;
  }

  public int read() throws IOException {
    int bytes_remaining_to_read = getBytesRemainingInChunk();
    if (bytes_remaining_to_read == 0) {
      if (end_reached) return -1;
      current_buffer = fetchNextChunk();
      local_index = 0;
      if (current_buffer == null) return -1;
      else if (current_buffer.length < chunk_size) end_reached = true;
      bytes_remaining_to_read = getBytesRemainingInChunk();
    }
    int retval = current_buffer[local_index++];
    index++;
    return retval;
  }

  @Override
  public int read(byte[] b) throws IOException {
    return read(b, 0, b.length);
  }

  @Override
  public int read(byte[] b, int off, int len) throws IOException {
    int bytes_read = 0;
    while (len > 0) {
      int bytes_remaining_to_read = getBytesRemainingInChunk();
      if (bytes_remaining_to_read == 0) {
        if (end_reached) return bytes_read > 0 ? bytes_read : -1;
        current_buffer = fetchNextChunk();
        local_index = 0;
        if (current_buffer == null) return bytes_read > 0 ? bytes_read : -1;
        else if (current_buffer.length < chunk_size) end_reached = true;
        bytes_remaining_to_read = getBytesRemainingInChunk();
      }
      int bytes_to_read = Math.min(len, bytes_remaining_to_read);
      // bytes_to_read=Math.min(bytes_to_read, current_buffer.length - local_index);
      System.arraycopy(current_buffer, local_index, b, off, bytes_to_read);
      local_index += bytes_to_read;
      off += bytes_to_read;
      len -= bytes_to_read;
      bytes_read += bytes_to_read;
      index += bytes_to_read;
    }

    return bytes_read;
  }

  @Override
  public long skip(long n) throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public int available() throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void close() throws IOException {
    local_index = index = 0;
    end_reached = false;
  }

  private int getBytesRemainingInChunk() {
    // return chunk_size - local_index;
    return current_buffer == null ? 0 : current_buffer.length - local_index;
  }

  private byte[] fetchNextChunk() {
    int chunk_number = getChunkNumber();
    String key = name + ".#" + chunk_number;
    byte[] val = cache.get(key);
    if (log.isTraceEnabled())
      log.trace(
          "fetching index="
              + index
              + ", key="
              + key
              + ": "
              + (val != null ? val.length + " bytes" : "null"));
    return val;
  }

  private int getChunkNumber() {
    return index / chunk_size;
  }
}
public class XmlConfigurator implements ProtocolStackConfigurator {
  private static final String JAXP_SCHEMA_LANGUAGE =
      "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
  private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
  private final List<ProtocolConfiguration> configuration = new ArrayList<ProtocolConfiguration>();
  protected static final Log log = LogFactory.getLog(XmlConfigurator.class);

  protected XmlConfigurator(List<ProtocolConfiguration> protocols) {
    configuration.addAll(protocols);
  }

  public static XmlConfigurator getInstance(URL url) throws java.io.IOException {
    return getInstance(url, null);
  }

  public static XmlConfigurator getInstance(InputStream stream) throws java.io.IOException {
    return getInstance(stream, null);
  }

  public static XmlConfigurator getInstance(Element el) throws java.io.IOException {
    return parse(el);
  }

  public static XmlConfigurator getInstance(URL url, Boolean validate) throws java.io.IOException {
    InputStream is = url.openStream();
    try {
      return getInstance(is, validate);
    } finally {
      Util.close(is);
    }
  }

  public static XmlConfigurator getInstance(InputStream stream, Boolean validate)
      throws java.io.IOException {
    return parse(stream, validate);
  }

  /**
   * @param convert If false: print old plain output, else print new XML format
   * @return String with protocol stack in specified format
   */
  public String getProtocolStackString(boolean convert) {
    StringBuilder buf = new StringBuilder();
    Iterator<ProtocolConfiguration> it = configuration.iterator();
    if (convert) buf.append("<config>\n");
    while (it.hasNext()) {
      ProtocolConfiguration d = it.next();
      if (convert) buf.append("    <");
      buf.append(d.getProtocolString(convert));
      if (convert) buf.append("/>");
      if (it.hasNext()) {
        if (convert) buf.append('\n');
        else buf.append(':');
      }
    }
    if (convert) buf.append("\n</config>");
    return buf.toString();
  }

  public String getProtocolStackString() {
    return getProtocolStackString(false);
  }

  public List<ProtocolConfiguration> getProtocolStack() {
    return configuration;
  }

  protected static XmlConfigurator parse(InputStream stream, Boolean validate)
      throws java.io.IOException {
    /**
     * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the code below is pretty
     * amateurish... But it seems to work, and it is executed only on startup, so no perf loss on
     * the critical path. If somebody wants to improve this, please be my guest.
     */
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      boolean validation = false;
      String tmp = Util.getProperty(new String[] {Global.XML_VALIDATION}, null, null, false, null);
      if (tmp != null) {
        validation = Boolean.valueOf(tmp).booleanValue();
      } else if (validate != null) {
        validation = validate.booleanValue();
      }
      factory.setValidating(validation);
      factory.setNamespaceAware(validation);
      if (validation) {
        factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
      }

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(
          new EntityResolver() {

            public InputSource resolveEntity(String publicId, String systemId) throws IOException {
              if (systemId != null
                  && systemId.startsWith("http://www.jgroups.org/schema/JGroups-")) {
                String schemaName = systemId.substring("http://www.jgroups.org/".length());
                InputStream schemaIs = getAsInputStreamFromClassLoader(schemaName);
                if (schemaIs == null) {
                  throw new IOException("Schema not found from classloader: " + schemaName);
                }
                InputSource source = new InputSource(schemaIs);
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                return source;
              }
              return null;
            }
          });
      // Use AtomicReference to allow make variable final, not for atomicity
      // We store only last exception
      final AtomicReference<SAXParseException> exceptionRef =
          new AtomicReference<SAXParseException>();
      builder.setErrorHandler(
          new ErrorHandler() {

            public void warning(SAXParseException exception) throws SAXException {
              log.warn("Warning during parse", exception);
            }

            public void fatalError(SAXParseException exception) throws SAXException {
              exceptionRef.set(exception);
            }

            public void error(SAXParseException exception) throws SAXException {
              exceptionRef.set(exception);
            }
          });
      Document document = builder.parse(stream);
      if (exceptionRef.get() != null) {
        throw exceptionRef.get();
      }

      // The root element of the document should be the "config" element,
      // but the parser(Element) method checks this so a check is not
      // needed here.
      Element configElement = document.getDocumentElement();
      return parse(configElement);
    } catch (Exception x) {
      throw new IOException(Util.getMessage("ParseError", x.getLocalizedMessage()));
    }
  }

  private static InputStream getAsInputStreamFromClassLoader(String filename) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    InputStream is = cl == null ? null : cl.getResourceAsStream(filename);
    if (is == null) {
      // check system class loader
      is = XmlConfigurator.class.getClassLoader().getResourceAsStream(filename);
    }
    return is;
  }

  protected static XmlConfigurator parse(Element root_element) throws java.io.IOException {
    XmlConfigurator configurator = null;
    final LinkedList<ProtocolConfiguration> prot_data = new LinkedList<ProtocolConfiguration>();

    /**
     * CAUTION: crappy code ahead ! I (bela) am not an XML expert, so the code below is pretty
     * amateurish... But it seems to work, and it is executed only on startup, so no perf loss on
     * the critical path. If somebody wants to improve this, please be my guest.
     */
    try {
      String root_name = root_element.getNodeName();
      if (!"config".equals(root_name.trim().toLowerCase()))
        throw new IOException("the configuration does not start with a <config> element");

      NodeList prots = root_element.getChildNodes();
      for (int i = 0; i < prots.getLength(); i++) {
        Node node = prots.item(i);
        if (node.getNodeType() != Node.ELEMENT_NODE) continue;

        Element tag = (Element) node;
        String protocol = tag.getTagName();
        Map<String, String> params = new HashMap<String, String>();

        NamedNodeMap attrs = tag.getAttributes();
        int attrLength = attrs.getLength();
        for (int a = 0; a < attrLength; a++) {
          Attr attr = (Attr) attrs.item(a);
          String name = attr.getName();
          String value = attr.getValue();
          params.put(name, value);
        }
        ProtocolConfiguration cfg = new ProtocolConfiguration(protocol, params);
        prot_data.add(cfg);
      }
      configurator = new XmlConfigurator(prot_data);
    } catch (Exception x) {
      if (x instanceof java.io.IOException) throw (java.io.IOException) x;
      else {
        IOException tmp = new IOException();
        tmp.initCause(x);
        throw tmp;
      }
    }
    return configurator;
  }

  public static void main(String[] args) throws Exception {
    String input_file = null;
    XmlConfigurator conf;
    boolean old_format = false;

    for (int i = 0; i < args.length; i++) {
      if (args[i].equals("-old")) {
        old_format = true;
        continue;
      }
      if (args[i].equals("-file")) {
        input_file = args[++i];
        continue;
      }
      help();
      return;
    }

    if (input_file != null) {
      InputStream input = null;

      try {
        input = new FileInputStream(new File(input_file));
      } catch (Throwable t) {
      }
      if (input == null) {
        try {
          input = new URL(input_file).openStream();
        } catch (Throwable t) {
        }
      }

      if (input == null)
        input = Thread.currentThread().getContextClassLoader().getResourceAsStream(input_file);

      if (old_format) {
        String cfg = inputAsString(input);
        List<ProtocolConfiguration> tmp = Configurator.parseConfigurations(cfg);
        System.out.println(dump(tmp));
      } else {
        conf = XmlConfigurator.getInstance(input);
        String tmp = conf.getProtocolStackString();
        System.out.println("\n" + tmp);
      }
    } else {
      log.error("no input file given");
    }
  }

  private static String dump(Collection<ProtocolConfiguration> configs) {
    StringBuilder sb = new StringBuilder();
    String indent = "  ";
    sb.append("<config>\n");

    for (ProtocolConfiguration cfg : configs) {
      sb.append(indent).append("<").append(cfg.getProtocolName());
      Map<String, String> props = cfg.getProperties();
      if (props.isEmpty()) {
        sb.append(" />\n");
      } else {
        sb.append("\n").append(indent).append(indent);
        for (Map.Entry<String, String> entry : props.entrySet()) {
          String key = entry.getKey();
          String val = entry.getValue();
          key = trim(key);
          val = trim(val);
          sb.append(key).append("=\"").append(val).append("\" ");
        }
        sb.append(" />\n");
      }
    }

    sb.append("</config>\n");
    return sb.toString();
  }

  private static String trim(String val) {
    String retval = "";
    int index;

    val = val.trim();
    while (true) {
      index = val.indexOf('\n');
      if (index == -1) {
        retval += val;
        break;
      }
      retval += val.substring(0, index);
      val = val.substring(index + 1);
    }

    return retval;
  }

  private static String inputAsString(InputStream input) throws IOException {
    int len = input.available();
    byte[] buf = new byte[len];
    input.read(buf, 0, len);
    return new String(buf);
  }

  public static String replace(String input, final String expr, String replacement) {
    StringBuilder sb = new StringBuilder();
    int new_index = 0, index = 0, len = expr.length(), input_len = input.length();

    while (true) {
      new_index = input.indexOf(expr, index);
      if (new_index == -1) {
        sb.append(input.substring(index, input_len));
        break;
      }
      sb.append(input.substring(index, new_index));
      sb.append(replacement);
      index = new_index + len;
    }

    return sb.toString();
  }

  static void help() {
    System.out.println("XmlConfigurator -file <input XML file> [-old]");
    System.out.println("(-old: converts old (plain-text) input format into new XML format)");
  }
}
Beispiel #10
0
/**
 * Handles merging. Called by CoordGmsImpl and ParticipantGmsImpl
 *
 * @author Bela Ban
 */
public class Merger {
  private final GMS gms;
  private final Log log = LogFactory.getLog(getClass());

  private final MergeTask merge_task = new MergeTask();

  /** For MERGE_REQ/MERGE_RSP correlation, contains MergeData elements */
  private final ResponseCollector<MergeData> merge_rsps = new ResponseCollector<MergeData>();

  /** For GET_DIGEST / DIGEST_RSP correlation */
  private final ResponseCollector<Digest> digest_collector = new ResponseCollector<Digest>();

  /** To serialize access to merge_id */
  private final Lock merge_lock = new ReentrantLock();

  @GuardedBy("merge_lock")
  private MergeId merge_id = null;

  protected final BoundedList<MergeId> merge_id_history = new BoundedList<MergeId>(20);

  @GuardedBy("merge_killer_lock")
  private Future<?> merge_killer = null;

  private final Lock merge_killer_lock = new ReentrantLock();

  public Merger(GMS gms) {
    this.gms = gms;
  }

  public String getMergeIdAsString() {
    return merge_id != null ? merge_id.toString() : null;
  }

  public String getMergeIdHistory() {
    return merge_id_history.toString();
  }

  /**
   * Invoked upon receiving a MERGE event from the MERGE layer. Starts the merge protocol. See
   * description of protocol in DESIGN.
   *
   * @param views A List of <em>different</em> views detected by the merge protocol
   */
  public void merge(Map<Address, View> views) {
    if (isMergeInProgress()) {
      if (log.isTraceEnabled())
        log.trace(gms.local_addr + ": merge is already running (merge_id=" + merge_id + ")");
      return;
    }

    // we need the merge *coordinators* not merge participants because not everyone can lead a merge
    // !
    Collection<Address> coords = Util.determineActualMergeCoords(views);
    if (coords.isEmpty()) {
      log.error(
          gms.local_addr
              + ": unable to determine merge leader from "
              + views
              + "; not starting a merge");
      return;
    }

    Membership tmp =
        new Membership(coords); // establish a deterministic order, so that coords can elect leader
    tmp.sort();
    Address merge_leader = tmp.elementAt(0);
    if (merge_leader.equals(gms.local_addr)) {
      if (log.isDebugEnabled()) {
        Collection<Address> merge_participants = Util.determineMergeParticipants(views);
        log.debug(
            "I ("
                + gms.local_addr
                + ") will be the leader. Starting the merge task for "
                + merge_participants.size()
                + " coords");
      }
      merge_task.start(views);
    } else {
      if (log.isTraceEnabled())
        log.trace(
            "I ("
                + gms.local_addr
                + ") am not the merge leader, "
                + "waiting for merge leader ("
                + merge_leader
                + ") to initiate merge");
    }
  }

  /**
   * Get the view and digest and send back both (MergeData) in the form of a MERGE_RSP to the
   * sender. If a merge is already in progress, send back a MergeData with the merge_rejected field
   * set to true.
   *
   * @param sender The address of the merge leader
   * @param merge_id The merge ID
   * @param mbrs The set of members from which we expect responses
   */
  public void handleMergeRequest(
      Address sender, MergeId merge_id, Collection<? extends Address> mbrs) {
    try {
      _handleMergeRequest(sender, merge_id, mbrs);
    } catch (Throwable t) {
      cancelMerge(merge_id);
      sendMergeRejectedResponse(sender, merge_id);
    }
  }

  protected void _handleMergeRequest(
      Address sender, MergeId merge_id, Collection<? extends Address> mbrs) throws Exception {
    boolean success = matchMergeId(merge_id) || setMergeId(null, merge_id);
    if (!success)
      throw new Exception(
          "merge " + this.merge_id + " is already in progress, received merge-id=" + merge_id);

    /* Clears the view handler queue and discards all JOIN/LEAVE/MERGE requests until after the MERGE  */
    // gms.getViewHandler().suspend();
    if (log.isTraceEnabled())
      log.trace(
          gms.local_addr
              + ": got merge request from "
              + sender
              + ", merge_id="
              + merge_id
              + ", mbrs="
              + mbrs);

    // merge the membership of the current view with mbrs
    List<Address> members = new LinkedList<Address>();
    if (mbrs
        != null) { // didn't use a set because we didn't want to change the membership order at this
      // time (although not incorrect)
      for (Address mbr : mbrs) {
        if (!members.contains(mbr)) members.add(mbr);
      }
    }

    ViewId tmp_vid = gms.getViewId();
    if (tmp_vid != null) tmp_vid = tmp_vid.copy();
    if (tmp_vid == null) throw new Exception("view ID is null; cannot return merge response");

    View view = new View(tmp_vid, new ArrayList<Address>(members));

    // [JGRP-524] - FLUSH and merge: flush doesn't wrap entire merge process
    // [JGRP-770] - Concurrent startup of many channels doesn't stabilize
    // [JGRP-700] - FLUSH: flushing should span merge

    /*if flush is in stack, let this coordinator flush its cluster island */
    if (gms.flushProtocolInStack && !gms.startFlush(view)) throw new Exception("flush failed");

    // we still need to fetch digests from all members, and not just return our own digest
    // (https://issues.jboss.org/browse/JGRP-948)
    Digest digest = fetchDigestsFromAllMembersInSubPartition(members, merge_id);
    if (digest == null || digest.size() == 0)
      throw new Exception(
          "failed fetching digests from subpartition members; dropping merge response");

    sendMergeResponse(sender, view, digest, merge_id);
  }

  public void handleMergeResponse(MergeData data, MergeId merge_id) {
    if (!matchMergeId(merge_id)) {
      if (log.isTraceEnabled())
        log.trace(
            gms.local_addr
                + ": this.merge_id ("
                + this.merge_id
                + ") is different from merge_id "
                + merge_id
                + " sent by "
                + data.getSender()
                + " as merge response, discarding it");
      return;
    }
    merge_rsps.add(data.getSender(), data);
  }

  /**
   * If merge_id is not equal to this.merge_id then discard. Else cast the view/digest to all
   * members of this group.
   */
  public void handleMergeView(final MergeData data, final MergeId merge_id) {
    if (!matchMergeId(merge_id)) {
      if (log.isTraceEnabled())
        log.trace(
            gms.local_addr
                + ": merge_ids (mine: "
                + this.merge_id
                + ", received: "
                + merge_id
                + ") don't match; merge view "
                + data.view.getViewId()
                + " is discarded");
      return;
    }

    // only send to our *current* members, if we have A and B being merged (we are B), then we would
    // *not*
    // want to block on a VIEW_ACK from A because A doesn't see us in the pre-merge view yet and
    // discards the view
    List<Address> newViewMembers = new ArrayList<Address>(data.view.getMembers());
    newViewMembers.removeAll(gms.members.getMembers());

    try {
      gms.castViewChange(data.view, data.digest, null, newViewMembers);
      // if we have flush in stack send ack back to merge coordinator
      if (gms.flushProtocolInStack) { // [JGRP-700] - FLUSH: flushing should span merge
        Message ack =
            new Message(data.getSender()).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL);
        GMS.GmsHeader ack_hdr = new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW_OK);
        ack.putHeader(gms.getId(), ack_hdr);
        gms.getDownProtocol().down(new Event(Event.MSG, ack));
      }
    } finally {
      cancelMerge(merge_id);
    }
  }

  public void handleMergeCancelled(MergeId merge_id) {
    try {
      gms.stopFlush();
    } catch (Throwable t) {
      log.error("stop flush failed", t);
    }
    if (log.isTraceEnabled()) log.trace(gms.local_addr + ": merge " + merge_id + " is cancelled");
    cancelMerge(merge_id);
  }

  public void handleDigestResponse(Address sender, Digest digest) {
    digest_collector.add(sender, digest);
  }

  /**
   * Removes all members from a given view which don't have us in their view
   * (https://jira.jboss.org/browse/JGRP-1061). Example:
   *
   * <pre>
   * A: AB
   * B: AB
   * C: ABC
   * </pre>
   *
   * becomes
   *
   * <pre>
   * A: AB
   * B: AB
   * C: C // A and B don't have C in their views
   * </pre>
   *
   * @param map A map of members and their associated views
   */
  public static void sanitizeViews(Map<Address, View> map) {
    if (map == null) return;
    for (Map.Entry<Address, View> entry : map.entrySet()) {
      Address key = entry.getKey();
      List<Address> members = new ArrayList<Address>(entry.getValue().getMembers());
      boolean modified = false;
      for (Iterator<Address> it = members.iterator(); it.hasNext(); ) {
        Address val = it.next();
        if (val.equals(key)) // we can always talk to ourself !
        continue;
        View view = map.get(val);
        final Collection<Address> tmp_mbrs = view != null ? view.getMembers() : null;
        if (tmp_mbrs != null && !tmp_mbrs.contains(key)) {
          it.remove();
          modified = true;
        }
      }
      if (modified) {
        View old_view = entry.getValue();
        entry.setValue(new View(old_view.getVid(), members));
      }
    }
  }

  /** Send back a response containing view and digest to sender */
  private void sendMergeResponse(Address sender, View view, Digest digest, MergeId merge_id) {
    Message msg = new Message(sender).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL);
    GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP);
    hdr.merge_id = merge_id;
    hdr.view = view;
    hdr.my_digest = digest;
    msg.putHeader(gms.getId(), hdr);
    if (log.isTraceEnabled()) log.trace(gms.local_addr + ": sending merge response=" + hdr);
    gms.getDownProtocol().down(new Event(Event.MSG, msg));
  }

  /**
   * Sends the new view and digest to all subgroup coordinors in coords. Each coord will in turn
   *
   * <ol>
   *   <li>broadcast the new view and digest to all the members of its subgroup (MergeView)
   *   <li>on reception of the view, if it is a MergeView, each member will set the digest and
   *       install the new view
   * </ol>
   */
  private void sendMergeView(
      Collection<Address> coords, MergeData combined_merge_data, MergeId merge_id) {
    if (coords == null || combined_merge_data == null) return;

    View view = combined_merge_data.view;
    Digest digest = combined_merge_data.digest;
    if (view == null || digest == null) {
      if (log.isErrorEnabled())
        log.error("view or digest is null, cannot send consolidated merge view/digest");
      return;
    }

    int size = 0;
    if (gms.flushProtocolInStack) {
      gms.merge_ack_collector.reset(coords);
      size = gms.merge_ack_collector.size();
    }

    long start = System.currentTimeMillis();
    for (Address coord : coords) {
      Message msg = new Message(coord);
      GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.INSTALL_MERGE_VIEW);
      hdr.view = view;
      hdr.my_digest = digest;
      hdr.merge_id = merge_id;
      msg.putHeader(gms.getId(), hdr);
      gms.getDownProtocol().down(new Event(Event.MSG, msg));
    }

    // [JGRP-700] - FLUSH: flushing should span merge
    // if flush is in stack wait for acks from separated island coordinators
    if (gms.flushProtocolInStack) {
      try {
        gms.merge_ack_collector.waitForAllAcks(gms.view_ack_collection_timeout);
        if (log.isTraceEnabled())
          log.trace(
              gms.local_addr
                  + ": received all ACKs ("
                  + size
                  + ") for merge view "
                  + view
                  + " in "
                  + (System.currentTimeMillis() - start)
                  + "ms");
      } catch (TimeoutException e) {
        log.warn(
            gms.local_addr
                + ": failed to collect all ACKs ("
                + size
                + ") for merge view "
                + view
                + " after "
                + gms.view_ack_collection_timeout
                + "ms, missing ACKs from "
                + gms.merge_ack_collector.printMissing());
      }
    }
  }

  protected void sendMergeRejectedResponse(Address sender, MergeId merge_id) {
    Message msg = new Message(sender).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL);
    GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.MERGE_RSP);
    hdr.merge_rejected = true;
    hdr.merge_id = merge_id;
    msg.putHeader(gms.getId(), hdr);
    gms.getDownProtocol().down(new Event(Event.MSG, msg));
  }

  private void sendMergeCancelledMessage(Collection<Address> coords, MergeId merge_id) {
    if (coords == null || merge_id == null) return;

    for (Address coord : coords) {
      Message msg = new Message(coord);
      // msg.setFlag(Message.Flag.OOB);
      GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.CANCEL_MERGE);
      hdr.merge_id = merge_id;
      msg.putHeader(gms.getId(), hdr);
      gms.getDownProtocol().down(new Event(Event.MSG, msg));
    }
  }

  /**
   * Multicasts a GET_DIGEST_REQ to all current members and waits for all responses (GET_DIGEST_RSP)
   * or N ms.
   *
   * @return
   */
  private Digest fetchDigestsFromAllMembersInSubPartition(
      List<Address> current_mbrs, MergeId merge_id) {

    // Optimization: if we're the only member, we don't need to multicast the get-digest message
    if (current_mbrs == null
        || current_mbrs.size() == 1 && current_mbrs.get(0).equals(gms.local_addr))
      return (Digest) gms.getDownProtocol().down(new Event(Event.GET_DIGEST, gms.local_addr));

    GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.GET_DIGEST_REQ);
    hdr.merge_id = merge_id;
    Message get_digest_req =
        new Message().setFlag(Message.Flag.OOB, Message.Flag.INTERNAL).putHeader(gms.getId(), hdr);

    long max_wait_time =
        gms.merge_timeout / 2; // gms.merge_timeout is guaranteed to be > 0, verified in init()
    digest_collector.reset(current_mbrs);

    gms.getDownProtocol().down(new Event(Event.MSG, get_digest_req));

    // add my own digest first - the get_digest_req needs to be sent first *before* getting our own
    // digest, so
    // we have that message in our digest !
    Digest digest =
        (Digest) gms.getDownProtocol().down(new Event(Event.GET_DIGEST, gms.local_addr));
    digest_collector.add(gms.local_addr, digest);
    digest_collector.waitForAllResponses(max_wait_time);
    if (log.isTraceEnabled()) {
      if (digest_collector.hasAllResponses())
        log.trace(gms.local_addr + ": fetched all digests for " + current_mbrs);
      else
        log.trace(
            gms.local_addr
                + ": fetched incomplete digests (after timeout of "
                + max_wait_time
                + ") ms for "
                + current_mbrs);
    }
    Map<Address, Digest> responses = new HashMap<Address, Digest>(digest_collector.getResults());
    MutableDigest retval = new MutableDigest(responses.size());
    for (Digest dig : responses.values()) {
      if (dig != null) retval.add(dig);
    }
    return retval;
  }

  /**
   * Fetches the digests from all members and installs them again. Used only for diagnosis and
   * support; don't use this otherwise !
   */
  void fixDigests() {
    Digest digest = fetchDigestsFromAllMembersInSubPartition(gms.view.getMembers(), null);
    Message msg = new Message();
    GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.INSTALL_DIGEST);
    hdr.my_digest = digest;
    msg.putHeader(gms.getId(), hdr);
    gms.getDownProtocol().down(new Event(Event.MSG, msg));
  }

  void stop() {
    merge_task.stop();
  }

  void cancelMerge(MergeId id) {
    if (setMergeId(id, null)) {
      merge_task.stop();
      stopMergeKiller();
      merge_rsps.reset();
      gms.getViewHandler().resume();
      gms.getDownProtocol().down(new Event(Event.RESUME_STABLE));
    }
  }

  boolean isMergeTaskRunning() {
    return merge_task.isRunning();
  }

  boolean isMergeKillerTaskRunning() {
    return merge_killer != null && !merge_killer.isDone();
  }

  void forceCancelMerge() {
    merge_lock.lock();
    try {
      if (this.merge_id != null) cancelMerge(this.merge_id);
    } finally {
      merge_lock.unlock();
    }
  }

  public boolean setMergeId(MergeId expected, MergeId new_value) {
    merge_lock.lock();
    try {
      boolean match = Util.match(this.merge_id, expected);
      if (match) {
        if (new_value != null && merge_id_history.contains(new_value)) return false;
        else merge_id_history.add(new_value);
        this.merge_id = new_value;
        if (this.merge_id != null) {
          // Clears the view handler queue and discards all JOIN/LEAVE/MERGE requests until after
          // the MERGE
          gms.getViewHandler().suspend();
          gms.getDownProtocol().down(new Event(Event.SUSPEND_STABLE, 20000));
          startMergeKiller();
        }
      }
      return match;
    } finally {
      merge_lock.unlock();
    }
  }

  /** Only used for testing, might get removed any time. Do not use ! */
  public MergeId getMergeId() {
    merge_lock.lock();
    try {
      return merge_id;
    } finally {
      merge_lock.unlock();
    }
  }

  public boolean isMergeInProgress() {
    merge_lock.lock();
    try {
      return merge_id != null;
    } finally {
      merge_lock.unlock();
    }
  }

  public boolean matchMergeId(MergeId id) {
    merge_lock.lock();
    try {
      return Util.match(this.merge_id, id);
    } finally {
      merge_lock.unlock();
    }
  }

  private void startMergeKiller() {
    merge_killer_lock.lock();
    try {
      if (merge_killer == null || merge_killer.isDone()) {
        MergeKiller task = new MergeKiller(this.merge_id);
        merge_killer =
            gms.timer.schedule(task, (long) (gms.merge_timeout * 2), TimeUnit.MILLISECONDS);
      }
    } finally {
      merge_killer_lock.unlock();
    }
  }

  private void stopMergeKiller() {
    merge_killer_lock.lock();
    try {
      if (merge_killer != null) {
        merge_killer.cancel(false);
        merge_killer = null;
      }
    } catch (Throwable t) {
    } finally {
      merge_killer_lock.unlock();
    }
  }

  /**
   * Starts the merge protocol (only run by the merge leader). Essentially sends a MERGE_REQ to all
   * coordinators of all subgroups found. Each coord receives its digest and view and returns it.
   * The leader then computes the digest and view for the new group from the return values. Finally,
   * it sends this merged view/digest to all subgroup coordinators; each coordinator will install it
   * in their subgroup.
   */
  class MergeTask implements Runnable {
    private Thread thread = null;

    /** List of all subpartition coordinators and their members */
    private final ConcurrentMap<Address, Collection<Address>> coords =
        Util.createConcurrentMap(8, 0.75f, 8);

    /**
     * @param views Guaranteed to be non-null and to have >= 2 members, or else this thread would
     *     not be started
     */
    public synchronized void start(Map<Address, View> views) {
      if (thread != null && thread.isAlive()) // the merge thread is already running
      return;

      this.coords.clear();

      // now remove all members which don't have us in their view, so RPCs won't block (e.g. FLUSH)
      // https://jira.jboss.org/browse/JGRP-1061
      sanitizeViews(views);

      // Add all different coordinators of the views into the hashmap and sets their members:
      Collection<Address> coordinators = Util.determineMergeCoords(views);
      for (Address coord : coordinators) {
        View view = views.get(coord);
        if (view != null) this.coords.put(coord, new ArrayList<Address>(view.getMembers()));
      }

      // For the merge participants which are not coordinator, we simply add them, and the
      // associated
      // membership list consists only of themselves
      Collection<Address> merge_participants = Util.determineMergeParticipants(views);
      merge_participants.removeAll(coordinators);
      for (Address merge_participant : merge_participants) {
        Collection<Address> tmp = new ArrayList<Address>();
        tmp.add(merge_participant);
        coords.putIfAbsent(merge_participant, tmp);
      }

      thread = gms.getThreadFactory().newThread(this, "MergeTask");
      thread.setDaemon(true);
      thread.start();
    }

    public synchronized void stop() {
      Thread tmp = thread;
      if (thread != null && thread.isAlive()) tmp.interrupt();
      thread = null;
    }

    public synchronized boolean isRunning() {
      return thread != null && thread.isAlive();
    }

    public void run() {
      // 1. Generate merge_id
      final MergeId new_merge_id = MergeId.create(gms.local_addr);
      final Collection<Address> coordsCopy = new ArrayList<Address>(coords.keySet());

      long start = System.currentTimeMillis();

      try {
        _run(new_merge_id, coordsCopy); // might remove members from coordsCopy
      } catch (Throwable ex) {
        if (log.isWarnEnabled()) log.warn(gms.local_addr + ": " + ex + ", merge is cancelled");
        sendMergeCancelledMessage(coordsCopy, new_merge_id);
        cancelMerge(
            new_merge_id); // the message above cancels the merge, too, but this is a 2nd line of
        // defense
      } finally {
        /* 5. if flush is in stack stop the flush for entire cluster [JGRP-700] - FLUSH: flushing should span merge */
        if (gms.flushProtocolInStack) gms.stopFlush();
        thread = null;
      }
      long diff = System.currentTimeMillis() - start;
      if (log.isDebugEnabled())
        log.debug(gms.local_addr + ": merge " + new_merge_id + " took " + diff + " ms");
    }

    /** Runs the merge protocol as a leader */
    protected void _run(MergeId new_merge_id, final Collection<Address> coordsCopy)
        throws Exception {
      boolean success = setMergeId(null, new_merge_id);
      if (!success) {
        log.warn("failed to set my own merge_id (" + merge_id + ") to " + new_merge_id);
        return;
      }

      if (log.isDebugEnabled())
        log.debug(
            gms.local_addr
                + ": merge task "
                + merge_id
                + " started with "
                + coords.keySet().size()
                + " coords");

      /* 2. Fetch the current Views/Digests from all subgroup coordinators */
      success = getMergeDataFromSubgroupCoordinators(coords, new_merge_id, gms.merge_timeout);
      List<Address> missing = null;
      if (!success) {
        missing = merge_rsps.getMissing();
        if (log.isDebugEnabled())
          log.debug(
              "merge leader "
                  + gms.local_addr
                  + " did not get responses from all "
                  + coords.keySet().size()
                  + " partition coordinators; missing responses from "
                  + missing.size()
                  + " members, removing them from the merge");
        merge_rsps.remove(missing);
      }

      /* 3. Remove null or rejected merge responses from merge_rsp and coords (so we'll send the new view
       * only to members who accepted the merge request) */
      if (missing != null && !missing.isEmpty()) {
        coords.keySet().removeAll(missing);
        coordsCopy.removeAll(missing);
      }

      removeRejectedMergeRequests(coords.keySet());
      if (merge_rsps.size() == 0)
        throw new Exception("did not get any merge responses from partition coordinators");

      if (!coords
          .keySet()
          .contains(
              gms.local_addr)) // another member might have invoked a merge req on us before we got
        // there...
        throw new Exception("merge leader rejected merge request");

      /* 4. Combine all views and digests into 1 View/1 Digest */
      List<MergeData> merge_data = new ArrayList<MergeData>(merge_rsps.getResults().values());
      MergeData combined_merge_data = consolidateMergeData(merge_data);
      if (combined_merge_data == null) throw new Exception("could not consolidate merge");

      /* 4. Send the new View/Digest to all coordinators (including myself). On reception, they will
      install the digest and view in all of their subgroup members */
      if (log.isDebugEnabled())
        log.debug(
            gms.local_addr
                + ": installing merge view "
                + combined_merge_data.view.getViewId()
                + " ("
                + combined_merge_data.view.size()
                + " members) in "
                + coords.keySet().size()
                + " coords");
      sendMergeView(coords.keySet(), combined_merge_data, new_merge_id);
    }

    /**
     * Sends a MERGE_REQ to all coords and populates a list of MergeData (in merge_rsps). Returns
     * after coords.size() response have been received, or timeout msecs have elapsed (whichever is
     * first).
     *
     * <p>If a subgroup coordinator rejects the MERGE_REQ (e.g. because of participation in a
     * different merge), <em>that member will be removed from coords !</em>
     *
     * @param coords A map of coordinatgor addresses and associated membership lists
     * @param new_merge_id The new merge id
     * @param timeout Max number of msecs to wait for the merge responses from the subgroup coords
     */
    protected boolean getMergeDataFromSubgroupCoordinators(
        Map<Address, Collection<Address>> coords, MergeId new_merge_id, long timeout) {
      boolean gotAllResponses;
      long start = System.currentTimeMillis();
      merge_rsps.reset(coords.keySet());
      if (log.isTraceEnabled())
        log.trace(gms.local_addr + ": sending MERGE_REQ to " + coords.keySet());

      for (Map.Entry<Address, Collection<Address>> entry : coords.entrySet()) {
        Address coord = entry.getKey();
        Collection<Address> mbrs = entry.getValue();
        Message msg = new Message(coord).setFlag(Message.Flag.OOB, Message.Flag.INTERNAL);
        GMS.GmsHeader hdr = new GMS.GmsHeader(GMS.GmsHeader.MERGE_REQ, mbrs);
        hdr.mbr = gms.local_addr;
        hdr.merge_id = new_merge_id;
        msg.putHeader(gms.getId(), hdr);
        gms.getDownProtocol().down(new Event(Event.MSG, msg));
      }

      // wait until num_rsps_expected >= num_rsps or timeout elapsed
      merge_rsps.waitForAllResponses(timeout);
      gotAllResponses = merge_rsps.hasAllResponses();
      long stop = System.currentTimeMillis();
      if (log.isTraceEnabled())
        log.trace(
            gms.local_addr
                + ": collected "
                + merge_rsps.numberOfValidResponses()
                + " merge response(s) in "
                + (stop - start)
                + " ms");
      return gotAllResponses;
    }

    /**
     * Removed rejected merge requests from merge_rsps and coords. This method has a lock on
     * merge_rsps
     */
    private void removeRejectedMergeRequests(Collection<Address> coords) {
      int num_removed = 0;
      for (Iterator<Map.Entry<Address, MergeData>> it =
              merge_rsps.getResults().entrySet().iterator();
          it.hasNext(); ) {
        Map.Entry<Address, MergeData> entry = it.next();
        MergeData data = entry.getValue();
        if (data.merge_rejected) {
          if (data.getSender() != null) coords.remove(data.getSender());
          it.remove();
          num_removed++;
        }
      }

      if (num_removed > 0) {
        if (log.isTraceEnabled())
          log.trace(gms.local_addr + ": removed " + num_removed + " rejected merge responses");
      }
    }

    /**
     * Merge all MergeData. All MergeData elements should be disjunct (both views and digests).
     * However, this method is prepared to resolve duplicate entries (for the same member).
     * Resolution strategy for views is to merge only 1 of the duplicate members. Resolution
     * strategy for digests is to take the higher seqnos for duplicate digests.
     *
     * <p>After merging all members into a Membership and subsequent sorting, the first member of
     * the sorted membership will be the new coordinator. This method has a lock on merge_rsps.
     *
     * @param merge_rsps A list of MergeData items. Elements with merge_rejected=true were removed
     *     before. Is guaranteed not to be null and to contain at least 1 member.
     */
    private MergeData consolidateMergeData(List<MergeData> merge_rsps) {
      long logical_time = 0; // for new_vid
      List<View> subgroups =
          new ArrayList<View>(11); // contains a list of Views, each View is a subgroup
      Collection<Collection<Address>> sub_mbrships = new ArrayList<Collection<Address>>();

      for (MergeData tmp_data : merge_rsps) {
        View tmp_view = tmp_data.getView();
        if (tmp_view != null) {
          ViewId tmp_vid = tmp_view.getVid();
          if (tmp_vid != null) {
            // compute the new view id (max of all vids +1)
            logical_time = Math.max(logical_time, tmp_vid.getId());
          }
          // merge all membership lists into one (prevent duplicates)
          sub_mbrships.add(new ArrayList<Address>(tmp_view.getMembers()));
          subgroups.add(tmp_view.copy());
        }
      }

      // determine the new digest
      Digest new_digest = consolidateDigests(merge_rsps, merge_rsps.size());
      if (new_digest == null) return null;

      // remove all members from the new member list that are not in the digest
      Collection<Address> digest_mbrs = new_digest.getMembers();
      for (Collection<Address> coll : sub_mbrships) coll.retainAll(digest_mbrs);

      List<Address> merged_mbrs = gms.computeNewMembership(sub_mbrships);

      // the new coordinator is the first member of the consolidated & sorted membership list
      Address new_coord = merged_mbrs.isEmpty() ? null : merged_mbrs.get(0);
      if (new_coord == null) return null;

      // should be the highest view ID seen up to now plus 1
      ViewId new_vid = new ViewId(new_coord, logical_time + 1);

      // determine the new view
      MergeView new_view = new MergeView(new_vid, merged_mbrs, subgroups);

      if (log.isTraceEnabled())
        log.trace(
            gms.local_addr
                + ": consolidated view="
                + new_view
                + "\nconsolidated digest="
                + new_digest);
      return new MergeData(gms.local_addr, new_view, new_digest);
    }

    /**
     * Merge all digests into one. For each sender, the new value is max(highest_delivered),
     * max(highest_received). This method has a lock on merge_rsps
     */
    private Digest consolidateDigests(List<MergeData> merge_rsps, int num_mbrs) {
      MutableDigest retval = new MutableDigest(num_mbrs);

      for (MergeData data : merge_rsps) {
        Digest tmp_digest = data.getDigest();
        if (tmp_digest == null) continue;

        retval.merge(tmp_digest);
      }
      return retval.copy();
    }
  }

  private class MergeKiller implements Runnable {
    private final MergeId my_merge_id;

    MergeKiller(MergeId my_merge_id) {
      this.my_merge_id = my_merge_id;
    }

    public void run() {
      cancelMerge(my_merge_id);
    }

    public String toString() {
      return Merger.class.getSimpleName() + ": " + getClass().getSimpleName();
    }
  }
}
Beispiel #11
0
/**
 * This class is to be used to pick up execution requests and actually run them. A single instance
 * can be used across any number of threads.
 *
 * @author wburns
 */
public class ExecutionRunner implements Runnable {
  protected JChannel ch;
  protected Executing _execProt;

  public ExecutionRunner(JChannel channel) {
    setChannel(channel);
  }

  public void setChannel(JChannel ch) {
    this.ch = ch;
    _execProt = (Executing) ch.getProtocolStack().findProtocol(Executing.class);
    if (_execProt == null)
      throw new IllegalStateException(
          "Channel configuration must include a executing protocol "
              + "(subclass of "
              + Executing.class.getName()
              + ")");
  }

  protected static class Holder<T> {
    protected T value;

    public Holder(T value) {
      this.value = value;
    }
  }

  // @see java.lang.Runnable#run()
  @Override
  public void run() {
    final Lock shutdownLock = new ReentrantLock();

    // The following 2 atomic boolean should only ever be updated while
    // protected by the above lock.  They don't have to be atomic boolean,
    // but it is a nice wrapper to share a reference between threads.
    // Reads can be okay in certain circumstances
    final AtomicBoolean canInterrupt = new AtomicBoolean(true);
    final AtomicBoolean shutdown = new AtomicBoolean();

    // This thread is only spawned so that we can differentiate between
    // an interrupt of a task and an interrupt causing a shutdown of
    // runner itself.
    Thread executionThread =
        new Thread() {

          // @see java.lang.Thread#run()
          @Override
          public void run() {

            Thread currentThread = Thread.currentThread();
            Runnable runnable = null;
            // This task exits by being interrupted when the task isn't running
            // or by updating shutdown to true when it can't be interrupted
            while (!shutdown.get()) {
              _runnables.put(currentThread, new Holder<>(null));
              runnable = (Runnable) ch.down(new ExecutorEvent(ExecutorEvent.CONSUMER_READY, null));

              // This means we were interrupted while waiting
              if (runnable == null) break;

              // First retrieve the lock to make sure we can tell them
              // to not interrupt us
              shutdownLock.lock();
              try {
                // Clear interrupt state as we don't want to stop the
                // task we just received.  If we got a shutdown signal
                // we will only do it after we loop back around.
                Thread.interrupted();
                canInterrupt.set(false);
              } finally {
                shutdownLock.unlock();
              }
              _runnables.put(currentThread, new Holder<>(runnable));

              Throwable throwable = null;
              try {
                runnable.run();
              }
              // This can only happen if user is directly doing an execute(Runnable)
              catch (Throwable t) {
                _logger.error("Unexpected Runtime Error encountered in Runnable request", t);
                throwable = t;
              }
              ch.down(
                  new ExecutorEvent(
                      ExecutorEvent.TASK_COMPLETE,
                      throwable != null ? new Object[] {runnable, throwable} : runnable));

              // We have to let the outer thread know we can now be interrupted
              shutdownLock.lock();
              try {
                canInterrupt.set(true);
              } finally {
                shutdownLock.unlock();
              }
            }

            _runnables.remove(currentThread);
          }
        };

    executionThread.setName(Thread.currentThread().getName() + "- Task Runner");
    executionThread.start();

    try {
      executionThread.join();
    } catch (InterruptedException e) {
      shutdownLock.lock();
      try {
        if (canInterrupt.get()) {
          executionThread.interrupt();
        }
        shutdown.set(true);
      } finally {
        shutdownLock.unlock();
      }

      if (_logger.isTraceEnabled()) {
        _logger.trace("Shutting down Execution Runner");
      }
    }
  }

  /**
   * Returns a copy of the runners being used with the runner and what threads. If a thread is not
   * currently running a task it will return with a null value. This map is a copy and can be
   * modified if necessary without causing issues.
   *
   * @return map of all threads that are active with this runner. If the thread is currently running
   *     a job the runnable value will be populated otherwise null would mean the thread is waiting
   */
  public Map<Thread, Runnable> getCurrentRunningTasks() {
    Map<Thread, Runnable> map = new HashMap<>();
    for (Entry<Thread, Holder<Runnable>> entry : _runnables.entrySet()) {
      map.put(entry.getKey(), entry.getValue().value);
    }
    return map;
  }

  private final Map<Thread, Holder<Runnable>> _runnables = new ConcurrentHashMap<>();

  protected static final Log _logger = LogFactory.getLog(ExecutionRunner.class);
}
Beispiel #12
0
/**
 * A DynamicMBean wrapping an annotated object instance.
 *
 * @author Chris Mills
 * @author Vladimir Blagojevic
 * @version $Id: ResourceDMBean.java,v 1.32 2009/12/11 13:21:17 belaban Exp $
 * @see ManagedAttribute
 * @see ManagedOperation
 * @see MBean
 */
public class ResourceDMBean implements DynamicMBean {
  private static final Class<?>[] primitives = {
    int.class,
    byte.class,
    short.class,
    long.class,
    float.class,
    double.class,
    boolean.class,
    char.class
  };

  private static final String MBEAN_DESCRITION = "Dynamic MBean Description";

  private final Log log = LogFactory.getLog(ResourceDMBean.class);
  private final Object obj;
  private String description = "";

  private final MBeanAttributeInfo[] attrInfo;
  private final MBeanOperationInfo[] opInfo;

  private final HashMap<String, AttributeEntry> atts = new HashMap<String, AttributeEntry>();
  private final List<MBeanOperationInfo> ops = new ArrayList<MBeanOperationInfo>();

  public ResourceDMBean(Object instance) {
    if (instance == null)
      throw new NullPointerException("Cannot make an MBean wrapper for null instance");

    this.obj = instance;
    findDescription();
    findFields();
    findMethods();

    attrInfo = new MBeanAttributeInfo[atts.size()];
    int i = 0;
    MBeanAttributeInfo info = null;
    for (AttributeEntry entry : atts.values()) {
      info = entry.getInfo();
      attrInfo[i++] = info;
    }

    opInfo = new MBeanOperationInfo[ops.size()];
    ops.toArray(opInfo);
  }

  Object getObject() {
    return obj;
  }

  private void findDescription() {
    MBean mbean = getObject().getClass().getAnnotation(MBean.class);
    if (mbean != null && mbean.description() != null && mbean.description().trim().length() > 0) {
      description = mbean.description();
      MBeanAttributeInfo info =
          new MBeanAttributeInfo(
              ResourceDMBean.MBEAN_DESCRITION,
              "java.lang.String",
              "MBean description",
              true,
              false,
              false);
      try {
        atts.put(
            ResourceDMBean.MBEAN_DESCRITION,
            new FieldAttributeEntry(info, getClass().getDeclaredField("description")));
      } catch (NoSuchFieldException e) {
        // this should not happen unless somebody removes description field
        log.warn("Could not reflect field description of this class. Was it removed?");
      }
    }
  }

  public synchronized MBeanInfo getMBeanInfo() {

    return new MBeanInfo(
        getObject().getClass().getCanonicalName(), description, attrInfo, null, opInfo, null);
  }

  public synchronized Object getAttribute(String name) {
    if (name == null || name.length() == 0)
      throw new NullPointerException("Invalid attribute requested " + name);
    Attribute attr = getNamedAttribute(name);
    return attr.getValue();
  }

  public synchronized void setAttribute(Attribute attribute) {
    if (attribute == null || attribute.getName() == null)
      throw new NullPointerException("Invalid attribute requested " + attribute);

    setNamedAttribute(attribute);
  }

  public synchronized AttributeList getAttributes(String[] names) {
    AttributeList al = new AttributeList();
    for (String name : names) {
      Attribute attr = getNamedAttribute(name);
      if (attr != null) {
        al.add(attr);
      } else {
        log.warn("Did not find attribute " + name);
      }
    }
    return al;
  }

  public synchronized AttributeList setAttributes(AttributeList list) {
    AttributeList results = new AttributeList();
    for (int i = 0; i < list.size(); i++) {
      Attribute attr = (Attribute) list.get(i);

      if (setNamedAttribute(attr)) {
        results.add(attr);
      } else {
        if (log.isWarnEnabled()) {
          log.warn(
              "Failed to update attribute name "
                  + attr.getName()
                  + " with value "
                  + attr.getValue());
        }
      }
    }
    return results;
  }

  public Object invoke(String name, Object[] args, String[] sig)
      throws MBeanException, ReflectionException {
    try {
      Class<?>[] classes = new Class[sig.length];
      for (int i = 0; i < classes.length; i++) {
        classes[i] = getClassForName(sig[i]);
      }
      Method method = getObject().getClass().getMethod(name, classes);
      return method.invoke(getObject(), args);
    } catch (Exception e) {
      throw new MBeanException(e);
    }
  }

  public static Class<?> getClassForName(String name) throws ClassNotFoundException {
    try {
      return Class.forName(name);
    } catch (ClassNotFoundException cnfe) {
      // Could be a primitive - let's check
      for (int i = 0; i < primitives.length; i++) {
        if (name.equals(primitives[i].getName())) {
          return primitives[i];
        }
      }
    }
    throw new ClassNotFoundException("Class " + name + " cannot be found");
  }

  private void findMethods() {
    // find all methods but don't include methods from Object class
    List<Method> methods =
        new ArrayList<Method>(Arrays.asList(getObject().getClass().getMethods()));
    List<Method> objectMethods = new ArrayList<Method>(Arrays.asList(Object.class.getMethods()));
    methods.removeAll(objectMethods);

    for (Method method : methods) {
      // does method have @ManagedAttribute annotation?
      if (method.isAnnotationPresent(ManagedAttribute.class)
          || method.isAnnotationPresent(Property.class)) {
        exposeManagedAttribute(method);
      }
      // or @ManagedOperation
      else if (method.isAnnotationPresent(ManagedOperation.class)
          || isMBeanAnnotationPresentWithExposeAll()) {
        exposeManagedOperation(method);
      }
    }
  }

  private void exposeManagedOperation(Method method) {
    ManagedOperation op = method.getAnnotation(ManagedOperation.class);
    String attName = method.getName();
    if (isSetMethod(method) || isGetMethod(method)) {
      attName = attName.substring(3);
    } else if (isIsMethod(method)) {
      attName = attName.substring(2);
    }
    // expose unless we already exposed matching attribute field
    boolean isAlreadyExposed = atts.containsKey(attName);
    if (!isAlreadyExposed) {
      ops.add(new MBeanOperationInfo(op != null ? op.description() : "", method));
    }
  }

  private void exposeManagedAttribute(Method method) {
    String methodName = method.getName();
    if (!methodName.startsWith("get")
        && !methodName.startsWith("set")
        && !methodName.startsWith("is")) {
      if (log.isWarnEnabled())
        log.warn(
            "method name "
                + methodName
                + " doesn't start with \"get\", \"set\", or \"is\""
                + ", but is annotated with @ManagedAttribute: will be ignored");
      return;
    }
    ManagedAttribute attr = method.getAnnotation(ManagedAttribute.class);
    Property prop = method.getAnnotation(Property.class);

    boolean expose_prop = prop != null && prop.exposeAsManagedAttribute();
    boolean expose = attr != null || expose_prop;
    if (!expose) return;

    // Is name field of @ManagedAttributed used?
    String attributeName = attr != null ? attr.name() : null;
    if (attributeName != null && attributeName.trim().length() > 0)
      attributeName = attributeName.trim();
    else attributeName = null;

    String descr = attr != null ? attr.description() : prop != null ? prop.description() : null;

    boolean writeAttribute = false;
    MBeanAttributeInfo info = null;
    if (isSetMethod(method)) { // setter
      attributeName = (attributeName == null) ? methodName.substring(3) : attributeName;
      info =
          new MBeanAttributeInfo(
              attributeName,
              method.getParameterTypes()[0].getCanonicalName(),
              descr,
              true,
              true,
              false);
      writeAttribute = true;
    } else { // getter
      if (method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE) {
        boolean hasSetter = atts.containsKey(attributeName);
        // we found is method
        if (methodName.startsWith("is")) {
          attributeName = (attributeName == null) ? methodName.substring(2) : attributeName;
          info =
              new MBeanAttributeInfo(
                  attributeName,
                  method.getReturnType().getCanonicalName(),
                  descr,
                  true,
                  hasSetter,
                  true);
        } else {
          // this has to be get
          attributeName = (attributeName == null) ? methodName.substring(3) : attributeName;
          info =
              new MBeanAttributeInfo(
                  attributeName,
                  method.getReturnType().getCanonicalName(),
                  descr,
                  true,
                  hasSetter,
                  false);
        }
      } else {
        if (log.isWarnEnabled()) {
          log.warn(
              "Method " + method.getName() + " must have a valid return type and zero parameters");
        }
        // silently skip this method
        return;
      }
    }

    AttributeEntry ae = atts.get(attributeName);
    // is it a read method?
    if (!writeAttribute) {
      // we already have annotated field as read
      if (ae instanceof FieldAttributeEntry && ae.getInfo().isReadable()) {
        log.warn("not adding annotated method " + method + " since we already have read attribute");
      }
      // we already have annotated set method
      else if (ae instanceof MethodAttributeEntry) {
        MethodAttributeEntry mae = (MethodAttributeEntry) ae;
        if (mae.hasSetMethod()) {
          atts.put(
              attributeName, new MethodAttributeEntry(mae.getInfo(), mae.getSetMethod(), method));
        }
      } // we don't have such entry
      else {
        atts.put(
            attributeName,
            new MethodAttributeEntry(info, findSetter(obj.getClass(), attributeName), method));
      }
    } // is it a set method?
    else {
      if (ae instanceof FieldAttributeEntry) {
        // we already have annotated field as write
        if (ae.getInfo().isWritable()) {
          log.warn(
              "Not adding annotated method "
                  + methodName
                  + " since we already have writable attribute");
        } else {
          // we already have annotated field as read
          // lets make the field writable
          Field f = ((FieldAttributeEntry) ae).getField();
          MBeanAttributeInfo i =
              new MBeanAttributeInfo(
                  ae.getInfo().getName(),
                  f.getType().getCanonicalName(),
                  descr,
                  true,
                  !Modifier.isFinal(f.getModifiers()),
                  false);
          atts.put(attributeName, new FieldAttributeEntry(i, f));
        }
      }
      // we already have annotated getOrIs method
      else if (ae instanceof MethodAttributeEntry) {
        MethodAttributeEntry mae = (MethodAttributeEntry) ae;
        if (mae.hasIsOrGetMethod()) {
          atts.put(attributeName, new MethodAttributeEntry(info, method, mae.getIsOrGetMethod()));
        }
      } // we don't have such entry
      else {
        atts.put(
            attributeName,
            new MethodAttributeEntry(info, method, findGetter(obj.getClass(), attributeName)));
      }
    }
  }

  private static boolean isSetMethod(Method method) {
    return (method.getName().startsWith("set")
        && method.getParameterTypes().length == 1
        && method.getReturnType() == java.lang.Void.TYPE);
  }

  private static boolean isGetMethod(Method method) {
    return (method.getParameterTypes().length == 0
        && method.getReturnType() != java.lang.Void.TYPE
        && method.getName().startsWith("get"));
  }

  private static boolean isIsMethod(Method method) {
    return (method.getParameterTypes().length == 0
        && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class)
        && method.getName().startsWith("is"));
  }

  public static Method findGetter(Class clazz, String name) {
    try {
      return clazz.getMethod("get" + name);
    } catch (NoSuchMethodException e) {
    }

    try {
      return clazz.getMethod("is" + name);
    } catch (NoSuchMethodException ex) {
    }
    return null;
  }

  public static Method findSetter(Class clazz, String name) {
    try {
      return clazz.getMethod("set" + name);
    } catch (NoSuchMethodException e) {
    }

    return null;
  }

  private void findFields() {
    // traverse class hierarchy and find all annotated fields
    for (Class<?> clazz = getObject().getClass(); clazz != null; clazz = clazz.getSuperclass()) {

      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
        ManagedAttribute attr = field.getAnnotation(ManagedAttribute.class);
        Property prop = field.getAnnotation(Property.class);
        boolean expose_prop = prop != null && prop.exposeAsManagedAttribute();
        boolean expose = attr != null || expose_prop;

        if (expose) {
          String fieldName = Util.attributeNameToMethodName(field.getName());
          String descr = attr != null ? attr.description() : prop.description();
          boolean writable = attr != null ? attr.writable() : prop.writable();

          MBeanAttributeInfo info =
              new MBeanAttributeInfo(
                  fieldName,
                  field.getType().getCanonicalName(),
                  descr,
                  true,
                  !Modifier.isFinal(field.getModifiers()) && writable,
                  false);

          atts.put(fieldName, new FieldAttributeEntry(info, field));
        }
      }
    }
  }

  private Attribute getNamedAttribute(String name) {
    Attribute result = null;
    if (name.equals(ResourceDMBean.MBEAN_DESCRITION)) {
      result = new Attribute(ResourceDMBean.MBEAN_DESCRITION, this.description);
    } else {
      AttributeEntry entry = atts.get(name);
      if (entry != null) {
        try {
          result = new Attribute(name, entry.invoke(null));
        } catch (Exception e) {
          log.warn("Exception while reading value of attribute " + name, e);
        }
      } else {
        log.warn("Did not find queried attribute with name " + name);
      }
    }
    return result;
  }

  private boolean setNamedAttribute(Attribute attribute) {
    boolean result = false;
    AttributeEntry entry = atts.get(attribute.getName());
    if (entry != null) {
      try {
        entry.invoke(attribute);
        result = true;
      } catch (Exception e) {
        log.warn("Exception while writing value for attribute " + attribute.getName(), e);
      }
    } else {
      log.warn(
          "Could not invoke set on attribute "
              + attribute.getName()
              + " with value "
              + attribute.getValue());
    }
    return result;
  }

  private boolean isMBeanAnnotationPresentWithExposeAll() {
    Class<? extends Object> c = getObject().getClass();
    return c.isAnnotationPresent(MBean.class) && c.getAnnotation(MBean.class).exposeAll();
  }

  private class MethodAttributeEntry implements AttributeEntry {

    final MBeanAttributeInfo info;

    final Method isOrGetmethod;

    final Method setMethod;

    public MethodAttributeEntry(
        final MBeanAttributeInfo info, final Method setMethod, final Method isOrGetMethod) {
      super();
      this.info = info;
      this.setMethod = setMethod;
      this.isOrGetmethod = isOrGetMethod;
    }

    public Object invoke(Attribute a) throws Exception {
      if (a == null && isOrGetmethod != null) return isOrGetmethod.invoke(getObject());
      else if (a != null && setMethod != null) return setMethod.invoke(getObject(), a.getValue());
      else return null;
    }

    public MBeanAttributeInfo getInfo() {
      return info;
    }

    public boolean hasIsOrGetMethod() {
      return isOrGetmethod != null;
    }

    public boolean hasSetMethod() {
      return setMethod != null;
    }

    public Method getIsOrGetMethod() {
      return isOrGetmethod;
    }

    public Method getSetMethod() {
      return setMethod;
    }
  }

  private class FieldAttributeEntry implements AttributeEntry {

    private final MBeanAttributeInfo info;

    private final Field field;

    public FieldAttributeEntry(final MBeanAttributeInfo info, final Field field) {
      super();
      this.info = info;
      this.field = field;
      if (!field.isAccessible()) {
        field.setAccessible(true);
      }
    }

    public Field getField() {
      return field;
    }

    public Object invoke(Attribute a) throws Exception {
      if (a == null) {
        return field.get(getObject());
      } else {
        field.set(getObject(), a.getValue());
        return null;
      }
    }

    public MBeanAttributeInfo getInfo() {
      return info;
    }
  }

  private interface AttributeEntry {
    public Object invoke(Attribute a) throws Exception;

    public MBeanAttributeInfo getInfo();
  }
}
/**
 * Provides synchronous and asynchronous message sending with request-response correlation; i.e.,
 * matching responses with the original request. It also offers push-style message reception (by
 * internally using the PullPushAdapter).
 *
 * <p>Channels are simple patterns to asynchronously send a receive messages. However, a significant
 * number of communication patterns in group communication require synchronous communication. For
 * example, a sender would like to send a message to the group and wait for all responses. Or
 * another application would like to send a message to the group and wait only until the majority of
 * the receivers have sent a response, or until a timeout occurred. MessageDispatcher offers a
 * combination of the above pattern with other patterns.
 *
 * <p>Used on top of channel to implement group requests. Client's <code>handle()</code> method is
 * called when request is received. Is the equivalent of RpcProtocol on the application instead of
 * protocol level.
 *
 * @author Bela Ban
 */
public class MessageDispatcher implements AsyncRequestHandler, ChannelListener {
  protected Channel channel;
  protected RequestCorrelator corr;
  protected MessageListener msg_listener;
  protected MembershipListener membership_listener;
  protected RequestHandler req_handler;
  protected boolean async_dispatching;
  protected ProtocolAdapter prot_adapter;
  protected volatile Collection<Address> members = new HashSet<Address>();
  protected Address local_addr;
  protected final Log log = LogFactory.getLog(getClass());
  protected boolean hardware_multicast_supported = false;
  protected final AtomicInteger sync_unicasts = new AtomicInteger(0);
  protected final AtomicInteger async_unicasts = new AtomicInteger(0);
  protected final AtomicInteger sync_multicasts = new AtomicInteger(0);
  protected final AtomicInteger async_multicasts = new AtomicInteger(0);
  protected final AtomicInteger sync_anycasts = new AtomicInteger(0);
  protected final AtomicInteger async_anycasts = new AtomicInteger(0);
  protected final Set<ChannelListener> channel_listeners =
      new CopyOnWriteArraySet<ChannelListener>();
  protected final DiagnosticsHandler.ProbeHandler probe_handler = new MyProbeHandler();

  public MessageDispatcher() {}

  public MessageDispatcher(Channel channel, MessageListener l, MembershipListener l2) {
    this.channel = channel;
    prot_adapter = new ProtocolAdapter();
    if (channel != null) {
      local_addr = channel.getAddress();
      channel.addChannelListener(this);
    }
    setMessageListener(l);
    setMembershipListener(l2);
    if (channel != null) installUpHandler(prot_adapter, true);
    start();
  }

  public MessageDispatcher(Channel channel, RequestHandler req_handler) {
    this(channel, null, null, req_handler);
  }

  public MessageDispatcher(
      Channel channel, MessageListener l, MembershipListener l2, RequestHandler req_handler) {
    this(channel, l, l2);
    setRequestHandler(req_handler);
  }

  public boolean asyncDispatching() {
    return async_dispatching;
  }

  public MessageDispatcher asyncDispatching(boolean flag) {
    async_dispatching = flag;
    if (corr != null) corr.asyncDispatching(flag);
    return this;
  }

  public UpHandler getProtocolAdapter() {
    return prot_adapter;
  }

  /**
   * If this dispatcher is using a user-provided PullPushAdapter, then need to set the members from
   * the adapter initially since viewChange has most likely already been called in PullPushAdapter.
   */
  protected void setMembers(List<Address> new_mbrs) {
    if (new_mbrs != null)
      members = new HashSet<Address>(new_mbrs); // volatile write - seen by a subsequent read
  }

  /** Adds a new channel listener to be notified on the channel's state change. */
  public void addChannelListener(ChannelListener l) {
    if (l != null) channel_listeners.add(l);
  }

  public void removeChannelListener(ChannelListener l) {
    if (l != null) channel_listeners.remove(l);
  }

  public void start() {
    if (corr == null)
      corr =
          createRequestCorrelator(prot_adapter, this, local_addr)
              .asyncDispatching(async_dispatching);
    correlatorStarted();
    corr.start();

    if (channel != null) {
      List<Address> tmp_mbrs = channel.getView() != null ? channel.getView().getMembers() : null;
      setMembers(tmp_mbrs);
      if (channel instanceof JChannel) {
        TP transport = channel.getProtocolStack().getTransport();
        corr.registerProbeHandler(transport);
      }
      TP transport = channel.getProtocolStack().getTransport();
      hardware_multicast_supported = transport.supportsMulticasting();
      transport.registerProbeHandler(probe_handler);
    }
  }

  protected RequestCorrelator createRequestCorrelator(
      Protocol transport, RequestHandler handler, Address local_addr) {
    return new RequestCorrelator(transport, handler, local_addr);
  }

  protected void correlatorStarted() {
    ;
  }

  public void stop() {
    if (corr != null) corr.stop();

    if (channel instanceof JChannel) {
      TP transport = channel.getProtocolStack().getTransport();
      transport.unregisterProbeHandler(probe_handler);
      corr.unregisterProbeHandler(transport);
    }
  }

  public final void setMessageListener(MessageListener l) {
    msg_listener = l;
  }

  public MessageListener getMessageListener() {
    return msg_listener;
  }

  public final void setMembershipListener(MembershipListener l) {
    membership_listener = l;
  }

  public final void setRequestHandler(RequestHandler rh) {
    req_handler = rh;
  }

  public Channel getChannel() {
    return channel;
  }

  public void setChannel(Channel ch) {
    if (ch == null) return;
    this.channel = ch;
    local_addr = channel.getAddress();
    if (prot_adapter == null) prot_adapter = new ProtocolAdapter();
    // Don't force installing the UpHandler so subclasses can use this
    // method and still integrate with a MuxUpHandler
    installUpHandler(prot_adapter, false);
  }

  /**
   * Sets the given UpHandler as the UpHandler for the channel, or, if the channel already has a
   * Muxer installed as it's UpHandler, sets the given handler as the Muxer's {@link
   * Muxer#setDefaultHandler(Object) default handler}. If the relevant handler is already installed,
   * the <code>canReplace</code> controls whether this method replaces it (after logging a WARN) or
   * simply leaves <code>handler</code> uninstalled.
   *
   * <p>Passing <code>false</code> as the <code>canReplace</code> value allows callers to use this
   * method to install defaults without concern about inadvertently overriding
   *
   * @param handler the UpHandler to install
   * @param canReplace <code>true</code> if an existing Channel upHandler or Muxer default upHandler
   *     can be replaced; <code>false</code> if this method shouldn't install
   */
  protected void installUpHandler(UpHandler handler, boolean canReplace) {
    UpHandler existing = channel.getUpHandler();
    if (existing == null) {
      channel.setUpHandler(handler);
    } else if (existing instanceof Muxer<?>) {
      @SuppressWarnings("unchecked")
      Muxer<UpHandler> mux = (Muxer<UpHandler>) existing;
      if (mux.getDefaultHandler() == null) {
        mux.setDefaultHandler(handler);
      } else if (canReplace) {
        log.warn(
            "Channel Muxer already has a default up handler installed ("
                + mux.getDefaultHandler()
                + ") but now it is being overridden");
        mux.setDefaultHandler(handler);
      }
    } else if (canReplace) {
      log.warn(
          "Channel already has an up handler installed ("
              + existing
              + ") but now it is being overridden");
      channel.setUpHandler(handler);
    }
  }

  /**
   * Sends a message to all members and expects responses from members in dests (if non-null).
   *
   * @param dests A list of group members from which to expect responses (if the call is blocking).
   * @param msg The message to be sent
   * @param options A set of options that govern the call. See {@link
   *     org.jgroups.blocks.RequestOptions} for details
   * @return RspList A list of Rsp elements
   * @throws Exception If the request cannot be sent
   * @since 2.9
   */
  public <T> RspList<T> castMessage(
      final Collection<Address> dests, Message msg, RequestOptions options) throws Exception {
    GroupRequest<T> req = cast(dests, msg, options, true);
    return req != null ? req.getResults() : new RspList();
  }

  /**
   * Sends a message to all members and expects responses from members in dests (if non-null).
   *
   * @param dests A list of group members from which to expect responses (if the call is blocking).
   * @param msg The message to be sent
   * @param options A set of options that govern the call. See {@link
   *     org.jgroups.blocks.RequestOptions} for details
   * @param listener A FutureListener which will be registered (if non null) with the future
   *     <em>before</em> the call is invoked
   * @return NotifyingFuture<T> A future from which the results (RspList) can be retrieved
   * @throws Exception If the request cannot be sent
   */
  public <T> NotifyingFuture<RspList<T>> castMessageWithFuture(
      final Collection<Address> dests,
      Message msg,
      RequestOptions options,
      FutureListener<RspList<T>> listener)
      throws Exception {
    GroupRequest<T> req = cast(dests, msg, options, false, listener);
    return req != null ? req : new NullFuture<RspList>(new RspList());
  }

  /**
   * Sends a message to all members and expects responses from members in dests (if non-null).
   *
   * @param dests A list of group members from which to expect responses (if the call is blocking).
   * @param msg The message to be sent
   * @param options A set of options that govern the call. See {@link
   *     org.jgroups.blocks.RequestOptions} for details
   * @return NotifyingFuture<T> A future from which the results (RspList) can be retrieved
   * @throws Exception If the request cannot be sent
   */
  public <T> NotifyingFuture<RspList<T>> castMessageWithFuture(
      final Collection<Address> dests, Message msg, RequestOptions options) throws Exception {
    return castMessageWithFuture(dests, msg, options, null);
  }

  protected <T> GroupRequest<T> cast(
      final Collection<Address> dests,
      Message msg,
      RequestOptions options,
      boolean block_for_results,
      FutureListener<RspList<T>> listener)
      throws Exception {
    if (msg.getDest() != null && !(msg.getDest() instanceof AnycastAddress))
      throw new IllegalArgumentException("message destination is non-null, cannot send message");

    if (options != null) {
      msg.setFlag(options.getFlags()).setTransientFlag(options.getTransientFlags());
      if (options.getScope() > 0) msg.setScope(options.getScope());
    }

    List<Address> real_dests;
    // we need to clone because we don't want to modify the original
    if (dests != null) {
      real_dests = new ArrayList<Address>();
      for (Address dest : dests) {
        if (dest instanceof SiteAddress || this.members.contains(dest)) {
          if (!real_dests.contains(dest)) real_dests.add(dest);
        }
      }
    } else real_dests = new ArrayList<Address>(members);

    // if local delivery is off, then we should not wait for the message from the local member.
    // therefore remove it from the membership
    Channel tmp = channel;
    if ((tmp != null && tmp.getDiscardOwnMessages())
        || msg.isTransientFlagSet(Message.TransientFlag.DONT_LOOPBACK)) {
      if (local_addr == null) local_addr = tmp != null ? tmp.getAddress() : null;
      if (local_addr != null) real_dests.remove(local_addr);
    }

    if (options != null && options.hasExclusionList()) {
      Address[] exclusion_list = options.exclusionList();
      for (Address excluding : exclusion_list) real_dests.remove(excluding);
    }

    // don't even send the message if the destination list is empty
    if (log.isTraceEnabled()) log.trace("real_dests=" + real_dests);

    if (real_dests.isEmpty()) {
      if (log.isTraceEnabled()) log.trace("destination list is empty, won't send message");
      return null;
    }

    if (options != null) {
      boolean async = options.getMode() == ResponseMode.GET_NONE;
      if (options.getAnycasting()) {
        if (async) async_anycasts.incrementAndGet();
        else sync_anycasts.incrementAndGet();
      } else {
        if (async) async_multicasts.incrementAndGet();
        else sync_multicasts.incrementAndGet();
      }
    }

    GroupRequest<T> req = new GroupRequest<T>(msg, corr, real_dests, options);
    if (listener != null) req.setListener(listener);
    if (options != null) {
      req.setResponseFilter(options.getRspFilter());
      req.setAnycasting(options.getAnycasting());
    }
    req.setBlockForResults(block_for_results);
    req.execute();
    return req;
  }

  protected <T> GroupRequest<T> cast(
      final Collection<Address> dests,
      Message msg,
      RequestOptions options,
      boolean block_for_results)
      throws Exception {
    return cast(dests, msg, options, block_for_results, null);
  }

  public void done(long req_id) {
    corr.done(req_id);
  }

  /**
   * Sends a unicast message and - depending on the options - returns a result
   *
   * @param msg the message to be sent. The destination needs to be non-null
   * @param opts the options to be used
   * @return T the result
   * @throws Exception If there was problem sending the request, processing it at the receiver, or
   *     processing it at the sender.
   * @throws TimeoutException If the call didn't succeed within the timeout defined in options (if
   *     set)
   */
  public <T> T sendMessage(Message msg, RequestOptions opts) throws Exception {
    Address dest = msg.getDest();
    if (dest == null)
      throw new IllegalArgumentException("message destination is null, cannot send message");

    if (opts != null) {
      msg.setFlag(opts.getFlags()).setTransientFlag(opts.getTransientFlags());
      if (opts.getScope() > 0) msg.setScope(opts.getScope());
      if (opts.getMode() == ResponseMode.GET_NONE) async_unicasts.incrementAndGet();
      else sync_unicasts.incrementAndGet();
    }

    UnicastRequest<T> req = new UnicastRequest<T>(msg, corr, dest, opts);
    req.execute();

    if (opts != null && opts.getMode() == ResponseMode.GET_NONE) return null;

    Rsp<T> rsp = req.getResult();
    if (rsp.wasSuspected()) throw new SuspectedException(dest);

    Throwable exception = rsp.getException();
    if (exception != null) {
      if (exception instanceof Error) throw (Error) exception;
      else if (exception instanceof RuntimeException) throw (RuntimeException) exception;
      else if (exception instanceof Exception) throw (Exception) exception;
      else throw new RuntimeException(exception);
    }

    if (rsp.wasUnreachable()) throw new UnreachableException(dest);
    if (!rsp.wasReceived() && !req.responseReceived())
      throw new TimeoutException("timeout sending message to " + dest);
    return rsp.getValue();
  }

  /**
   * Sends a unicast message to the target defined by msg.getDest() and returns a future
   *
   * @param msg The unicast message to be sent. msg.getDest() must not be null
   * @param options
   * @param listener A FutureListener which will be registered (if non null) with the future
   *     <em>before</em> the call is invoked
   * @return NotifyingFuture<T> A future from which the result can be fetched
   * @throws Exception If there was problem sending the request, processing it at the receiver, or
   *     processing it at the sender. {@link java.util.concurrent.Future#get()} will throw this
   *     exception
   * @throws TimeoutException If the call didn't succeed within the timeout defined in options (if
   *     set)
   */
  public <T> NotifyingFuture<T> sendMessageWithFuture(
      Message msg, RequestOptions options, FutureListener<T> listener) throws Exception {
    Address dest = msg.getDest();
    if (dest == null)
      throw new IllegalArgumentException("message destination is null, cannot send message");

    if (options != null) {
      msg.setFlag(options.getFlags()).setTransientFlag(options.getTransientFlags());
      if (options.getScope() > 0) msg.setScope(options.getScope());
      if (options.getMode() == ResponseMode.GET_NONE) async_unicasts.incrementAndGet();
      else sync_unicasts.incrementAndGet();
    }

    UnicastRequest<T> req = new UnicastRequest<T>(msg, corr, dest, options);
    if (listener != null) req.setListener(listener);
    req.setBlockForResults(false);
    req.execute();
    if (options != null && options.getMode() == ResponseMode.GET_NONE)
      return new NullFuture<T>(null);
    return req;
  }

  /**
   * Sends a unicast message to the target defined by msg.getDest() and returns a future
   *
   * @param msg The unicast message to be sent. msg.getDest() must not be null
   * @param options
   * @return NotifyingFuture<T> A future from which the result can be fetched
   * @throws Exception If there was problem sending the request, processing it at the receiver, or
   *     processing it at the sender. {@link java.util.concurrent.Future#get()} will throw this
   *     exception
   * @throws TimeoutException If the call didn't succeed within the timeout defined in options (if
   *     set)
   */
  public <T> NotifyingFuture<T> sendMessageWithFuture(Message msg, RequestOptions options)
      throws Exception {
    return sendMessageWithFuture(msg, options, null);
  }

  /* ------------------------ RequestHandler Interface ---------------------- */
  public Object handle(Message msg) throws Exception {
    if (req_handler != null) return req_handler.handle(msg);
    return null;
  }
  /* -------------------- End of RequestHandler Interface ------------------- */

  /* -------------------- AsyncRequestHandler Interface --------------------- */
  public void handle(Message request, Response response) throws Exception {
    if (req_handler != null) {
      if (req_handler instanceof AsyncRequestHandler)
        ((AsyncRequestHandler) req_handler).handle(request, response);
      else {
        Object retval = req_handler.handle(request);
        if (response != null) response.send(retval, false);
      }
      return;
    }

    Object retval = handle(request);
    if (response != null) response.send(retval, false);
  }
  /* ------------------ End of AsyncRequestHandler Interface----------------- */

  /* --------------------- Interface ChannelListener ---------------------- */

  public void channelConnected(Channel channel) {
    for (ChannelListener l : channel_listeners) {
      try {
        l.channelConnected(channel);
      } catch (Throwable t) {
        log.warn("notifying channel listener " + l + " failed", t);
      }
    }
  }

  public void channelDisconnected(Channel channel) {
    stop();
    for (ChannelListener l : channel_listeners) {
      try {
        l.channelDisconnected(channel);
      } catch (Throwable t) {
        log.warn("notifying channel listener " + l + " failed", t);
      }
    }
  }

  public void channelClosed(Channel channel) {
    stop();
    for (ChannelListener l : channel_listeners) {
      try {
        l.channelClosed(channel);
      } catch (Throwable t) {
        log.warn("notifying channel listener " + l + " failed", t);
      }
    }
  }

  /* ----------------------------------------------------------------------- */

  protected Object handleUpEvent(Event evt) throws Exception {
    switch (evt.getType()) {
      case Event.MSG:
        if (msg_listener != null) msg_listener.receive((Message) evt.getArg());
        break;

      case Event.GET_APPLSTATE: // reply with GET_APPLSTATE_OK
        byte[] tmp_state = null;
        if (msg_listener != null) {
          ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
          msg_listener.getState(output);
          tmp_state = output.toByteArray();
        }
        return new StateTransferInfo(null, 0L, tmp_state);

      case Event.GET_STATE_OK:
        if (msg_listener != null) {
          StateTransferResult result = (StateTransferResult) evt.getArg();
          if (result.hasBuffer()) {
            ByteArrayInputStream input = new ByteArrayInputStream(result.getBuffer());
            msg_listener.setState(input);
          }
        }
        break;

      case Event.STATE_TRANSFER_OUTPUTSTREAM:
        OutputStream os = (OutputStream) evt.getArg();
        if (msg_listener != null && os != null) {
          msg_listener.getState(os);
        }
        break;

      case Event.STATE_TRANSFER_INPUTSTREAM:
        InputStream is = (InputStream) evt.getArg();
        if (msg_listener != null && is != null) msg_listener.setState(is);
        break;

      case Event.VIEW_CHANGE:
        View v = (View) evt.getArg();
        List<Address> new_mbrs = v.getMembers();
        setMembers(new_mbrs);
        if (membership_listener != null) membership_listener.viewAccepted(v);
        break;

      case Event.SET_LOCAL_ADDRESS:
        if (log.isTraceEnabled())
          log.trace("setting local_addr (" + local_addr + ") to " + evt.getArg());
        local_addr = (Address) evt.getArg();
        break;

      case Event.SUSPECT:
        if (membership_listener != null) membership_listener.suspect((Address) evt.getArg());
        break;

      case Event.BLOCK:
        if (membership_listener != null) membership_listener.block();
        break;
      case Event.UNBLOCK:
        if (membership_listener != null) membership_listener.unblock();
        break;
    }

    return null;
  }

  class MyProbeHandler implements DiagnosticsHandler.ProbeHandler {

    public Map<String, String> handleProbe(String... keys) {
      Map<String, String> retval = new HashMap<String, String>();
      for (String key : keys) {
        if ("rpcs".equals(key)) {
          String channel_name = channel != null ? channel.getClusterName() : "";
          retval.put(channel_name + ": sync  unicast   RPCs", sync_unicasts.toString());
          retval.put(channel_name + ": sync  multicast RPCs", sync_multicasts.toString());
          retval.put(channel_name + ": async unicast   RPCs", async_unicasts.toString());
          retval.put(channel_name + ": async multicast RPCs", async_multicasts.toString());
          retval.put(channel_name + ": sync  anycast   RPCs", sync_anycasts.toString());
          retval.put(channel_name + ": async anycast   RPCs", async_anycasts.toString());
        }
        if ("rpcs-reset".equals(key)) {
          sync_unicasts.set(0);
          sync_multicasts.set(0);
          async_unicasts.set(0);
          async_multicasts.set(0);
          sync_anycasts.set(0);
          async_anycasts.set(0);
        }
      }
      return retval;
    }

    public String[] supportedKeys() {
      return new String[] {"rpcs", "rpcs-reset"};
    }
  }

  class ProtocolAdapter extends Protocol implements UpHandler {

    /* ------------------------- Protocol Interface --------------------------- */

    public String getName() {
      return "MessageDispatcher";
    }

    /**
     * Called by channel (we registered before) when event is received. This is the UpHandler
     * interface.
     */
    public Object up(Event evt) {
      if (corr != null) {
        if (!corr.receive(evt)) {
          try {
            return handleUpEvent(evt);
          } catch (Throwable t) {
            throw new RuntimeException(t);
          }
        }
      }
      return null;
    }

    public Object down(Event evt) {
      if (channel != null) {
        if (evt.getType() == Event.MSG && !(channel.isConnected() || channel.isConnecting()))
          throw new IllegalStateException("channel is not connected");
        return channel.down(evt);
      }
      return null;
    }

    /* ----------------------- End of Protocol Interface ------------------------ */

  }
}
Beispiel #14
0
/**
 * The Protocol class provides a set of common services for protocol layers. Each layer has to be a
 * subclass of Protocol and override a number of methods (typically just <code>up()</code>, <code>
 * down()</code> and <code>getName()</code>. Layers are stacked in a certain order to form a
 * protocol stack. <a href=org.jgroups.Event.html>Events</a> are passed from lower layers to upper
 * ones and vice versa. E.g. a Message received by the UDP layer at the bottom will be passed to its
 * higher layer as an Event. That layer will in turn pass the Event to its layer and so on, until a
 * layer handles the Message and sends a response or discards it, the former resulting in another
 * Event being passed down the stack.
 *
 * <p>The important thing to bear in mind is that Events have to passed on between layers in FIFO
 * order which is guaranteed by the Protocol implementation and must be guranteed by subclasses
 * implementing their on Event queuing.
 *
 * <p><b>Note that each class implementing interface Protocol MUST provide an empty, public
 * constructor !</b>
 *
 * @author Bela Ban
 */
public abstract class Protocol {
  protected Protocol up_prot = null, down_prot = null;
  protected ProtocolStack stack = null;

  @Property(
      description =
          "Determines whether to collect statistics (and expose them via JMX). Default is true",
      writable = true)
  protected boolean stats = true;

  @Property(
      description =
          "Enables ergonomics: dynamically find the best values for properties at runtime")
  protected boolean ergonomics = true;

  /**
   * The name of the protocol. Is by default set to the protocol's classname. This property should
   * rarely need to be set, e.g. only in cases where we want to create more than 1 protocol of the
   * same class in the same stack
   */
  @Property(
      name = "name",
      description =
          "Give the protocol a different name if needed so we can have multiple "
              + "instances of it in the same stack (also change ID)",
      writable = false)
  protected String name = getClass().getSimpleName();

  @Property(
      description =
          "Give the protocol a different ID if needed so we can have multiple "
              + "instances of it in the same stack",
      writable = false)
  protected short id = ClassConfigurator.getProtocolId(getClass());

  protected final Log log = LogFactory.getLog(this.getClass());

  /**
   * Sets the level of a logger. This method is used to dynamically change the logging level of a
   * running system, e.g. via JMX. The appender of a level needs to exist.
   *
   * @param level The new level. Valid values are "fatal", "error", "warn", "info", "debug", "trace"
   *     (capitalization not relevant)
   */
  @Property(name = "level", description = "Sets the logger level (see javadocs)")
  public void setLevel(String level) {
    log.setLevel(level);
  }

  public String getLevel() {
    return log.getLevel();
  }

  public boolean isErgonomics() {
    return ergonomics;
  }

  public void setErgonomics(boolean ergonomics) {
    this.ergonomics = ergonomics;
  }

  public Object getValue(String name) {
    if (name == null) return null;
    Field field = Util.getField(getClass(), name);
    if (field == null) throw new IllegalArgumentException("field \"" + name + "\n not found");
    return Util.getField(field, this);
  }

  public Protocol setValues(Map<String, Object> values) {
    if (values == null) return this;
    for (Map.Entry<String, Object> entry : values.entrySet()) {
      String attrname = entry.getKey();
      Object value = entry.getValue();
      Field field = Util.getField(getClass(), attrname);
      if (field != null) {
        Util.setField(field, this, value);
      }
    }
    return this;
  }

  public Protocol setValue(String name, Object value) {
    if (name == null || value == null) return this;
    Field field = Util.getField(getClass(), name);
    if (field == null) throw new IllegalArgumentException("field \"" + name + "\n not found");
    Util.setField(field, this, value);
    return this;
  }

  public ProtocolStack getProtocolStack() {
    return stack;
  }

  /**
   * After configuring the protocol itself from the properties defined in the XML config, a protocol
   * might have additional objects which need to be configured. This callback allows a protocol
   * developer to configure those other objects. This call is guaranteed to be invoked
   * <em>after</em> the protocol itself has been configured. See AUTH for an example.
   *
   * @return
   */
  protected List<Object> getConfigurableObjects() {
    return null;
  }

  protected TP getTransport() {
    Protocol retval = this;
    while (retval != null && retval.down_prot != null) {
      retval = retval.down_prot;
    }
    return (TP) retval;
  }

  /**
   * Supposed to be overwritten by subclasses. Usually the transport returns a valid non-null thread
   * factory, but thread factories can also be created by individual protocols
   *
   * @return
   */
  public ThreadFactory getThreadFactory() {
    return down_prot != null ? down_prot.getThreadFactory() : null;
  }

  /**
   * Returns the SocketFactory associated with this protocol, if overridden in a subclass, or passes
   * the call down
   *
   * @return SocketFactory
   */
  public SocketFactory getSocketFactory() {
    return down_prot != null ? down_prot.getSocketFactory() : null;
  }

  /**
   * Sets a SocketFactory. Socket factories are typically provided by the transport ({@link
   * org.jgroups.protocols.TP}) or {@link org.jgroups.protocols.TP.ProtocolAdapter}
   *
   * @param factory
   */
  public void setSocketFactory(SocketFactory factory) {
    if (down_prot != null) down_prot.setSocketFactory(factory);
  }

  public boolean statsEnabled() {
    return stats;
  }

  public void enableStats(boolean flag) {
    stats = flag;
  }

  public void resetStats() {
    ;
  }

  public String printStats() {
    return null;
  }

  public Map<String, Object> dumpStats() {
    HashMap<String, Object> map = new HashMap<String, Object>();
    for (Class<?> clazz = this.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
        if (field.isAnnotationPresent(ManagedAttribute.class)
            || (field.isAnnotationPresent(Property.class)
                && field.getAnnotation(Property.class).exposeAsManagedAttribute())) {
          String attributeName = field.getName();
          try {
            field.setAccessible(true);
            Object value = field.get(this);
            map.put(attributeName, value != null ? value.toString() : null);
          } catch (Exception e) {
            log.warn("Could not retrieve value of attribute (field) " + attributeName, e);
          }
        }
      }

      Method[] methods = this.getClass().getMethods();
      for (Method method : methods) {
        if (method.isAnnotationPresent(ManagedAttribute.class)
            || (method.isAnnotationPresent(Property.class)
                && method.getAnnotation(Property.class).exposeAsManagedAttribute())) {

          String method_name = method.getName();
          if (method_name.startsWith("is") || method_name.startsWith("get")) {
            try {
              Object value = method.invoke(this);
              String attributeName = Util.methodNameToAttributeName(method_name);
              map.put(attributeName, value != null ? value.toString() : null);
            } catch (Exception e) {
              log.warn("Could not retrieve value of attribute (method) " + method_name, e);
            }
          } else if (method_name.startsWith("set")) {
            String stem = method_name.substring(3);
            Method getter = ResourceDMBean.findGetter(getClass(), stem);
            if (getter != null) {
              try {
                Object value = getter.invoke(this);
                String attributeName = Util.methodNameToAttributeName(method_name);
                map.put(attributeName, value != null ? value.toString() : null);
              } catch (Exception e) {
                log.warn("Could not retrieve value of attribute (method) " + method_name, e);
              }
            }
          }
        }
      }
    }
    return map;
  }

  /**
   * Called after instance has been created (null constructor) and before protocol is started.
   * Properties are already set. Other protocols are not yet connected and events cannot yet be
   * sent.
   *
   * @exception Exception Thrown if protocol cannot be initialized successfully. This will cause the
   *     ProtocolStack to fail, so the channel constructor will throw an exception
   */
  public void init() throws Exception {}

  /**
   * This method is called on a {@link org.jgroups.Channel#connect(String)}. Starts work. Protocols
   * are connected and queues are ready to receive events. Will be called <em>from bottom to
   * top</em>. This call will replace the <b>START</b> and <b>START_OK</b> events.
   *
   * @exception Exception Thrown if protocol cannot be started successfully. This will cause the
   *     ProtocolStack to fail, so {@link org.jgroups.Channel#connect(String)} will throw an
   *     exception
   */
  public void start() throws Exception {}

  /**
   * This method is called on a {@link org.jgroups.Channel#disconnect()}. Stops work (e.g. by
   * closing multicast socket). Will be called <em>from top to bottom</em>. This means that at the
   * time of the method invocation the neighbor protocol below is still working. This method will
   * replace the <b>STOP</b>, <b>STOP_OK</b>, <b>CLEANUP</b> and <b>CLEANUP_OK</b> events. The
   * ProtocolStack guarantees that when this method is called all messages in the down queue will
   * have been flushed
   */
  public void stop() {}

  /**
   * This method is called on a {@link org.jgroups.Channel#close()}. Does some cleanup; after the
   * call the VM will terminate
   */
  public void destroy() {}

  /**
   * List of events that are required to be answered by some layer above.
   *
   * @return Vector (of Integers)
   */
  public List<Integer> requiredUpServices() {
    return null;
  }

  /**
   * List of events that are required to be answered by some layer below.
   *
   * @return Vector (of Integers)
   */
  public List<Integer> requiredDownServices() {
    return null;
  }

  /**
   * List of events that are provided to layers above (they will be handled when sent down from
   * above).
   *
   * @return Vector (of Integers)
   */
  public List<Integer> providedUpServices() {
    return null;
  }

  /**
   * List of events that are provided to layers below (they will be handled when sent down from
   * below).
   *
   * @return Vector<Integer (of Integers)
   */
  public List<Integer> providedDownServices() {
    return null;
  }

  /** All protocol names have to be unique ! */
  public String getName() {
    return name;
  }

  public short getId() {
    return id;
  }

  public void setId(short id) {
    this.id = id;
  }

  public Protocol getUpProtocol() {
    return up_prot;
  }

  public Protocol getDownProtocol() {
    return down_prot;
  }

  public void setUpProtocol(Protocol up_prot) {
    this.up_prot = up_prot;
  }

  public void setDownProtocol(Protocol down_prot) {
    this.down_prot = down_prot;
  }

  public void setProtocolStack(ProtocolStack stack) {
    this.stack = stack;
  }

  /**
   * An event was received from the layer below. Usually the current layer will want to examine the
   * event type and - depending on its type - perform some computation (e.g. removing headers from a
   * MSG event type, or updating the internal membership list when receiving a VIEW_CHANGE event).
   * Finally the event is either a) discarded, or b) an event is sent down the stack using <code>
   * down_prot.down()</code> or c) the event (or another event) is sent up the stack using <code>
   * up_prot.up()</code>.
   */
  public Object up(Event evt) {
    return up_prot.up(evt);
  }

  /**
   * An event is to be sent down the stack. The layer may want to examine its type and perform some
   * action on it, depending on the event's type. If the event is a message MSG, then the layer may
   * need to add a header to it (or do nothing at all) before sending it down the stack using <code>
   * down_prot.down()</code>. In case of a GET_ADDRESS event (which tries to retrieve the stack's
   * address from one of the bottom layers), the layer may need to send a new response event back up
   * the stack using <code>up_prot.up()</code>.
   */
  public Object down(Event evt) {
    return down_prot.down(evt);
  }
}
public class TCPConnectionMap {

  private final Mapper mapper;
  private final InetAddress bind_addr;
  private final Address local_addr; // bind_addr + port of srv_sock
  private final ThreadGroup thread_group =
      new ThreadGroup(Util.getGlobalThreadGroup(), "ConnectionMap");
  private final ServerSocket srv_sock;
  private Receiver receiver;
  private final long conn_expire_time;
  private final Log log = LogFactory.getLog(getClass());
  private int recv_buf_size = 120000;
  private int send_buf_size = 60000;
  private int send_queue_size = 0;
  private int sock_conn_timeout =
      1000; // max time in millis to wait for Socket.connect() to return
  private boolean tcp_nodelay = false;
  private int linger = -1;
  private final Thread acceptor;
  private final AtomicBoolean running = new AtomicBoolean(false);
  private volatile boolean use_send_queues = false;
  protected SocketFactory socket_factory = new DefaultSocketFactory();

  public TCPConnectionMap(
      String service_name,
      ThreadFactory f,
      SocketFactory socket_factory,
      Receiver r,
      InetAddress bind_addr,
      InetAddress external_addr,
      int srv_port,
      int max_port)
      throws Exception {
    this(service_name, f, socket_factory, r, bind_addr, external_addr, srv_port, max_port, 0, 0);
  }

  public TCPConnectionMap(
      String service_name,
      ThreadFactory f,
      Receiver r,
      InetAddress bind_addr,
      InetAddress external_addr,
      int srv_port,
      int max_port,
      long reaper_interval,
      long conn_expire_time)
      throws Exception {
    this(
        service_name,
        f,
        null,
        r,
        bind_addr,
        external_addr,
        srv_port,
        max_port,
        reaper_interval,
        conn_expire_time);
  }

  public TCPConnectionMap(
      String service_name,
      ThreadFactory f,
      SocketFactory socket_factory,
      Receiver r,
      InetAddress bind_addr,
      InetAddress external_addr,
      int srv_port,
      int max_port,
      long reaper_interval,
      long conn_expire_time)
      throws Exception {
    this.mapper = new Mapper(f, reaper_interval);
    this.receiver = r;
    this.bind_addr = bind_addr;
    this.conn_expire_time = conn_expire_time;
    if (socket_factory != null) this.socket_factory = socket_factory;
    this.srv_sock =
        Util.createServerSocket(this.socket_factory, service_name, bind_addr, srv_port, max_port);

    if (external_addr != null) local_addr = new IpAddress(external_addr, srv_sock.getLocalPort());
    else if (bind_addr != null) local_addr = new IpAddress(bind_addr, srv_sock.getLocalPort());
    else local_addr = new IpAddress(srv_sock.getLocalPort());

    acceptor = f.newThread(thread_group, new ConnectionAcceptor(), "ConnectionMap.Acceptor");
  }

  public Address getLocalAddress() {
    return local_addr;
  }

  public Receiver getReceiver() {
    return receiver;
  }

  public void setReceiver(Receiver receiver) {
    this.receiver = receiver;
  }

  public SocketFactory getSocketFactory() {
    return socket_factory;
  }

  public void setSocketFactory(SocketFactory socket_factory) {
    this.socket_factory = socket_factory;
  }

  public void addConnectionMapListener(
      AbstractConnectionMap.ConnectionMapListener<TCPConnection> l) {
    mapper.addConnectionMapListener(l);
  }

  public void removeConnectionMapListener(
      AbstractConnectionMap.ConnectionMapListener<TCPConnection> l) {
    mapper.removeConnectionMapListener(l);
  }

  /**
   * Calls the receiver callback. We do not serialize access to this method, and it may be called
   * concurrently by several Connection handler threads. Therefore the receiver needs to be
   * reentrant.
   */
  public void receive(Address sender, byte[] data, int offset, int length) {
    receiver.receive(sender, data, offset, length);
  }

  public void send(Address dest, byte[] data, int offset, int length) throws Exception {
    if (dest == null) {
      if (log.isErrorEnabled()) log.error("destination is null");
      return;
    }

    if (data == null) {
      log.warn("data is null; discarding packet");
      return;
    }

    if (!running.get()) {
      if (log.isDebugEnabled())
        log.debug("connection table is not running, discarding message to " + dest);
      return;
    }

    if (dest.equals(local_addr)) {
      receive(local_addr, data, offset, length);
      return;
    }

    // 1. Try to obtain correct Connection (or create one if not yet existent)
    TCPConnection conn;
    conn = mapper.getConnection(dest);

    // 2. Send the message using that connection
    if (conn != null) {
      try {
        conn.send(data, offset, length);
      } catch (Exception ex) {
        mapper.removeConnection(dest);
        throw ex;
      }
    }
  }

  public void start() throws Exception {
    if (running.compareAndSet(false, true)) {
      acceptor.start();
      mapper.start();
    }
  }

  public void stop() {
    if (running.compareAndSet(true, false)) {
      try {
        getSocketFactory().close(srv_sock);
      } catch (IOException e) {
      }
      Util.interruptAndWaitToDie(acceptor);
      mapper.stop();
    }
  }

  private void setSocketParameters(Socket client_sock) throws SocketException {
    if (log.isTraceEnabled())
      log.trace(
          "["
              + local_addr
              + "] accepted connection from "
              + client_sock.getInetAddress()
              + ":"
              + client_sock.getPort());
    try {
      client_sock.setSendBufferSize(send_buf_size);
    } catch (IllegalArgumentException ex) {
      if (log.isErrorEnabled())
        log.error("exception setting send buffer size to " + send_buf_size + " bytes", ex);
    }
    try {
      client_sock.setReceiveBufferSize(recv_buf_size);
    } catch (IllegalArgumentException ex) {
      if (log.isErrorEnabled())
        log.error("exception setting receive buffer size to " + send_buf_size + " bytes", ex);
    }

    client_sock.setKeepAlive(true);
    client_sock.setTcpNoDelay(tcp_nodelay);
    if (linger > 0) client_sock.setSoLinger(true, linger);
    else client_sock.setSoLinger(false, -1);
  }

  /** Used for message reception. */
  public interface Receiver {
    void receive(Address sender, byte[] data, int offset, int length);
  }

  private class ConnectionAcceptor implements Runnable {

    /**
     * Acceptor thread. Continuously accept new connections. Create a new thread for each new
     * connection and put it in conns. When the thread should stop, it is interrupted by the thread
     * creator.
     */
    public void run() {
      while (!srv_sock.isClosed() && !Thread.currentThread().isInterrupted()) {
        TCPConnection conn = null;
        Socket client_sock = null;
        try {
          client_sock = srv_sock.accept();
          conn = new TCPConnection(client_sock);
          Address peer_addr = conn.getPeerAddress();
          mapper.getLock().lock();
          try {
            boolean currentConnectionOpen = mapper.hasOpenConnection(peer_addr);
            boolean replaceWithNewConnection = false;
            if (currentConnectionOpen) {
              replaceWithNewConnection = peer_addr.compareTo(local_addr) > 0;
            }
            if (!currentConnectionOpen || replaceWithNewConnection) {
              mapper.removeConnection(peer_addr);
              mapper.addConnection(peer_addr, conn);
              conn.start(mapper.getThreadFactory()); // starts handler thread on this socket
            } else {
              Util.close(conn);
            }
          } finally {
            mapper.getLock().unlock();
          }
        } catch (SocketException se) {
          boolean threadExiting = srv_sock.isClosed() || Thread.currentThread().isInterrupted();
          if (threadExiting) {
            break;
          } else {
            if (log.isWarnEnabled()) log.warn("Could not accept connection from peer ", se);
            Util.close(conn);
            Util.close(client_sock);
          }
        } catch (Exception ex) {
          if (log.isWarnEnabled()) log.warn("Could not read accept connection from peer " + ex);
          Util.close(conn);
          Util.close(client_sock);
        }
      }
      if (log.isTraceEnabled()) log.trace(Thread.currentThread().getName() + " terminated");
    }
  }

  public void setReceiveBufferSize(int recv_buf_size) {
    this.recv_buf_size = recv_buf_size;
  }

  public void setSocketConnectionTimeout(int sock_conn_timeout) {
    this.sock_conn_timeout = sock_conn_timeout;
  }

  public void setSendBufferSize(int send_buf_size) {
    this.send_buf_size = send_buf_size;
  }

  public void setLinger(int linger) {
    this.linger = linger;
  }

  public void setTcpNodelay(boolean tcp_nodelay) {
    this.tcp_nodelay = tcp_nodelay;
  }

  public void setSendQueueSize(int send_queue_size) {
    this.send_queue_size = send_queue_size;
  }

  public void setUseSendQueues(boolean use_send_queues) {
    this.use_send_queues = use_send_queues;
  }

  public int getNumConnections() {
    return mapper.getNumConnections();
  }

  public boolean connectionEstablishedTo(Address addr) {
    return mapper.connectionEstablishedTo(addr);
  }

  public String printConnections() {
    return mapper.printConnections();
  }

  public void retainAll(Collection<Address> members) {
    mapper.retainAll(members);
  }

  public long getConnectionExpiryTimeout() {
    return conn_expire_time;
  }

  public int getSenderQueueSize() {
    return send_queue_size;
  }

  public String toString() {
    StringBuilder ret = new StringBuilder();
    ret.append("local_addr=" + local_addr).append("\n");
    ret.append("connections (" + mapper.size() + "):\n");
    ret.append(mapper.toString());
    ret.append('\n');
    return ret.toString();
  }

  public class TCPConnection implements Connection {

    private final Socket sock; // socket to/from peer (result of srv_sock.accept() or new Socket())
    private final Lock send_lock = new ReentrantLock(); // serialize send()
    private final Log log = LogFactory.getLog(getClass());
    private final byte[] cookie = {'b', 'e', 'l', 'a'};
    private final DataOutputStream out;
    private final DataInputStream in;
    private final Address peer_addr; // address of the 'other end' of the connection
    private final int peer_addr_read_timeout =
        2000; // max time in milliseconds to block on reading peer address
    private long last_access =
        System.currentTimeMillis(); // last time a message was sent or received
    private Sender sender;
    private ConnectionPeerReceiver connectionPeerReceiver;
    private AtomicBoolean active = new AtomicBoolean(false);

    TCPConnection(Address peer_addr) throws Exception {
      if (peer_addr == null)
        throw new IllegalArgumentException("Invalid parameter peer_addr=" + peer_addr);
      SocketAddress destAddr =
          new InetSocketAddress(
              ((IpAddress) peer_addr).getIpAddress(), ((IpAddress) peer_addr).getPort());
      this.sock = new Socket();
      this.sock.bind(new InetSocketAddress(bind_addr, 0));
      Util.connect(this.sock, destAddr, sock_conn_timeout);
      setSocketParameters(sock);
      this.out = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream()));
      this.in = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
      sendLocalAddress(getLocalAddress());
      this.peer_addr = peer_addr;
    }

    TCPConnection(Socket s) throws Exception {
      if (s == null) throw new IllegalArgumentException("Invalid parameter s=" + s);
      setSocketParameters(s);
      this.out = new DataOutputStream(new BufferedOutputStream(s.getOutputStream()));
      this.in = new DataInputStream(new BufferedInputStream(s.getInputStream()));
      this.peer_addr = readPeerAddress(s);
      this.sock = s;
    }

    private Address getPeerAddress() {
      return peer_addr;
    }

    private void updateLastAccessed() {
      last_access = System.currentTimeMillis();
    }

    private void start(ThreadFactory f) {
      // only start once....
      if (active.compareAndSet(false, true)) {
        connectionPeerReceiver = new ConnectionPeerReceiver(f);
        connectionPeerReceiver.start();

        if (isSenderUsed()) {
          sender = new Sender(f, getSenderQueueSize());
          sender.start();
        }
      }
    }

    private boolean isSenderUsed() {
      return getSenderQueueSize() > 0 && use_send_queues;
    }

    private String getSockAddress() {
      StringBuilder sb = new StringBuilder();
      if (sock != null) {
        sb.append(sock.getLocalAddress().getHostAddress()).append(':').append(sock.getLocalPort());
        sb.append(" - ")
            .append(sock.getInetAddress().getHostAddress())
            .append(':')
            .append(sock.getPort());
      }
      return sb.toString();
    }

    /**
     * @param data Guaranteed to be non null
     * @param offset
     * @param length
     */
    private void send(byte[] data, int offset, int length) throws Exception {
      if (isSenderUsed()) {
        // we need to copy the byte[] buffer here because the original buffer might get
        // changed meanwhile
        byte[] tmp = new byte[length];
        System.arraycopy(data, offset, tmp, 0, length);
        sender.addToQueue(tmp);
      } else {
        _send(data, offset, length, true);
      }
    }

    /**
     * Sends data using the 'out' output stream of the socket
     *
     * @param data
     * @param offset
     * @param length
     * @param acquire_lock
     * @throws Exception
     */
    private void _send(byte[] data, int offset, int length, boolean acquire_lock) throws Exception {
      if (acquire_lock) send_lock.lock();

      try {
        doSend(data, offset, length);
        updateLastAccessed();
      } catch (InterruptedException iex) {
        Thread.currentThread().interrupt(); // set interrupt flag again
      } finally {
        if (acquire_lock) send_lock.unlock();
      }
    }

    private void doSend(byte[] data, int offset, int length) throws Exception {
      // we're using 'double-writes', sending the buffer to the destination in 2 pieces. this would
      // ensure that, if the peer closed the connection while we were idle, we would get an
      // exception.
      // this won't happen if we use a single write (see Stevens, ch. 5.13).

      out.writeInt(length); // write the length of the data buffer first
      Util.doubleWrite(data, offset, length, out);
      out.flush(); // may not be very efficient (but safe)
    }

    /**
     * Reads the peer's address. First a cookie has to be sent which has to match my own cookie,
     * otherwise the connection will be refused
     */
    private Address readPeerAddress(Socket client_sock) throws Exception {
      int timeout = client_sock.getSoTimeout();
      client_sock.setSoTimeout(peer_addr_read_timeout);

      try {
        // read the cookie first
        byte[] input_cookie = new byte[cookie.length];
        in.readFully(input_cookie, 0, input_cookie.length);
        if (!matchCookie(input_cookie))
          throw new SocketException(
              "ConnectionMap.Connection.readPeerAddress(): cookie read by "
                  + getLocalAddress()
                  + " does not match own cookie; terminating connection");
        // then read the version
        short version = in.readShort();

        if (!Version.isBinaryCompatible(version)) {
          if (log.isWarnEnabled())
            log.warn(
                new StringBuilder("packet from ")
                    .append(client_sock.getInetAddress())
                    .append(':')
                    .append(client_sock.getPort())
                    .append(" has different version (")
                    .append(Version.print(version))
                    .append(") from ours (")
                    .append(Version.printVersion())
                    .append("). This may cause problems")
                    .toString());
        }
        Address client_peer_addr = new IpAddress();
        client_peer_addr.readFrom(in);

        updateLastAccessed();
        return client_peer_addr;
      } finally {
        client_sock.setSoTimeout(timeout);
      }
    }

    /**
     * Send the cookie first, then the our port number. If the cookie doesn't match the receiver's
     * cookie, the receiver will reject the connection and close it.
     *
     * @throws IOException
     */
    private void sendLocalAddress(Address local_addr) throws IOException {
      // write the cookie
      out.write(cookie, 0, cookie.length);

      // write the version
      out.writeShort(Version.version);
      local_addr.writeTo(out);
      out.flush(); // needed ?
      updateLastAccessed();
    }

    private boolean matchCookie(byte[] input) {
      if (input == null || input.length < cookie.length) return false;
      for (int i = 0; i < cookie.length; i++) if (cookie[i] != input[i]) return false;
      return true;
    }

    private class ConnectionPeerReceiver implements Runnable {
      private Thread recv;
      private final AtomicBoolean receiving = new AtomicBoolean(false);

      public ConnectionPeerReceiver(ThreadFactory f) {
        recv = f.newThread(this, "Connection.Receiver [" + getSockAddress() + "]");
      }

      public void start() {
        if (receiving.compareAndSet(false, true)) {
          recv.start();
        }
      }

      public void stop() {
        if (receiving.compareAndSet(true, false)) {
          recv.interrupt();
        }
      }

      public boolean isRunning() {
        return receiving.get();
      }

      public boolean canRun() {
        return isRunning() && isConnected();
      }

      public void run() {
        try {
          while (!Thread.currentThread().isInterrupted() && canRun()) {
            try {
              int len = in.readInt();
              byte[] buf = new byte[len];
              in.readFully(buf, 0, len);
              updateLastAccessed();
              receiver.receive(peer_addr, buf, 0, len);
            } catch (OutOfMemoryError mem_ex) {
              break; // continue;
            } catch (IOException io_ex) {
              break;
            } catch (Throwable e) {
            }
          }
        } finally {
          Util.close(TCPConnection.this);
        }
      }
    }

    private class Sender implements Runnable {

      final BlockingQueue<byte[]> send_queue;
      final Thread runner;
      private final AtomicBoolean running = new AtomicBoolean(false);

      public Sender(ThreadFactory tf, int send_queue_size) {
        this.runner = tf.newThread(this, "Connection.Sender [" + getSockAddress() + "]");
        this.send_queue = new LinkedBlockingQueue<byte[]>(send_queue_size);
      }

      public void addToQueue(byte[] data) throws Exception {
        if (canRun()) send_queue.add(data);
      }

      public void start() {
        if (running.compareAndSet(false, true)) {
          runner.start();
        }
      }

      public void stop() {
        if (running.compareAndSet(true, false)) {
          runner.interrupt();
        }
      }

      public boolean isRunning() {
        return running.get();
      }

      public boolean canRun() {
        return isRunning() && isConnected();
      }

      public void run() {
        try {
          while (!Thread.currentThread().isInterrupted() && canRun()) {
            byte[] data = null;
            try {
              data = send_queue.take();
            } catch (InterruptedException e) {
              // Thread.currentThread().interrupt();
              break;
            }

            if (data != null) {
              try {
                _send(data, 0, data.length, false);
              } catch (Throwable ignored) {
              }
            }
          }
        } finally {
          Util.close(TCPConnection.this);
        }
        if (log.isTraceEnabled())
          log.trace("TCPConnection.Sender thread terminated at " + local_addr);
      }
    }

    public String toString() {
      StringBuilder ret = new StringBuilder();
      InetAddress local = null, remote = null;
      String local_str, remote_str;

      Socket tmp_sock = sock;
      if (tmp_sock == null) ret.append("<null socket>");
      else {
        // since the sock variable gets set to null we want to make
        // make sure we make it through here without a nullpointer exception
        local = tmp_sock.getLocalAddress();
        remote = tmp_sock.getInetAddress();
        local_str = local != null ? Util.shortName(local) : "<null>";
        remote_str = remote != null ? Util.shortName(remote) : "<null>";
        ret.append(
            '<'
                + local_str
                + ':'
                + tmp_sock.getLocalPort()
                + " --> "
                + remote_str
                + ':'
                + tmp_sock.getPort()
                + "> ("
                + ((System.currentTimeMillis() - last_access) / 1000)
                + " secs old)");
      }
      tmp_sock = null;

      return ret.toString();
    }

    public boolean isExpired(long now) {
      return getConnectionExpiryTimeout() > 0 && now - last_access >= getConnectionExpiryTimeout();
    }

    public boolean isConnected() {
      return !sock.isClosed() && sock.isConnected();
    }

    public boolean isOpen() {
      return isConnected()
          && (!isSenderUsed() || sender.isRunning())
          && (connectionPeerReceiver != null && connectionPeerReceiver.isRunning());
    }

    public void close() throws IOException {
      // can close even if start was never called...
      send_lock.lock();
      try {
        connectionPeerReceiver.stop();
        if (isSenderUsed()) {
          sender.stop();
        }
        Util.close(sock);
        Util.close(out);
        Util.close(in);
      } finally {
        send_lock.unlock();
      }
      mapper.notifyConnectionClosed(peer_addr);
    }
  }

  private class Mapper extends AbstractConnectionMap<TCPConnection> {

    public Mapper(ThreadFactory factory) {
      super(factory);
    }

    public Mapper(ThreadFactory factory, long reaper_interval) {
      super(factory, reaper_interval);
    }

    public TCPConnection getConnection(Address dest) throws Exception {
      TCPConnection conn = null;
      getLock().lock();
      try {
        conn = conns.get(dest);
        if (conn != null && conn.isOpen()) return conn;
        try {
          conn = new TCPConnection(dest);
          conn.start(getThreadFactory());
          addConnection(dest, conn);
          if (log.isTraceEnabled()) log.trace("created socket to " + dest);
        } catch (Exception ex) {
          if (log.isTraceEnabled()) log.trace("failed creating connection to " + dest);
        }
      } finally {
        getLock().unlock();
      }
      return conn;
    }

    public boolean connectionEstablishedTo(Address address) {
      lock.lock();
      try {
        TCPConnection conn = conns.get(address);
        return conn != null && conn.isConnected();
      } finally {
        lock.unlock();
      }
    }

    public int size() {
      return conns.size();
    }

    public String toString() {
      StringBuilder sb = new StringBuilder();

      getLock().lock();
      try {
        for (Map.Entry<Address, TCPConnection> entry : conns.entrySet()) {
          sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
        }
        return sb.toString();
      } finally {
        getLock().unlock();
      }
    }
  }
}
Beispiel #16
0
/**
 * A Message encapsulates data sent to members of a group. It contains among other things the
 * address of the sender, the destination address, a payload (byte buffer) and a list of headers.
 * Headers are added by protocols on the sender side and removed by protocols on the receiver's
 * side.
 *
 * <p>The byte buffer can point to a reference, and we can subset it using index and length.
 * However, when the message is serialized, we only write the bytes between index and length.
 *
 * @author Bela Ban
 */
public class Message implements Streamable {
  protected Address dest_addr;
  protected Address src_addr;

  /** The payload */
  private byte[] buf;

  /** The index into the payload (usually 0) */
  protected int offset;

  /** The number of bytes in the buffer (usually buf.length is buf not equal to null). */
  protected int length;

  /** All headers are placed here */
  protected Headers headers;

  private volatile byte flags;

  private volatile byte transient_flags; // transient_flags is neither marshalled nor copied

  protected static final Log log = LogFactory.getLog(Message.class);

  static final byte DEST_SET = 1 << 0;
  static final byte SRC_SET = 1 << 1;
  static final byte BUF_SET = 1 << 2;
  // static final byte HDRS_SET=8; // bela July 15 2005: not needed, we always create headers

  // =============================== Flags ====================================
  public static final byte OOB = 1 << 0;
  public static final byte LOOPBACK = 1 << 1; // if message was sent to self
  public static final byte DONT_BUNDLE = 1 << 2; // don't bundle message at the transport
  public static final byte NO_FC = 1 << 3; // bypass flow control
  public static final byte SCOPED = 1 << 4; // when a message has a scope

  // the following 2 flags will be removed in 3.x, when https://jira.jboss.org/browse/JGRP-1250 has
  // been resolved
  public static final byte NO_RELIABILITY = 1 << 5; // bypass UNICAST(2) and NAKACK
  public static final byte NO_TOTAL_ORDER = 1 << 6; // bypass total order (e.g. SEQUENCER)

  // =========================== Transient flags ==============================
  public static final byte OOB_DELIVERED =
      1 << 0; // OOB which has already been delivered up the stack

  /**
   * Public constructor
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   */
  public Message(Address dest) {
    setDest(dest);
    headers = createHeaders(3);
  }

  /**
   * Public constructor
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   * @param src Address of sender
   * @param buf Message to be sent. Note that this buffer must not be modified (e.g. buf[0]=0 is not
   *     allowed), since we don't copy the contents on clopy() or clone().
   */
  public Message(Address dest, Address src, byte[] buf) {
    this(dest);
    setSrc(src);
    setBuffer(buf);
  }

  /**
   * Constructs a message. The index and length parameters allow to provide a <em>reference</em> to
   * a byte buffer, rather than a copy, and refer to a subset of the buffer. This is important when
   * we want to avoid copying. When the message is serialized, only the subset is serialized.<br>
   * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we
   * retransmit the message, it would still have a ref to the original byte[] buffer passed in as
   * argument, and so we would retransmit a changed byte[] buffer ! </em>
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   * @param src Address of sender
   * @param buf A reference to a byte buffer
   * @param offset The index into the byte buffer
   * @param length The number of bytes to be used from <tt>buf</tt>. Both index and length are
   *     checked for array index violations and an ArrayIndexOutOfBoundsException will be thrown if
   *     invalid
   */
  public Message(Address dest, Address src, byte[] buf, int offset, int length) {
    this(dest);
    setSrc(src);
    setBuffer(buf, offset, length);
  }

  /**
   * Public constructor
   *
   * @param dest Address of receiver. If it is <em>null</em> then the message sent to the group.
   *     Otherwise, it contains a single destination and is sent to that member.
   *     <p>
   * @param src Address of sender
   * @param obj The object will be serialized into the byte buffer. <em>Object has to be
   *     serializable </em>! The resulting buffer must not be modified (e.g. buf[0]=0 is not
   *     allowed), since we don't copy the contents on clopy() or clone().
   *     <p>Note that this is a convenience method and JGroups will use default Java serialization
   *     to serialize <code>obj</code> into a byte buffer.
   */
  public Message(Address dest, Address src, Serializable obj) {
    this(dest);
    setSrc(src);
    setObject(obj);
  }

  public Message() {
    headers = createHeaders(3);
  }

  public Message(boolean create_headers) {
    if (create_headers) headers = createHeaders(3);
  }

  public Address getDest() {
    return dest_addr;
  }

  public void setDest(Address new_dest) {
    dest_addr = new_dest;
  }

  public Address getSrc() {
    return src_addr;
  }

  public void setSrc(Address new_src) {
    src_addr = new_src;
  }

  /**
   * Returns a <em>reference</em> to the payload (byte buffer). Note that this buffer should not be
   * modified as we do not copy the buffer on copy() or clone(): the buffer of the copied message is
   * simply a reference to the old buffer.<br>
   * Even if offset and length are used: we return the <em>entire</em> buffer, not a subset.
   */
  public byte[] getRawBuffer() {
    return buf;
  }

  /**
   * Returns a copy of the buffer if offset and length are used, otherwise a reference.
   *
   * @return byte array with a copy of the buffer.
   */
  public final byte[] getBuffer() {
    if (buf == null) return null;
    if (offset == 0 && length == buf.length) return buf;
    else {
      byte[] retval = new byte[length];
      System.arraycopy(buf, offset, retval, 0, length);
      return retval;
    }
  }

  public final void setBuffer(byte[] b) {
    buf = b;
    if (buf != null) {
      offset = 0;
      length = buf.length;
    } else {
      offset = length = 0;
    }
  }

  /**
   * Set the internal buffer to point to a subset of a given buffer
   *
   * @param b The reference to a given buffer. If null, we'll reset the buffer to null
   * @param offset The initial position
   * @param length The number of bytes
   */
  public final void setBuffer(byte[] b, int offset, int length) {
    buf = b;
    if (buf != null) {
      if (offset < 0 || offset > buf.length) throw new ArrayIndexOutOfBoundsException(offset);
      if ((offset + length) > buf.length)
        throw new ArrayIndexOutOfBoundsException((offset + length));
      this.offset = offset;
      this.length = length;
    } else {
      this.offset = this.length = 0;
    }
  }

  /**
   * <em> Note that the byte[] buffer passed as argument must not be modified. Reason: if we
   * retransmit the message, it would still have a ref to the original byte[] buffer passed in as
   * argument, and so we would retransmit a changed byte[] buffer ! </em>
   */
  public final void setBuffer(Buffer buf) {
    if (buf != null) {
      this.buf = buf.getBuf();
      this.offset = buf.getOffset();
      this.length = buf.getLength();
    }
  }

  /** Returns the offset into the buffer at which the data starts */
  public int getOffset() {
    return offset;
  }

  /** Returns the number of bytes in the buffer */
  public int getLength() {
    return length;
  }

  /**
   * Returns a reference to the headers hashmap, which is <em>immutable</em>. Any attempt to modify
   * the returned map will cause a runtime exception
   */
  public Map<Short, Header> getHeaders() {
    return headers.getHeaders();
  }

  public String printHeaders() {
    return headers.printHeaders();
  }

  public int getNumHeaders() {
    return headers.size();
  }

  /**
   * Takes an object and uses Java serialization to generate the byte[] buffer which is set in the
   * message.
   */
  public final void setObject(Serializable obj) {
    if (obj == null) return;
    try {
      byte[] tmp = Util.objectToByteBuffer(obj);
      setBuffer(tmp);
    } catch (Exception ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  /**
   * Uses Java serialization to create an object from the buffer of the message. Note that this is
   * dangerous when using your own classloader, e.g. inside of an application server ! Most likely,
   * JGroups will use the system classloader to deserialize the buffer into an object, whereas (for
   * example) a web application will want to use the webapp's classloader, resulting in a
   * ClassCastException. The recommended way is for the application to use their own serialization
   * and only pass byte[] buffer to JGroups.
   *
   * @return
   */
  public final Object getObject() {
    try {
      return Util.objectFromByteBuffer(buf, offset, length);
    } catch (Exception ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  public void setFlag(byte flag) {
    if (flag > Byte.MAX_VALUE || flag < 0)
      throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE);
    flags |= flag;
  }

  public void clearFlag(byte flag) {
    if (flag > Byte.MAX_VALUE || flag < 0)
      throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE);
    flags &= ~flag;
  }

  public boolean isFlagSet(byte flag) {
    return isFlagSet(flags, flag);
  }

  /**
   * Same as {@link #setFlag(byte)} but transient flags are never marshalled
   *
   * @param flag
   */
  public void setTransientFlag(byte flag) {
    if (flag > Byte.MAX_VALUE || flag < 0)
      throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE);
    transient_flags |= flag;
  }

  /**
   * Atomically checks if a given flag is set and - if not - sets it. When multiple threads
   * concurrently call this method with the same flag, only one of them will be able to set the flag
   *
   * @param flag
   * @return True if the flag could be set, false if not (was already set)
   */
  public boolean setTransientFlagIfAbsent(byte flag) {
    if (flag > Byte.MAX_VALUE || flag < 0)
      throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE);
    synchronized (this) {
      if (isTransientFlagSet(flag)) return false;
      else setTransientFlag(flag);
      return true;
    }
  }

  public void clearTransientFlag(byte flag) {
    if (flag > Byte.MAX_VALUE || flag < 0)
      throw new IllegalArgumentException("flag has to be >= 0 and <= " + Byte.MAX_VALUE);
    transient_flags &= ~flag;
  }

  public boolean isTransientFlagSet(byte flag) {
    return isFlagSet(transient_flags, flag);
  }

  protected static boolean isFlagSet(byte flags, byte flag) {
    return (flags & flag) == flag;
  }

  public byte getFlags() {
    return flags;
  }

  public byte getTransientFlags() {
    return transient_flags;
  }

  public void setScope(short scope) {
    Util.setScope(this, scope);
  }

  public short getScope() {
    return Util.getScope(this);
  }

  /*---------------------- Used by protocol layers ----------------------*/

  /** Puts a header given an ID into the hashmap. Overwrites potential existing entry. */
  public void putHeader(short id, Header hdr) {
    if (id < 0) throw new IllegalArgumentException("An ID of " + id + " is invalid");
    headers.putHeader(id, hdr);
  }

  /**
   * Puts a header given a key into the map, only if the key doesn't exist yet
   *
   * @param id
   * @param hdr
   * @return the previous value associated with the specified key, or <tt>null</tt> if there was no
   *     mapping for the key. (A <tt>null</tt> return can also indicate that the map previously
   *     associated <tt>null</tt> with the key, if the implementation supports null values.)
   */
  public Header putHeaderIfAbsent(short id, Header hdr) {
    if (id <= 0) throw new IllegalArgumentException("An ID of " + id + " is invalid");
    return headers.putHeaderIfAbsent(id, hdr);
  }

  /**
   * @param key
   * @return the header associated with key
   * @deprecated Use getHeader() instead. The issue with removing a header is described in
   *     http://jira.jboss.com/jira/browse/JGRP-393
   */
  public Header removeHeader(short id) {
    return getHeader(id);
  }

  public Header getHeader(short id) {
    if (id <= 0) throw new IllegalArgumentException("An ID of " + id + " is invalid");
    return headers.getHeader(id);
  }
  /*---------------------------------------------------------------------*/

  public Message copy() {
    return copy(true);
  }

  /**
   * Create a copy of the message. If offset and length are used (to refer to another buffer), the
   * copy will contain only the subset offset and length point to, copying the subset into the new
   * copy.
   *
   * @param copy_buffer
   * @return Message with specified data
   */
  public Message copy(boolean copy_buffer) {
    return copy(copy_buffer, true);
  }

  /**
   * Create a copy of the message. If offset and length are used (to refer to another buffer), the
   * copy will contain only the subset offset and length point to, copying the subset into the new
   * copy.
   *
   * @param copy_buffer
   * @param copy_headers Copy the headers
   * @return Message with specified data
   */
  public Message copy(boolean copy_buffer, boolean copy_headers) {
    Message retval = new Message(false);
    retval.dest_addr = dest_addr;
    retval.src_addr = src_addr;
    retval.flags = flags;

    if (copy_buffer && buf != null) {

      // change bela Feb 26 2004: we don't resolve the reference
      retval.setBuffer(buf, offset, length);
    }

    retval.headers = copy_headers ? createHeaders(headers) : createHeaders(3);
    return retval;
  }

  protected Object clone() throws CloneNotSupportedException {
    return copy();
  }

  public Message makeReply() {
    return new Message(src_addr);
  }

  public String toString() {
    StringBuilder ret = new StringBuilder(64);
    ret.append("[dst: ");
    if (dest_addr == null) ret.append("<null>");
    else ret.append(dest_addr);
    ret.append(", src: ");
    if (src_addr == null) ret.append("<null>");
    else ret.append(src_addr);

    int size;
    if ((size = getNumHeaders()) > 0) ret.append(" (").append(size).append(" headers)");

    ret.append(", size=");
    if (buf != null && length > 0) ret.append(length);
    else ret.append('0');
    ret.append(" bytes");
    if (flags > 0) ret.append(", flags=").append(flagsToString(flags));
    if (transient_flags > 0)
      ret.append(", transient_flags=" + transientFlagsToString(transient_flags));
    ret.append(']');
    return ret.toString();
  }

  /** Tries to read an object from the message's buffer and prints it */
  public String toStringAsObject() {

    if (buf == null) return null;
    try {
      Object obj = getObject();
      return obj != null ? obj.toString() : "";
    } catch (Exception e) { // it is not an object
      return "";
    }
  }

  public String printObjectHeaders() {
    return headers.printObjectHeaders();
  }

  /* ----------------------------------- Interface Streamable  ------------------------------- */

  /**
   * Streams all members (dest and src addresses, buffer and headers) to the output stream.
   *
   * @param out
   * @throws IOException
   */
  public void writeTo(DataOutputStream out) throws IOException {
    byte leading = 0;

    if (dest_addr != null) leading = Util.setFlag(leading, DEST_SET);

    if (src_addr != null) leading = Util.setFlag(leading, SRC_SET);

    if (buf != null) leading = Util.setFlag(leading, BUF_SET);

    // 1. write the leading byte first
    out.write(leading);

    // 2. the flags (e.g. OOB, LOW_PRIO)
    out.write(flags);

    // 3. dest_addr
    if (dest_addr != null) Util.writeAddress(dest_addr, out);

    // 4. src_addr
    if (src_addr != null) Util.writeAddress(src_addr, out);

    // 5. buf
    if (buf != null) {
      out.writeInt(length);
      out.write(buf, offset, length);
    }

    // 6. headers
    int size = headers.size();
    out.writeShort(size);
    final short[] ids = headers.getRawIDs();
    final Header[] hdrs = headers.getRawHeaders();
    for (int i = 0; i < ids.length; i++) {
      if (ids[i] > 0) {
        out.writeShort(ids[i]);
        writeHeader(hdrs[i], out);
      }
    }
  }

  /**
   * Writes the message to the output stream, but excludes the dest and src addresses unless the src
   * address given as argument is different from the message's src address
   *
   * @param src
   * @param out
   * @throws IOException
   */
  public void writeToNoAddrs(Address src, DataOutputStream out) throws IOException {
    byte leading = 0;

    boolean write_src_addr = src == null || src_addr != null && !src_addr.equals(src);

    if (write_src_addr) leading = Util.setFlag(leading, SRC_SET);

    if (buf != null) leading = Util.setFlag(leading, BUF_SET);

    // 1. write the leading byte first
    out.write(leading);

    // 2. the flags (e.g. OOB, LOW_PRIO)
    out.write(flags);

    // 4. src_addr
    if (write_src_addr) Util.writeAddress(src_addr, out);

    // 5. buf
    if (buf != null) {
      out.writeInt(length);
      out.write(buf, offset, length);
    }

    // 6. headers
    int size = headers.size();
    out.writeShort(size);
    final short[] ids = headers.getRawIDs();
    final Header[] hdrs = headers.getRawHeaders();
    for (int i = 0; i < ids.length; i++) {
      if (ids[i] > 0) {
        out.writeShort(ids[i]);
        writeHeader(hdrs[i], out);
      }
    }
  }

  public void readFrom(DataInputStream in)
      throws IOException, IllegalAccessException, InstantiationException {

    // 1. read the leading byte first
    byte leading = in.readByte();

    // 2. the flags
    flags = in.readByte();

    // 3. dest_addr
    if (Util.isFlagSet(leading, DEST_SET)) dest_addr = Util.readAddress(in);

    // 4. src_addr
    if (Util.isFlagSet(leading, SRC_SET)) src_addr = Util.readAddress(in);

    // 5. buf
    if (Util.isFlagSet(leading, BUF_SET)) {
      int len = in.readInt();
      buf = new byte[len];
      in.readFully(buf, 0, len);
      length = len;
    }

    // 6. headers
    int len = in.readShort();
    headers = createHeaders(len);

    short[] ids = headers.getRawIDs();
    Header[] hdrs = headers.getRawHeaders();

    for (int i = 0; i < len; i++) {
      short id = in.readShort();
      Header hdr = readHeader(in);
      ids[i] = id;
      hdrs[i] = hdr;
    }
  }

  /* --------------------------------- End of Interface Streamable ----------------------------- */

  /**
   * Returns the exact size of the marshalled message. Uses method size() of each header to compute
   * the size, so if a Header subclass doesn't implement size() we will use an approximation.
   * However, most relevant header subclasses have size() implemented correctly. (See
   * org.jgroups.tests.SizeTest).
   *
   * @return The number of bytes for the marshalled message
   */
  public long size() {
    long retval =
        Global.BYTE_SIZE // leading byte
            + Global.BYTE_SIZE; // flags
    if (dest_addr != null) retval += Util.size(dest_addr);
    if (src_addr != null) retval += Util.size(src_addr);
    if (buf != null)
      retval +=
          Global.INT_SIZE // length (integer)
              + length; // number of bytes in the buffer

    retval += Global.SHORT_SIZE; // number of headers
    retval += headers.marshalledSize();
    return retval;
  }

  /* ----------------------------------- Private methods ------------------------------- */

  public static String flagsToString(byte flags) {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    if (isFlagSet(flags, OOB)) {
      first = false;
      sb.append("OOB");
    }
    if (isFlagSet(flags, LOOPBACK)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("LOOPBACK");
    }
    if (isFlagSet(flags, DONT_BUNDLE)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("DONT_BUNDLE");
    }
    if (isFlagSet(flags, NO_FC)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_FC");
    }
    if (isFlagSet(flags, SCOPED)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("SCOPED");
    }
    if (isFlagSet(flags, NO_RELIABILITY)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_RELIABILITY");
    }
    if (isFlagSet(flags, NO_TOTAL_ORDER)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("NO_TOTAL_ORDER");
    }
    return sb.toString();
  }

  public static String transientFlagsToString(byte flags) {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    if (isFlagSet(flags, OOB_DELIVERED)) {
      if (!first) sb.append("|");
      else first = false;
      sb.append("OOB_DELIVERED");
    }
    return sb.toString();
  }

  private static void writeHeader(Header hdr, DataOutputStream out) throws IOException {
    short magic_number = ClassConfigurator.getMagicNumber(hdr.getClass());
    out.writeShort(magic_number);
    hdr.writeTo(out);
  }

  private static Header readHeader(DataInputStream in) throws IOException {
    try {
      short magic_number = in.readShort();
      Class clazz = ClassConfigurator.get(magic_number);
      if (clazz == null)
        throw new IllegalArgumentException(
            "magic number " + magic_number + " is not available in magic map");

      Header hdr = (Header) clazz.newInstance();
      hdr.readFrom(in);
      return hdr;
    } catch (Exception ex) {
      IOException io_ex = new IOException("failed reading header");
      io_ex.initCause(ex);
      throw io_ex;
    }
  }

  private static Headers createHeaders(int size) {
    return size > 0 ? new Headers(size) : new Headers(3);
  }

  private static Headers createHeaders(Headers m) {
    return new Headers(m);
  }

  /* ------------------------------- End of Private methods ---------------------------- */

}
  public class TCPConnection implements Connection {

    private final Socket sock; // socket to/from peer (result of srv_sock.accept() or new Socket())
    private final Lock send_lock = new ReentrantLock(); // serialize send()
    private final Log log = LogFactory.getLog(getClass());
    private final byte[] cookie = {'b', 'e', 'l', 'a'};
    private final DataOutputStream out;
    private final DataInputStream in;
    private final Address peer_addr; // address of the 'other end' of the connection
    private final int peer_addr_read_timeout =
        2000; // max time in milliseconds to block on reading peer address
    private long last_access =
        System.currentTimeMillis(); // last time a message was sent or received
    private Sender sender;
    private ConnectionPeerReceiver connectionPeerReceiver;
    private AtomicBoolean active = new AtomicBoolean(false);

    TCPConnection(Address peer_addr) throws Exception {
      if (peer_addr == null)
        throw new IllegalArgumentException("Invalid parameter peer_addr=" + peer_addr);
      SocketAddress destAddr =
          new InetSocketAddress(
              ((IpAddress) peer_addr).getIpAddress(), ((IpAddress) peer_addr).getPort());
      this.sock = new Socket();
      this.sock.bind(new InetSocketAddress(bind_addr, 0));
      Util.connect(this.sock, destAddr, sock_conn_timeout);
      setSocketParameters(sock);
      this.out = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream()));
      this.in = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
      sendLocalAddress(getLocalAddress());
      this.peer_addr = peer_addr;
    }

    TCPConnection(Socket s) throws Exception {
      if (s == null) throw new IllegalArgumentException("Invalid parameter s=" + s);
      setSocketParameters(s);
      this.out = new DataOutputStream(new BufferedOutputStream(s.getOutputStream()));
      this.in = new DataInputStream(new BufferedInputStream(s.getInputStream()));
      this.peer_addr = readPeerAddress(s);
      this.sock = s;
    }

    private Address getPeerAddress() {
      return peer_addr;
    }

    private void updateLastAccessed() {
      last_access = System.currentTimeMillis();
    }

    private void start(ThreadFactory f) {
      // only start once....
      if (active.compareAndSet(false, true)) {
        connectionPeerReceiver = new ConnectionPeerReceiver(f);
        connectionPeerReceiver.start();

        if (isSenderUsed()) {
          sender = new Sender(f, getSenderQueueSize());
          sender.start();
        }
      }
    }

    private boolean isSenderUsed() {
      return getSenderQueueSize() > 0 && use_send_queues;
    }

    private String getSockAddress() {
      StringBuilder sb = new StringBuilder();
      if (sock != null) {
        sb.append(sock.getLocalAddress().getHostAddress()).append(':').append(sock.getLocalPort());
        sb.append(" - ")
            .append(sock.getInetAddress().getHostAddress())
            .append(':')
            .append(sock.getPort());
      }
      return sb.toString();
    }

    /**
     * @param data Guaranteed to be non null
     * @param offset
     * @param length
     */
    private void send(byte[] data, int offset, int length) throws Exception {
      if (isSenderUsed()) {
        // we need to copy the byte[] buffer here because the original buffer might get
        // changed meanwhile
        byte[] tmp = new byte[length];
        System.arraycopy(data, offset, tmp, 0, length);
        sender.addToQueue(tmp);
      } else {
        _send(data, offset, length, true);
      }
    }

    /**
     * Sends data using the 'out' output stream of the socket
     *
     * @param data
     * @param offset
     * @param length
     * @param acquire_lock
     * @throws Exception
     */
    private void _send(byte[] data, int offset, int length, boolean acquire_lock) throws Exception {
      if (acquire_lock) send_lock.lock();

      try {
        doSend(data, offset, length);
        updateLastAccessed();
      } catch (InterruptedException iex) {
        Thread.currentThread().interrupt(); // set interrupt flag again
      } finally {
        if (acquire_lock) send_lock.unlock();
      }
    }

    private void doSend(byte[] data, int offset, int length) throws Exception {
      // we're using 'double-writes', sending the buffer to the destination in 2 pieces. this would
      // ensure that, if the peer closed the connection while we were idle, we would get an
      // exception.
      // this won't happen if we use a single write (see Stevens, ch. 5.13).

      out.writeInt(length); // write the length of the data buffer first
      Util.doubleWrite(data, offset, length, out);
      out.flush(); // may not be very efficient (but safe)
    }

    /**
     * Reads the peer's address. First a cookie has to be sent which has to match my own cookie,
     * otherwise the connection will be refused
     */
    private Address readPeerAddress(Socket client_sock) throws Exception {
      int timeout = client_sock.getSoTimeout();
      client_sock.setSoTimeout(peer_addr_read_timeout);

      try {
        // read the cookie first
        byte[] input_cookie = new byte[cookie.length];
        in.readFully(input_cookie, 0, input_cookie.length);
        if (!matchCookie(input_cookie))
          throw new SocketException(
              "ConnectionMap.Connection.readPeerAddress(): cookie read by "
                  + getLocalAddress()
                  + " does not match own cookie; terminating connection");
        // then read the version
        short version = in.readShort();

        if (!Version.isBinaryCompatible(version)) {
          if (log.isWarnEnabled())
            log.warn(
                new StringBuilder("packet from ")
                    .append(client_sock.getInetAddress())
                    .append(':')
                    .append(client_sock.getPort())
                    .append(" has different version (")
                    .append(Version.print(version))
                    .append(") from ours (")
                    .append(Version.printVersion())
                    .append("). This may cause problems")
                    .toString());
        }
        Address client_peer_addr = new IpAddress();
        client_peer_addr.readFrom(in);

        updateLastAccessed();
        return client_peer_addr;
      } finally {
        client_sock.setSoTimeout(timeout);
      }
    }

    /**
     * Send the cookie first, then the our port number. If the cookie doesn't match the receiver's
     * cookie, the receiver will reject the connection and close it.
     *
     * @throws IOException
     */
    private void sendLocalAddress(Address local_addr) throws IOException {
      // write the cookie
      out.write(cookie, 0, cookie.length);

      // write the version
      out.writeShort(Version.version);
      local_addr.writeTo(out);
      out.flush(); // needed ?
      updateLastAccessed();
    }

    private boolean matchCookie(byte[] input) {
      if (input == null || input.length < cookie.length) return false;
      for (int i = 0; i < cookie.length; i++) if (cookie[i] != input[i]) return false;
      return true;
    }

    private class ConnectionPeerReceiver implements Runnable {
      private Thread recv;
      private final AtomicBoolean receiving = new AtomicBoolean(false);

      public ConnectionPeerReceiver(ThreadFactory f) {
        recv = f.newThread(this, "Connection.Receiver [" + getSockAddress() + "]");
      }

      public void start() {
        if (receiving.compareAndSet(false, true)) {
          recv.start();
        }
      }

      public void stop() {
        if (receiving.compareAndSet(true, false)) {
          recv.interrupt();
        }
      }

      public boolean isRunning() {
        return receiving.get();
      }

      public boolean canRun() {
        return isRunning() && isConnected();
      }

      public void run() {
        try {
          while (!Thread.currentThread().isInterrupted() && canRun()) {
            try {
              int len = in.readInt();
              byte[] buf = new byte[len];
              in.readFully(buf, 0, len);
              updateLastAccessed();
              receiver.receive(peer_addr, buf, 0, len);
            } catch (OutOfMemoryError mem_ex) {
              break; // continue;
            } catch (IOException io_ex) {
              break;
            } catch (Throwable e) {
            }
          }
        } finally {
          Util.close(TCPConnection.this);
        }
      }
    }

    private class Sender implements Runnable {

      final BlockingQueue<byte[]> send_queue;
      final Thread runner;
      private final AtomicBoolean running = new AtomicBoolean(false);

      public Sender(ThreadFactory tf, int send_queue_size) {
        this.runner = tf.newThread(this, "Connection.Sender [" + getSockAddress() + "]");
        this.send_queue = new LinkedBlockingQueue<byte[]>(send_queue_size);
      }

      public void addToQueue(byte[] data) throws Exception {
        if (canRun()) send_queue.add(data);
      }

      public void start() {
        if (running.compareAndSet(false, true)) {
          runner.start();
        }
      }

      public void stop() {
        if (running.compareAndSet(true, false)) {
          runner.interrupt();
        }
      }

      public boolean isRunning() {
        return running.get();
      }

      public boolean canRun() {
        return isRunning() && isConnected();
      }

      public void run() {
        try {
          while (!Thread.currentThread().isInterrupted() && canRun()) {
            byte[] data = null;
            try {
              data = send_queue.take();
            } catch (InterruptedException e) {
              // Thread.currentThread().interrupt();
              break;
            }

            if (data != null) {
              try {
                _send(data, 0, data.length, false);
              } catch (Throwable ignored) {
              }
            }
          }
        } finally {
          Util.close(TCPConnection.this);
        }
        if (log.isTraceEnabled())
          log.trace("TCPConnection.Sender thread terminated at " + local_addr);
      }
    }

    public String toString() {
      StringBuilder ret = new StringBuilder();
      InetAddress local = null, remote = null;
      String local_str, remote_str;

      Socket tmp_sock = sock;
      if (tmp_sock == null) ret.append("<null socket>");
      else {
        // since the sock variable gets set to null we want to make
        // make sure we make it through here without a nullpointer exception
        local = tmp_sock.getLocalAddress();
        remote = tmp_sock.getInetAddress();
        local_str = local != null ? Util.shortName(local) : "<null>";
        remote_str = remote != null ? Util.shortName(remote) : "<null>";
        ret.append(
            '<'
                + local_str
                + ':'
                + tmp_sock.getLocalPort()
                + " --> "
                + remote_str
                + ':'
                + tmp_sock.getPort()
                + "> ("
                + ((System.currentTimeMillis() - last_access) / 1000)
                + " secs old)");
      }
      tmp_sock = null;

      return ret.toString();
    }

    public boolean isExpired(long now) {
      return getConnectionExpiryTimeout() > 0 && now - last_access >= getConnectionExpiryTimeout();
    }

    public boolean isConnected() {
      return !sock.isClosed() && sock.isConnected();
    }

    public boolean isOpen() {
      return isConnected()
          && (!isSenderUsed() || sender.isRunning())
          && (connectionPeerReceiver != null && connectionPeerReceiver.isRunning());
    }

    public void close() throws IOException {
      // can close even if start was never called...
      send_lock.lock();
      try {
        connectionPeerReceiver.stop();
        if (isSenderUsed()) {
          sender.stop();
        }
        Util.close(sock);
        Util.close(out);
        Util.close(in);
      } finally {
        send_lock.unlock();
      }
      mapper.notifyConnectionClosed(peer_addr);
    }
  }
Beispiel #18
0
/**
 * Implementation of {@link org.jgroups.util.TimeScheduler}. Uses a thread pool and a single thread
 * which waits for the next task to be executed. When ready, it passes the task to the associated
 * pool to get executed. When multiple tasks are scheduled to get executed at the same time, they're
 * collected in a queue associated with the task execution time, and are executed together.
 *
 * @author Bela Ban
 */
public class TimeScheduler2 implements TimeScheduler, Runnable {
  private final ThreadManagerThreadPoolExecutor pool;

  private final ConcurrentSkipListMap<Long, Entry> tasks = new ConcurrentSkipListMap<Long, Entry>();

  private Thread runner = null;

  private final Lock lock = new ReentrantLock();

  private final Condition tasks_available = lock.newCondition();

  @GuardedBy("lock")
  private long next_execution_time = 0;

  /** Needed to signal going from 0 tasks to non-zero (we cannot use tasks.isEmpty() here ...) */
  protected final AtomicBoolean no_tasks = new AtomicBoolean(true);

  protected volatile boolean running = false;

  protected static final Log log = LogFactory.getLog(TimeScheduler2.class);

  protected ThreadDecorator threadDecorator = null;

  protected ThreadFactory timer_thread_factory = null;

  protected static final long SLEEP_TIME = 10000;

  /** Create a scheduler that executes tasks in dynamically adjustable intervals */
  public TimeScheduler2() {
    pool =
        new ThreadManagerThreadPoolExecutor(
            4,
            10,
            5000,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(5000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    init();
  }

  public TimeScheduler2(
      ThreadFactory factory,
      int min_threads,
      int max_threads,
      long keep_alive_time,
      int max_queue_size) {
    timer_thread_factory = factory;
    pool =
        new ThreadManagerThreadPoolExecutor(
            min_threads,
            max_threads,
            keep_alive_time,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(max_queue_size),
            factory,
            new ThreadPoolExecutor.CallerRunsPolicy());
    init();
  }

  public TimeScheduler2(int corePoolSize) {
    pool =
        new ThreadManagerThreadPoolExecutor(
            corePoolSize,
            corePoolSize * 2,
            5000,
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(5000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    init();
  }

  public ThreadDecorator getThreadDecorator() {
    return threadDecorator;
  }

  public void setThreadDecorator(ThreadDecorator threadDecorator) {
    this.threadDecorator = threadDecorator;
    pool.setThreadDecorator(threadDecorator);
  }

  public void setThreadFactory(ThreadFactory factory) {
    pool.setThreadFactory(factory);
  }

  public int getMinThreads() {
    return pool.getCorePoolSize();
  }

  public void setMinThreads(int size) {
    pool.setCorePoolSize(size);
  }

  public int getMaxThreads() {
    return pool.getMaximumPoolSize();
  }

  public void setMaxThreads(int size) {
    pool.setMaximumPoolSize(size);
  }

  public long getKeepAliveTime() {
    return pool.getKeepAliveTime(TimeUnit.MILLISECONDS);
  }

  public void setKeepAliveTime(long time) {
    pool.setKeepAliveTime(time, TimeUnit.MILLISECONDS);
  }

  public int getCurrentThreads() {
    return pool.getPoolSize();
  }

  public int getQueueSize() {
    return pool.getQueue().size();
  }

  public String dumpTimerTasks() {
    StringBuilder sb = new StringBuilder();
    for (Entry entry : tasks.values()) {
      sb.append(entry.dump()).append("\n");
    }
    return sb.toString();
  }

  public void execute(Runnable task) {
    schedule(task, 0, TimeUnit.MILLISECONDS);
  }

  public Future<?> schedule(Runnable work, long delay, TimeUnit unit) {
    if (work == null) return null;

    Future<?> retval = null;

    long key =
        System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(delay, unit); // execution time
    Entry task = new Entry(work);
    while (!isShutdown()) {
      Entry existing = tasks.putIfAbsent(key, task);
      if (existing == null) {
        retval = task.getFuture();
        break; // break out of the while loop
      }
      if ((retval = existing.add(work)) != null) break;
    }

    if (key < next_execution_time || no_tasks.compareAndSet(true, false)) {
      if (key >= next_execution_time) key = 0L;
      taskReady(key);
    }

    return retval;
  }

  public Future<?> scheduleWithFixedDelay(
      Runnable task, long initial_delay, long delay, TimeUnit unit) {
    if (task == null) throw new NullPointerException();
    if (isShutdown()) return null;
    RecurringTask wrapper = new FixedIntervalTask(task, delay);
    wrapper.doSchedule(initial_delay);
    return wrapper;
  }

  public Future<?> scheduleAtFixedRate(
      Runnable task, long initial_delay, long delay, TimeUnit unit) {
    if (task == null) throw new NullPointerException();
    if (isShutdown()) return null;
    RecurringTask wrapper = new FixedRateTask(task, delay);
    wrapper.doSchedule(initial_delay);
    return wrapper;
  }

  /**
   * Schedule a task for execution at varying intervals. After execution, the task will get
   * rescheduled after {@link org.jgroups.util.TimeScheduler2.Task#nextInterval()} milliseconds. The
   * task is neve done until nextInterval() return a value <= 0 or the task is cancelled.
   *
   * @param task the task to execute Task is rescheduled relative to the last time it
   *     <i>actually</i> started execution
   *     <p><tt>false</tt>:<br>
   *     Task is scheduled relative to its <i>last</i> execution schedule. This has the effect that
   *     the time between two consecutive executions of the task remains the same.
   *     <p>Note that relative is always true; we always schedule the next execution relative to the
   *     last *actual*
   */
  public Future<?> scheduleWithDynamicInterval(Task task) {
    if (task == null) throw new NullPointerException();
    if (isShutdown()) return null;
    RecurringTask task_wrapper = new DynamicIntervalTask(task);
    task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor
    return task_wrapper;
  }

  /**
   * Returns the number of tasks currently in the timer
   *
   * @return The number of tasks currently in the timer
   */
  public int size() {
    int retval = 0;
    Collection<Entry> values = tasks.values();
    for (Entry entry : values) retval += entry.size();
    return retval;
  }

  public String toString() {
    return getClass().getSimpleName();
  }

  /**
   * Stops the timer, cancelling all tasks
   *
   * @throws InterruptedException if interrupted while waiting for thread to return
   */
  public void stop() {
    stopRunner();

    java.util.List<Runnable> remaining_tasks = pool.shutdownNow();
    for (Runnable task : remaining_tasks) {
      if (task instanceof Future) {
        Future future = (Future) task;
        future.cancel(true);
      }
    }
    pool.getQueue().clear();
    try {
      pool.awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
    }

    for (Entry entry : tasks.values()) entry.cancel();
    tasks.clear();
  }

  public boolean isShutdown() {
    return pool.isShutdown();
  }

  public void run() {
    while (running) {
      try {
        _run();
      } catch (Throwable t) {
        log.error("failed executing tasks(s)", t);
      }
    }
  }

  protected void _run() {
    ConcurrentNavigableMap<Long, Entry>
        head_map; // head_map = entries which are <= curr time (ready to be executed)
    if (!(head_map = tasks.headMap(System.currentTimeMillis(), true)).isEmpty()) {
      final List<Long> keys = new LinkedList<Long>();
      for (Map.Entry<Long, Entry> entry : head_map.entrySet()) {
        final Long key = entry.getKey();
        final Entry val = entry.getValue();
        pool.execute(
            new Runnable() {
              public void run() {
                val.execute();
              }
            });
        keys.add(key);
      }
      tasks.keySet().removeAll(keys);
    }

    if (tasks.isEmpty()) {
      no_tasks.compareAndSet(false, true);
      waitFor(); // sleeps until time elapses, or a task with a lower execution time is added
    } else
      waitUntilNextExecution(); // waits until next execution, or a task with a lower execution time
    // is added
  }

  protected void init() {
    if (threadDecorator != null) pool.setThreadDecorator(threadDecorator);
    // pool.allowCoreThreadTimeOut(true);
    startRunner();
  }

  /** Sleeps until the next task in line is ready to be executed */
  protected void waitUntilNextExecution() {
    lock.lock();
    try {
      if (!running) return;
      next_execution_time = tasks.firstKey();
      long sleep_time = next_execution_time - System.currentTimeMillis();
      tasks_available.await(sleep_time, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
    } finally {
      lock.unlock();
    }
  }

  protected void waitFor() {
    lock.lock();
    try {
      if (!running) return;
      tasks_available.await(SLEEP_TIME, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
    } finally {
      lock.unlock();
    }
  }

  /** Signals that a task with a lower execution time than next_execution_time is ready */
  protected void taskReady(long trigger_time) {
    lock.lock();
    try {
      if (trigger_time > 0) next_execution_time = trigger_time;
      tasks_available.signal();
    } finally {
      lock.unlock();
    }
  }

  protected void startRunner() {
    running = true;
    runner =
        timer_thread_factory != null
            ? timer_thread_factory.newThread(this, "Timer runner")
            : new Thread(this, "Timer runner");
    runner.start();
  }

  protected void stopRunner() {
    lock.lock();
    try {
      running = false;
      tasks_available.signal();
    } finally {
      lock.unlock();
    }
  }

  private static class Entry {
    private final MyTask task; // the task (wrapper) to execute
    private MyTask last; // points to the last task
    private final Lock lock = new ReentrantLock();

    @GuardedBy("lock")
    private boolean completed = false; // set to true when the task has been executed

    public Entry(Runnable task) {
      last = this.task = new MyTask(task);
    }

    Future<?> getFuture() {
      return task;
    }

    Future<?> add(Runnable task) {
      lock.lock();
      try {
        if (completed) return null;
        MyTask retval = new MyTask(task);
        last.next = retval;
        last = last.next;
        return retval;
      } finally {
        lock.unlock();
      }
    }

    void execute() {
      lock.lock();
      try {
        if (completed) return;
        completed = true;

        for (MyTask tmp = task; tmp != null; tmp = tmp.next) {
          if (!(tmp.isCancelled() || tmp.isDone())) {
            try {
              tmp.run();
            } catch (Throwable t) {
              log.error("task execution failed", t);
            } finally {
              tmp.done = true;
            }
          }
        }
      } finally {
        lock.unlock();
      }
    }

    void cancel() {
      lock.lock();
      try {
        if (completed) return;
        for (MyTask tmp = task; tmp != null; tmp = tmp.next) tmp.cancel(true);
      } finally {
        lock.unlock();
      }
    }

    int size() {
      int retval = 1;
      for (MyTask tmp = task.next; tmp != null; tmp = tmp.next) retval++;
      return retval;
    }

    public String toString() {
      return size() + " tasks";
    }

    public String dump() {
      StringBuilder sb = new StringBuilder();
      boolean first = true;
      for (MyTask tmp = task; tmp != null; tmp = tmp.next) {
        if (!first) sb.append(", ");
        else first = false;
        sb.append(tmp);
      }
      return sb.toString();
    }
  }

  /** Simple task wrapper, always executed by at most 1 thread. */
  protected static class MyTask implements Future, Runnable {
    protected final Runnable task;
    protected volatile boolean cancelled = false;
    protected volatile boolean done = false;
    protected MyTask next;

    public MyTask(Runnable task) {
      this.task = task;
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
      boolean retval = !isDone();
      cancelled = true;
      return retval;
    }

    public boolean isCancelled() {
      return cancelled;
    }

    public boolean isDone() {
      return done || cancelled;
    }

    public Object get() throws InterruptedException, ExecutionException {
      return null;
    }

    public Object get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
      return null;
    }

    public void run() {
      if (isDone()) return;
      try {
        task.run();
      } catch (Throwable t) {
        log.error("failed executing task " + task, t);
      } finally {
        done = true;
      }
    }

    public String toString() {
      return task.toString();
    }
  }

  /**
   * Task which executes multiple times. An instance of this class wraps the real task and
   * intercepts run(): when called, it forwards the call to task.run() and then schedules another
   * execution (until cancelled). The {@link #nextInterval()} method determines the time to wait
   * until the next execution.
   *
   * @param <V>
   */
  private abstract class RecurringTask<V> implements Runnable, Future<V> {
    protected final Runnable task;
    protected volatile Future<?> future; // cannot be null !
    protected volatile boolean cancelled = false;

    public RecurringTask(Runnable task) {
      this.task = task;
    }

    /**
     * The time to wait until the next execution
     *
     * @return Number of milliseconds to wait until the next execution is scheduled
     */
    protected abstract long nextInterval();

    protected boolean rescheduleOnZeroDelay() {
      return false;
    }

    public void doSchedule() {
      long next_interval = nextInterval();
      if (next_interval <= 0 && !rescheduleOnZeroDelay()) {
        if (log.isTraceEnabled())
          log.trace("task will not get rescheduled as interval is " + next_interval);
        return;
      }

      future = schedule(this, next_interval, TimeUnit.MILLISECONDS);
      if (cancelled) future.cancel(true);
    }

    public void doSchedule(long next_interval) {
      future = schedule(this, next_interval, TimeUnit.MILLISECONDS);
      if (cancelled) future.cancel(true);
    }

    public void run() {
      if (cancelled) {
        if (future != null) future.cancel(true);
        return;
      }

      try {
        task.run();
      } catch (Throwable t) {
        log.error("failed running task " + task, t);
      }
      if (!cancelled) doSchedule();
    }

    public boolean cancel(boolean mayInterruptIfRunning) {
      boolean retval = !isDone();
      cancelled = true;
      if (future != null) future.cancel(mayInterruptIfRunning);
      return retval;
    }

    public boolean isCancelled() {
      return cancelled;
    }

    public boolean isDone() {
      return cancelled || (future == null || future.isDone());
    }

    public V get() throws InterruptedException, ExecutionException {
      return null;
    }

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
      return null;
    }

    public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(getClass().getSimpleName() + ": task=" + task + ", cancelled=" + isCancelled());
      return sb.toString();
    }
  }

  private class FixedIntervalTask<V> extends RecurringTask<V> {
    final long interval;

    public FixedIntervalTask(Runnable task, long interval) {
      super(task);
      this.interval = interval;
    }

    protected long nextInterval() {
      return interval;
    }
  }

  private class FixedRateTask<V> extends RecurringTask<V> {
    final long interval;
    final long first_execution;
    int num_executions = 0;

    public FixedRateTask(Runnable task, long interval) {
      super(task);
      this.interval = interval;
      this.first_execution = System.currentTimeMillis();
    }

    protected long nextInterval() {
      long target_time = first_execution + (interval * ++num_executions);
      return target_time - System.currentTimeMillis();
    }

    protected boolean rescheduleOnZeroDelay() {
      return true;
    }
  }

  private class DynamicIntervalTask<V> extends RecurringTask<V> {

    public DynamicIntervalTask(Task task) {
      super(task);
    }

    protected long nextInterval() {
      if (task instanceof Task) return ((Task) task).nextInterval();
      return 0;
    }
  }
}