/**
   * This method deals with messages arrived as regular message but its contents are compressed.
   * Such messages come from message senders who are configured to compress large messages, and if
   * some of the messages are compressed below the min-large-message-size limit, they are sent as
   * regular messages. <br>
   * However when decompressing the message, we are not sure how large the message could be.. for
   * that reason we fake a large message controller that will deal with the message as it was a
   * large message <br>
   * Say that you sent a 1G message full of spaces. That could be just bellow 100K compressed but
   * you wouldn't have enough memory to decompress it
   */
  private void handleCompressedMessage(final ClientMessageInternal clMessage) throws Exception {
    ClientLargeMessageImpl largeMessage = new ClientLargeMessageImpl();
    largeMessage.retrieveExistingData(clMessage);

    File largeMessageCache = null;

    if (session.isCacheLargeMessageClient()) {
      largeMessageCache =
          File.createTempFile("tmp-large-message-" + largeMessage.getMessageID() + "-", ".tmp");
      largeMessageCache.deleteOnExit();
    }

    ClientSessionFactory sf = session.getSessionFactory();
    ServerLocator locator = sf.getServerLocator();
    long callTimeout = locator.getCallTimeout();

    currentLargeMessageController =
        new LargeMessageControllerImpl(
            this, largeMessage.getLargeMessageSize(), callTimeout, largeMessageCache);
    currentLargeMessageController.setLocal(true);

    // sets the packet
    ActiveMQBuffer qbuff = clMessage.getBodyBuffer();
    int bytesToRead = qbuff.writerIndex() - qbuff.readerIndex();
    final byte[] body = qbuff.readBytes(bytesToRead).toByteBuffer().array();

    largeMessage.setLargeMessageController(
        new CompressedLargeMessageControllerImpl(currentLargeMessageController));
    currentLargeMessageController.addPacket(body, body.length, false);

    handleRegularMessage(largeMessage);
  }
  public void write(
      final ActiveMQBuffer buffer,
      final boolean flush,
      final boolean batch,
      final ChannelFutureListener futureListener) {
    final ActiveMQBuffer copied = buffer.copy(0, buffer.capacity());

    copied.setIndex(buffer.readerIndex(), buffer.writerIndex());

    try {
      executor.execute(
          new Runnable() {
            public void run() {
              try {
                if (!closed) {
                  copied.readInt(); // read and discard
                  if (isTrace) {
                    ActiveMQServerLogger.LOGGER.trace(
                        InVMConnection.this + "::Sending inVM packet");
                  }
                  handler.bufferReceived(id, copied);
                  if (futureListener != null) {
                    // TODO BEFORE MERGE: (is null a good option here?)
                    futureListener.operationComplete(null);
                  }
                }
              } catch (Exception e) {
                final String msg = "Failed to write to handler on connector " + this;
                ActiveMQServerLogger.LOGGER.errorWritingToInvmConnector(e, this);
                throw new IllegalStateException(msg, e);
              } finally {
                if (isTrace) {
                  ActiveMQServerLogger.LOGGER.trace(InVMConnection.this + "::packet sent done");
                }
              }
            }
          });

      if (flush && flushEnabled) {
        final CountDownLatch latch = new CountDownLatch(1);
        executor.execute(
            new Runnable() {
              public void run() {
                latch.countDown();
              }
            });

        try {
          if (!latch.await(10, TimeUnit.SECONDS)) {
            ActiveMQServerLogger.LOGGER.timedOutFlushingInvmChannel();
          }
        } catch (InterruptedException e) {
          throw new ActiveMQInterruptedException(e);
        }
      }
    } catch (RejectedExecutionException e) {
      // Ignore - this can happen if server/client is shutdown and another request comes in
    }
  }
  public ActiveMQBuffer encode(final RemotingConnection connection) {
    ActiveMQBuffer buffer = connection.createTransportBuffer(PacketImpl.INITIAL_PACKET_SIZE);

    // The standard header fields

    buffer.writeInt(0); // The length gets filled in at the end
    buffer.writeByte(type);
    buffer.writeLong(channelID);

    encodeRest(buffer);

    size = buffer.writerIndex();

    // The length doesn't include the actual length byte
    int len = size - DataConstants.SIZE_INT;

    buffer.setInt(0, len);

    return buffer;
  }
  public void write(
      ActiveMQBuffer buffer,
      final boolean flush,
      final boolean batched,
      final ChannelFutureListener futureListener) {

    try {
      writeLock.acquire();

      try {
        if (batchBuffer == null && batchingEnabled && batched && !flush) {
          // Lazily create batch buffer

          batchBuffer = ActiveMQBuffers.dynamicBuffer(BATCHING_BUFFER_SIZE);
        }

        if (batchBuffer != null) {
          batchBuffer.writeBytes(buffer, 0, buffer.writerIndex());

          if (batchBuffer.writerIndex() >= BATCHING_BUFFER_SIZE || !batched || flush) {
            // If the batch buffer is full or it's flush param or not batched then flush the buffer

            buffer = batchBuffer;
          } else {
            return;
          }

          if (!batched || flush) {
            batchBuffer = null;
          } else {
            // Create a new buffer

            batchBuffer = ActiveMQBuffers.dynamicBuffer(BATCHING_BUFFER_SIZE);
          }
        }

        // depending on if we need to flush or not we can use a voidPromise or
        // use a normal promise
        final ByteBuf buf = buffer.byteBuf();
        final ChannelPromise promise;
        if (flush || futureListener != null) {
          promise = channel.newPromise();
        } else {
          promise = channel.voidPromise();
        }

        EventLoop eventLoop = channel.eventLoop();
        boolean inEventLoop = eventLoop.inEventLoop();
        if (!inEventLoop) {
          if (futureListener != null) {
            channel.writeAndFlush(buf, promise).addListener(futureListener);
          } else {
            channel.writeAndFlush(buf, promise);
          }
        } else {
          // create a task which will be picked up by the eventloop and trigger the write.
          // This is mainly needed as this method is triggered by different threads for the same
          // channel.
          // if we not do this we may produce out of order writes.
          final Runnable task =
              new Runnable() {
                @Override
                public void run() {
                  if (futureListener != null) {
                    channel.writeAndFlush(buf, promise).addListener(futureListener);
                  } else {
                    channel.writeAndFlush(buf, promise);
                  }
                }
              };
          // execute the task on the eventloop
          eventLoop.execute(task);
        }

        // only try to wait if not in the eventloop otherwise we will produce a deadlock
        if (flush && !inEventLoop) {
          while (true) {
            try {
              boolean ok = promise.await(10000);

              if (!ok) {
                ActiveMQClientLogger.LOGGER.timeoutFlushingPacket();
              }

              break;
            } catch (InterruptedException e) {
              throw new ActiveMQInterruptedException(e);
            }
          }
        }
      } finally {
        writeLock.release();
      }
    } catch (InterruptedException e) {
      throw new ActiveMQInterruptedException(e);
    }
  }