/**
  * {@inheritDoc}
  *
  * @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#onSynchronousResponse(int,
  *     java.lang.Object)
  */
 @Override
 public void onSynchronousResponse(int requestId, Object value) {
   SimpleLogger.debug("Response for req [", requestId, "]:[", value, "]");
   synchResultMap.putIfAbsent(requestId, value);
   CountDownLatch latch = synchTimeoutMap.remove(requestId);
   if (latch != null) {
     latch.countDown();
     SimpleLogger.debug("Counted Down Latch for req [", requestId, "]:[", value, "]");
   }
 }
 /**
  * {@inheritDoc}
  *
  * @see
  *     org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#waitForSynchronousResponse(int,
  *     long)
  */
 @Override
 public void waitForSynchronousResponse(int requestId, long timeout) {
   CountDownLatch latch = new CountDownLatch(1);
   synchTimeoutMap.put(requestId, latch);
   try {
     SimpleLogger.debug("Waiting for response to [", requestId, "]....");
     boolean timedout = latch.await(timeout, TimeUnit.MILLISECONDS);
     SimpleLogger.debug("Result of Waiting for response to [", requestId, "]:", timedout);
   } catch (InterruptedException iex) {
     synchResultMap.putIfAbsent(
         requestId,
         new IOException(
             "Thread was interrupted while waiting on Operation completion", new Throwable()));
   }
 }
  /**
   * {@inheritDoc}
   *
   * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method,
   *     java.lang.Object[])
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (MBeanServerConnection.class != method.getDeclaringClass()) {
      return method.invoke(Modifier.isStatic(method.getModifiers()) ? null : this, args);
    }
    if (channel.getPipeline().get(getClass().getSimpleName()) == null) {
      throw new IOException("This MBeanServerConnection has been closed", new Throwable());
    }
    // SimpleLogger.debug("MBeanServerConnection [", method.getName(), "] Payload Size [",
    // sargs.length+6+4, "]");
    final int reqId = requestId.incrementAndGet();
    if ("addNotificationListener".equals(method.getName())
        && !method.getParameterTypes()[1].equals(ObjectName.class)) {
      NotificationListener listener = (NotificationListener) args[1];
      args[1] = reqId;
      addRegisteredListener(reqId, listener);
    } else if ("removeNotificationListener".equals(method.getName())
        && !method.getParameterTypes()[1].equals(ObjectName.class)) {
      removeRegisteredListener((NotificationListener) args[1]);
      args = new Object[0];
    }
    byte[] sargs = getOutput(args);
    ChannelBuffer cb =
        ChannelBuffers.directBuffer(1 + domainInfoData.length + 4 + 1 + 4 + sargs.length);
    cb.writeByte(OpCode.JMX_REQUEST.op()); // 1
    cb.writeBytes(domainInfoData); // domain data
    cb.writeInt(reqId); // 4
    cb.writeByte(methodToKey.get(method)); // 1
    cb.writeInt(sargs.length); // 4
    cb.writeBytes(sargs); // sargs.length

    if (listener == null) {
      synchTimeoutMap.addListener(
          new TimeoutListener<Integer, CountDownLatch>() {
            @Override
            public void onTimeout(Integer key, CountDownLatch value) {
              if (reqId == key) {
                synchTimeoutMap.remove(key);
                synchTimeoutMap.removeListener(this);
                onSynchronousResponse(
                    reqId,
                    new IOException(
                        "Operation timed out after [" + timeout + "] ms.", new Throwable()));
              }
            }
          });
    } else {
      asynchTimeoutMap.put(reqId, listener, timeout);
      asynchTimeoutMap.addListener(
          new TimeoutListener<Integer, AsynchJMXResponseListener>() {
            @Override
            public void onTimeout(Integer key, AsynchJMXResponseListener value) {
              if (reqId == key) {
                asynchTimeoutMap.remove(key);
                listener.onTimeout(reqId, timeout);
                asynchTimeoutMap.removeListener(this);
              }
            }
          });
    }

    channel
        .write(cb, remoteAddress)
        .addListener(
            new ChannelFutureListener() {
              @Override
              public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                  SimpleLogger.debug("Sent JMX Request to [", remoteAddress, "]");
                } else {
                  SimpleLogger.error(
                      "Failed to send JMX Request to [", remoteAddress, "]", future.getCause());
                }
              }
            });
    if (listener == null) {
      waitForSynchronousResponse(reqId, timeout);
      Object result = synchResultMap.get(reqId);
      if (result != null && result instanceof Throwable) {
        throw (Throwable) result;
      }
      return result;
    }
    return null;
  }