void addListener(
      Object listener, PropertyKey propertyKey, ChangeType changeType, EventType[] eventType) {
    checkConnected();

    PropertyType type = propertyKey.getType();

    synchronized (this) {
      for (CallbackHandler handler : _handlers) {
        // compare property-key path and listener reference
        if (handler.getPath().equals(propertyKey.getPath())
            && handler.getListener().equals(listener)) {
          LOG.info(
              "Listener: "
                  + listener
                  + " on path: "
                  + propertyKey.getPath()
                  + " already exists. skip add");

          return;
        }
      }

      CallbackHandler newHandler =
          new CallbackHandler(this, _zkclient, propertyKey, listener, eventType, changeType);

      _handlers.add(newHandler);
      LOG.info(
          "Added listener: "
              + listener
              + " for type: "
              + type
              + " to path: "
              + newHandler.getPath());
    }
  }
  @Override
  public boolean removeListener(PropertyKey key, Object listener) {
    LOG.info(
        "Removing listener: "
            + listener
            + " on path: "
            + key.getPath()
            + " from cluster: "
            + _clusterName
            + " by instance: "
            + _instanceName);

    synchronized (this) {
      List<CallbackHandler> toRemove = new ArrayList<CallbackHandler>();
      for (CallbackHandler handler : _handlers) {
        // compare property-key path and listener reference
        if (handler.getPath().equals(key.getPath()) && handler.getListener().equals(listener)) {
          toRemove.add(handler);
        }
      }

      _handlers.removeAll(toRemove);

      // handler.reset() may modify the handlers list, so do it outside the iteration
      for (CallbackHandler handler : toRemove) {
        handler.reset();
      }
    }

    return true;
  }