public synchronized void freeze(final CoreRemotingConnection connectionToKeepOpen) {
    if (!started) return;
    failureCheckAndFlushThread.close(false);

    for (Acceptor acceptor : acceptors) {
      try {
        acceptor.pause();
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorStoppingAcceptor();
      }
    }
    HashMap<Object, ConnectionEntry> connectionEntries =
        new HashMap<Object, ConnectionEntry>(connections);

    // Now we ensure that no connections will process any more packets after this method is
    // complete then send a disconnect packet
    for (Entry<Object, ConnectionEntry> entry : connectionEntries.entrySet()) {
      RemotingConnection conn = entry.getValue().connection;

      if (conn.equals(connectionToKeepOpen)) continue;

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace("Sending connection.disconnection packet to " + conn);
      }

      if (!conn.isClient()) {
        conn.disconnect(false);
        connections.remove(entry.getKey());
      }
    }
  }
Exemple #2
0
  public void route(final ServerMessage message, final RoutingContext context) throws Exception {
    // We must make a copy of the message, otherwise things like returning credits to the page won't
    // work
    // properly on ack, since the original address will be overwritten

    // TODO we can optimise this so it doesn't copy if it's not routed anywhere else

    if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
      HornetQServerLogger.LOGGER.trace("Diverting message " + message + " into " + this);
    }

    long id = storageManager.generateUniqueID();

    ServerMessage copy = message.copy(id);
    copy.finishCopy();

    // This will set the original MessageId, and the original address
    copy.setOriginalHeaders(message, null, false);

    copy.setAddress(forwardAddress);

    if (transformer != null) {
      copy = transformer.transform(copy);
    }

    postOffice.route(copy, context.getTransaction(), false);
  }
  public void stop(final boolean criticalError) throws Exception {
    if (!started) {
      return;
    }

    failureCheckAndFlushThread.close(criticalError);

    // We need to stop them accepting first so no new connections are accepted after we send the
    // disconnect message
    for (Acceptor acceptor : acceptors) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug("Pausing acceptor " + acceptor);
      }
      acceptor.pause();
    }

    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug("Sending disconnect on live connections");
    }

    HashSet<ConnectionEntry> connectionEntries = new HashSet<ConnectionEntry>(connections.values());

    // Now we ensure that no connections will process any more packets after this method is complete
    // then send a disconnect packet
    for (ConnectionEntry entry : connectionEntries) {
      RemotingConnection conn = entry.connection;

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace("Sending connection.disconnection packet to " + conn);
      }

      conn.disconnect(criticalError);
    }

    for (Acceptor acceptor : acceptors) {
      acceptor.stop();
    }

    acceptors.clear();

    connections.clear();

    if (managementService != null) {
      managementService.unregisterAcceptors();
    }

    threadPool.shutdown();

    if (!criticalError) {
      boolean ok = threadPool.awaitTermination(10000, TimeUnit.MILLISECONDS);

      if (!ok) {
        HornetQServerLogger.LOGGER.timeoutRemotingThreadPool();
      }
    }

    started = false;
  }
    private synchronized void doConsumerCreated(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            ClusterConnectionImpl.this + " Consumer created " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_DISTANCE)) {
        throw new IllegalStateException("distance is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      Integer distance = message.getIntProperty(ManagementHelper.HDR_DISTANCE);

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      message.putIntProperty(ManagementHelper.HDR_DISTANCE, distance + 1);

      SimpleString filterString =
          message.getSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING);

      RemoteQueueBinding binding = bindings.get(clusterName);

      if (binding == null) {
        throw new IllegalStateException(
            "Cannot find binding for " + clusterName + " on " + ClusterConnectionImpl.this);
      }

      binding.addConsumer(filterString);

      // Need to propagate the consumer add
      TypedProperties props = new TypedProperties();

      props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS, binding.getAddress());

      props.putSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME, clusterName);

      props.putSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME, binding.getRoutingName());

      props.putIntProperty(ManagementHelper.HDR_DISTANCE, distance + 1);

      Queue theQueue = (Queue) binding.getBindable();

      props.putIntProperty(ManagementHelper.HDR_CONSUMER_COUNT, theQueue.getConsumerCount());

      if (filterString != null) {
        props.putSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING, filterString);
      }

      Notification notification = new Notification(null, CONSUMER_CREATED, props);

      managementService.sendNotification(notification);
    }
    public void bufferReceived(final Object connectionID, final HornetQBuffer buffer) {
      ConnectionEntry conn = connections.get(connectionID);

      if (conn != null) {
        conn.connection.bufferReceived(connectionID, buffer);
      } else {
        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              "ConnectionID = " + connectionID + " was already closed, so ignoring packet");
        }
      }
    }
    private void doBindingRemoved(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            ClusterConnectionImpl.this + " Removing binding " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      removeBinding(clusterName);
    }
  /**
   * @param ref
   * @param message
   */
  private void deliverStandardMessage(final MessageReference ref, final ServerMessage message) {
    int packetSize = callback.sendMessage(message, id, ref.getDeliveryCount());

    if (availableCredits != null) {
      availableCredits.addAndGet(-packetSize);

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            this
                + "::FlowControl::delivery standard taking "
                + packetSize
                + " from credits, available now is "
                + availableCredits);
      }
    }
  }
  public void sendLarge(final MessageInternal message) throws Exception {
    // need to create the LargeMessage before continue
    long id = storageManager.generateUniqueID();

    LargeServerMessage largeMsg = storageManager.createLargeMessage(id, message);

    if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
      HornetQServerLogger.LOGGER.trace("sendLarge::" + largeMsg);
    }

    if (currentLargeMessage != null) {
      HornetQServerLogger.LOGGER.replacingIncompleteLargeMessage(
          currentLargeMessage.getMessageID());
    }

    currentLargeMessage = largeMsg;
  }
  public void receiveCredits(final int credits) throws Exception {
    if (credits == -1) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this + ":: FlowControl::Received disable flow control message");
      }
      // No flow control
      availableCredits = null;

      // There may be messages already in the queue
      promptDelivery();
    } else if (credits == 0) {
      // reset, used on slow consumers
      HornetQServerLogger.LOGGER.debug(
          this + ":: FlowControl::Received reset flow control message");
      availableCredits.set(0);
    } else {
      int previous = availableCredits.getAndAdd(credits);

      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this
                + "::FlowControl::Received "
                + credits
                + " credits, previous value = "
                + previous
                + " currentValue = "
                + availableCredits.get());
      }

      if (previous <= 0 && previous + credits > 0) {
        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              this + "::calling promptDelivery from receiving credits");
        }
        promptDelivery();
      }
    }
  }
Exemple #10
0
/** @author <a href="mailto:[email protected]">Clebert Suconic</a> */
public final class Page implements Comparable<Page> {
  // Constants -----------------------------------------------------
  private static final boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();
  private static final boolean isDebug = HornetQServerLogger.LOGGER.isDebugEnabled();

  public static final int SIZE_RECORD =
      DataConstants.SIZE_BYTE + DataConstants.SIZE_INT + DataConstants.SIZE_BYTE;

  private static final byte START_BYTE = (byte) '{';

  private static final byte END_BYTE = (byte) '}';

  // Attributes ----------------------------------------------------

  private final int pageId;

  private boolean suspiciousRecords = false;

  private final AtomicInteger numberOfMessages = new AtomicInteger(0);

  private final SequentialFile file;

  private final SequentialFileFactory fileFactory;

  /** The page cache that will be filled with data as we write more data */
  private volatile LivePageCache pageCache;

  private final AtomicInteger size = new AtomicInteger(0);

  private final StorageManager storageManager;

  private final SimpleString storeName;

  public Page(
      final SimpleString storeName,
      final StorageManager storageManager,
      final SequentialFileFactory factory,
      final SequentialFile file,
      final int pageId)
      throws Exception {
    this.pageId = pageId;
    this.file = file;
    fileFactory = factory;
    this.storageManager = storageManager;
    this.storeName = storeName;
  }

  public int getPageId() {
    return pageId;
  }

  public void setLiveCache(LivePageCache pageCache) {
    this.pageCache = pageCache;
  }

  public synchronized List<PagedMessage> read(StorageManager storage) throws Exception {
    if (isDebug) {
      HornetQServerLogger.LOGGER.debug(
          "reading page " + this.pageId + " on address = " + storeName);
    }

    if (!file.isOpen()) {
      throw HornetQMessageBundle.BUNDLE.invalidPageIO();
    }

    ArrayList<PagedMessage> messages = new ArrayList<PagedMessage>();

    size.set((int) file.size());
    // Using direct buffer, as described on https://jira.jboss.org/browse/HORNETQ-467
    ByteBuffer directBuffer = storage.allocateDirectBuffer((int) file.size());

    try {

      file.position(0);
      file.read(directBuffer);

      directBuffer.rewind();

      HornetQBuffer fileBuffer = HornetQBuffers.wrappedBuffer(directBuffer);
      fileBuffer.writerIndex(fileBuffer.capacity());

      while (fileBuffer.readable()) {
        final int position = fileBuffer.readerIndex();

        byte byteRead = fileBuffer.readByte();

        if (byteRead == Page.START_BYTE) {
          if (fileBuffer.readerIndex() + DataConstants.SIZE_INT < fileBuffer.capacity()) {
            int messageSize = fileBuffer.readInt();
            int oldPos = fileBuffer.readerIndex();
            if (fileBuffer.readerIndex() + messageSize < fileBuffer.capacity()
                && fileBuffer.getByte(oldPos + messageSize) == Page.END_BYTE) {
              PagedMessage msg = new PagedMessageImpl();
              msg.decode(fileBuffer);
              byte b = fileBuffer.readByte();
              if (b != Page.END_BYTE) {
                // Sanity Check: This would only happen if there is a bug on decode or any internal
                // code, as
                // this
                // constraint was already checked
                throw new IllegalStateException(
                    "Internal error, it wasn't possible to locate END_BYTE " + b);
              }
              msg.initMessage(storage);
              if (isTrace) {
                HornetQServerLogger.LOGGER.trace(
                    "Reading message "
                        + msg
                        + " on pageId="
                        + this.pageId
                        + " for address="
                        + storeName);
              }
              messages.add(msg);
            } else {
              markFileAsSuspect(file.getFileName(), position, messages.size());
              break;
            }
          }
        } else {
          markFileAsSuspect(file.getFileName(), position, messages.size());
          break;
        }
      }
    } finally {
      storage.freeDirectBuffer(directBuffer);
    }

    numberOfMessages.set(messages.size());

    return messages;
  }

  public synchronized void write(final PagedMessage message) throws Exception {
    if (!file.isOpen()) {

      return;
    }

    ByteBuffer buffer = fileFactory.newBuffer(message.getEncodeSize() + Page.SIZE_RECORD);

    HornetQBuffer wrap = HornetQBuffers.wrappedBuffer(buffer);
    wrap.clear();

    wrap.writeByte(Page.START_BYTE);
    wrap.writeInt(0);
    int startIndex = wrap.writerIndex();
    message.encode(wrap);
    int endIndex = wrap.writerIndex();
    wrap.setInt(1, endIndex - startIndex); // The encoded length
    wrap.writeByte(Page.END_BYTE);

    buffer.rewind();

    file.writeDirect(buffer, false);

    if (pageCache != null) {
      pageCache.addLiveMessage(message);
    }

    numberOfMessages.incrementAndGet();
    size.addAndGet(buffer.limit());

    storageManager.pageWrite(message, pageId);
  }

  public void sync() throws Exception {
    file.sync();
  }

  public void open() throws Exception {
    if (!file.isOpen()) {
      file.open();
    }
    size.set((int) file.size());
    file.position(0);
  }

  public synchronized void close() throws Exception {
    if (storageManager != null) {
      storageManager.pageClosed(storeName, pageId);
    }
    if (pageCache != null) {
      pageCache.close();
      // leave it to the soft cache to decide when to release it now
      pageCache = null;
    }
    file.close();
  }

  public boolean isLive() {
    return pageCache != null;
  }

  public boolean delete(final PagedMessage[] messages) throws Exception {
    if (storageManager != null) {
      storageManager.pageDeleted(storeName, pageId);
    }

    if (isDebug) {
      HornetQServerLogger.LOGGER.debug("Deleting pageId=" + pageId + " on store " + storeName);
    }

    if (messages != null) {
      for (PagedMessage msg : messages) {
        if (msg.getMessage().isLargeMessage()) {
          LargeServerMessage lmsg = (LargeServerMessage) msg.getMessage();

          // Remember, cannot call delete directly here
          // Because the large-message may be linked to another message
          // or it may still being delivered even though it has been acked already
          lmsg.decrementDelayDeletionCount();
        }
      }
    }

    try {
      if (suspiciousRecords) {
        HornetQServerLogger.LOGGER.pageInvalid(file.getFileName(), file.getFileName());
        file.renameTo(file.getFileName() + ".invalidPage");
      } else {
        file.delete();
      }

      return true;
    } catch (Exception e) {
      HornetQServerLogger.LOGGER.pageDeleteError(e);
      return false;
    }
  }

  public int getNumberOfMessages() {
    return numberOfMessages.intValue();
  }

  public int getSize() {
    return size.intValue();
  }

  @Override
  public String toString() {
    return "Page::pageID=" + this.pageId + ", file=" + this.file;
  }

  public int compareTo(Page otherPage) {
    return otherPage.getPageId() - this.pageId;
  }

  @Override
  protected void finalize() {
    try {
      if (file != null && file.isOpen()) {
        file.close();
      }
    } catch (Exception e) {
      HornetQServerLogger.LOGGER.pageFinaliseError(e);
    }
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + pageId;
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Page other = (Page) obj;
    if (pageId != other.pageId) return false;
    return true;
  }

  /**
   * @param position
   * @param msgNumber
   */
  private void markFileAsSuspect(final String fileName, final int position, final int msgNumber) {
    HornetQServerLogger.LOGGER.pageSuspectFile(fileName, position, msgNumber);
    suspiciousRecords = true;
  }

  public SequentialFile getFile() {
    return file;
  }
}
    public boolean deliver() throws Exception {
      lockDelivery.readLock().lock();
      try {
        if (largeMessage == null) {
          return true;
        }

        if (availableCredits != null && availableCredits.get() <= 0) {
          if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
            HornetQServerLogger.LOGGER.trace(
                this
                    + "::FlowControl::delivery largeMessage interrupting as there are no more credits, available="
                    + availableCredits);
          }

          return false;
        }

        if (!sentInitialPacket) {
          context = largeMessage.getBodyEncoder();

          sizePendingLargeMessage = context.getLargeBodySize();

          context.open();

          sentInitialPacket = true;

          int packetSize =
              callback.sendLargeMessage(
                  largeMessage, id, context.getLargeBodySize(), ref.getDeliveryCount());

          if (availableCredits != null) {
            availableCredits.addAndGet(-packetSize);

            if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
              HornetQServerLogger.LOGGER.trace(
                  this
                      + "::FlowControl::"
                      + " deliver initialpackage with "
                      + packetSize
                      + " delivered, available now = "
                      + availableCredits);
            }
          }

          // Execute the rest of the large message on a different thread so as not to tie up the
          // delivery thread
          // for too long

          resumeLargeMessage();

          return false;
        } else {
          if (availableCredits != null && availableCredits.get() <= 0) {
            if (ServerConsumerImpl.isTrace) {
              HornetQServerLogger.LOGGER.trace(
                  this
                      + "::FlowControl::deliverLargeMessage Leaving loop of send LargeMessage because of credits, available="
                      + availableCredits);
            }

            return false;
          }

          int localChunkLen = 0;

          localChunkLen =
              (int)
                  Math.min(
                      sizePendingLargeMessage - positionPendingLargeMessage, minLargeMessageSize);

          HornetQBuffer bodyBuffer = HornetQBuffers.fixedBuffer(localChunkLen);

          context.encode(bodyBuffer, localChunkLen);

          byte[] body = bodyBuffer.toByteBuffer().array();

          int packetSize =
              callback.sendLargeMessageContinuation(
                  id,
                  body,
                  positionPendingLargeMessage + localChunkLen < sizePendingLargeMessage,
                  false);

          int chunkLen = body.length;

          if (availableCredits != null) {
            availableCredits.addAndGet(-packetSize);

            if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
              HornetQServerLogger.LOGGER.trace(
                  this
                      + "::FlowControl::largeMessage deliver continuation, packetSize="
                      + packetSize
                      + " available now="
                      + availableCredits);
            }
          }

          positionPendingLargeMessage += chunkLen;

          if (positionPendingLargeMessage < sizePendingLargeMessage) {
            resumeLargeMessage();

            return false;
          }
        }

        if (ServerConsumerImpl.isTrace) {
          HornetQServerLogger.LOGGER.trace("Finished deliverLargeMessage");
        }

        finish();

        return true;
      } finally {
        lockDelivery.readLock().unlock();
      }
    }
/**
 * Concrete implementation of a ClientConsumer.
 *
 * @author <a href="mailto:[email protected]">Tim Fox</a>
 * @author <a href="mailto:[email protected]">Jeff Mesnil</a>
 * @author <a href="mailto:[email protected]">Clebert Suconic</a>
 * @version <tt>$Revision: 3783 $</tt> $Id: ServerConsumerImpl.java 3783 2008-02-25 12:15:14Z timfox
 *     $
 */
public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
  // Constants ------------------------------------------------------------------------------------

  private static boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();

  // Static ---------------------------------------------------------------------------------------

  // Attributes -----------------------------------------------------------------------------------

  private final long id;

  private final Queue messageQueue;

  private final Filter filter;

  private final int minLargeMessageSize;

  private final ServerSession session;

  private final Object lock = new Object();

  /**
   * We get a readLock when a message is handled, and return the readLock when the message is
   * finally delivered When stopping the consumer we need to get a writeLock to make sure we had all
   * delivery finished otherwise a rollback may get message sneaking in
   */
  private final ReadWriteLock lockDelivery = new ReentrantReadWriteLock();

  private volatile AtomicInteger availableCredits = new AtomicInteger(0);

  private boolean started;

  private volatile LargeMessageDeliverer largeMessageDeliverer = null;

  public String debug() {
    return toString() + "::Delivering " + this.deliveringRefs.size();
  }

  /**
   * if we are a browse only consumer we don't need to worry about acknowledgements or being
   * started/stopped by the session.
   */
  private final boolean browseOnly;

  private BrowserDeliverer browserDeliverer;

  private final boolean strictUpdateDeliveryCount;

  private final StorageManager storageManager;

  private final java.util.Queue<MessageReference> deliveringRefs =
      new ConcurrentLinkedQueue<MessageReference>();

  private final SessionCallback callback;

  private final boolean preAcknowledge;

  private final ManagementService managementService;

  private final Binding binding;

  private boolean transferring = false;

  /* As well as consumer credit based flow control, we also tap into TCP flow control (assuming transport is using TCP)
   * This is useful in the case where consumer-window-size = -1, but we don't want to OOM by sending messages ad infinitum to the Netty
   * write queue when the TCP buffer is full, e.g. the client is slow or has died.
   */
  private final AtomicBoolean writeReady = new AtomicBoolean(true);

  private final long creationTime;

  // Constructors ---------------------------------------------------------------------------------

  public ServerConsumerImpl(
      final long id,
      final ServerSession session,
      final QueueBinding binding,
      final Filter filter,
      final boolean started,
      final boolean browseOnly,
      final StorageManager storageManager,
      final SessionCallback callback,
      final boolean preAcknowledge,
      final boolean strictUpdateDeliveryCount,
      final ManagementService managementService)
      throws Exception {
    this.id = id;

    this.filter = filter;

    this.session = session;

    this.binding = binding;

    messageQueue = binding.getQueue();

    this.started = browseOnly || started;

    this.browseOnly = browseOnly;

    this.storageManager = storageManager;

    this.callback = callback;

    this.preAcknowledge = preAcknowledge;

    this.managementService = managementService;

    minLargeMessageSize = session.getMinLargeMessageSize();

    this.strictUpdateDeliveryCount = strictUpdateDeliveryCount;

    this.callback.addReadyListener(this);

    this.creationTime = System.currentTimeMillis();

    if (browseOnly) {
      browserDeliverer = new BrowserDeliverer(messageQueue.iterator());
    } else {
      messageQueue.addConsumer(this);
    }
  }

  // ServerConsumer implementation
  // ----------------------------------------------------------------------

  public long getID() {
    return id;
  }

  public boolean isBrowseOnly() {
    return browseOnly;
  }

  public long getCreationTime() {
    return creationTime;
  }

  public String getConnectionID() {
    return this.session.getConnectionID().toString();
  }

  public String getSessionID() {
    return this.session.getName();
  }

  public void getDeliveringMessages(List<MessageReference> refList) {
    synchronized (lock) {
      refList.addAll(deliveringRefs);
    }
  }

  public HandleStatus handle(final MessageReference ref) throws Exception {
    if (availableCredits != null && availableCredits.get() <= 0) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this
                + " is busy for the lack of credits. Current credits = "
                + availableCredits
                + " Can't receive reference "
                + ref);
      }

      return HandleStatus.BUSY;
    }

    // TODO - https://jira.jboss.org/browse/HORNETQ-533
    // if (!writeReady.get())
    // {
    // return HandleStatus.BUSY;
    // }

    synchronized (lock) {
      // If the consumer is stopped then we don't accept the message, it
      // should go back into the
      // queue for delivery later.
      if (!started || transferring) {
        return HandleStatus.BUSY;
      }

      // If there is a pendingLargeMessage we can't take another message
      // This has to be checked inside the lock as the set to null is done inside the lock
      if (largeMessageDeliverer != null) {
        if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
          HornetQServerLogger.LOGGER.debug(
              this
                  + " is busy delivering large message "
                  + largeMessageDeliverer
                  + ", can't deliver reference "
                  + ref);
        }
        return HandleStatus.BUSY;
      }
      final ServerMessage message = ref.getMessage();

      if (filter != null && !filter.match(message)) {
        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              "Reference " + ref + " is a noMatch on consumer " + this);
        }
        return HandleStatus.NO_MATCH;
      }

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace("Handling reference " + ref);
      }

      if (!browseOnly) {
        if (!preAcknowledge) {
          deliveringRefs.add(ref);
        }

        ref.handled();

        ref.incrementDeliveryCount();

        // If updateDeliveries = false (set by strict-update),
        // the updateDeliveryCount would still be updated after c
        if (strictUpdateDeliveryCount && !ref.isPaged()) {
          if (ref.getMessage().isDurable()
              && ref.getQueue().isDurable()
              && !ref.getQueue().isInternalQueue()
              && !ref.isPaged()) {
            storageManager.updateDeliveryCount(ref);
          }
        }

        if (preAcknowledge) {
          if (message.isLargeMessage()) {
            // we must hold one reference, or the file will be deleted before it could be delivered
            ((LargeServerMessage) message).incrementDelayDeletionCount();
          }

          // With pre-ack, we ack *before* sending to the client
          ref.getQueue().acknowledge(ref);
        }
      }

      if (message.isLargeMessage()) {
        largeMessageDeliverer = new LargeMessageDeliverer((LargeServerMessage) message, ref);
      }

      lockDelivery.readLock().lock();

      return HandleStatus.HANDLED;
    }
  }

  public void proceedDeliver(MessageReference reference) throws Exception {
    try {
      ServerMessage message = reference.getMessage();

      if (message.isLargeMessage()) {
        if (largeMessageDeliverer == null) {
          // This can't really happen as handle had already crated the deliverer
          // instead of throwing an exception in weird cases there is no problem on just go ahead
          // and create it
          // again here
          largeMessageDeliverer =
              new LargeMessageDeliverer((LargeServerMessage) message, reference);
        }
        // The deliverer was prepared during handle, as we can't have more than one pending large
        // message
        // as it would return busy if there is anything pending
        largeMessageDeliverer.deliver();
      } else {
        deliverStandardMessage(reference, message);
      }
    } finally {
      lockDelivery.readLock().unlock();
    }
  }

  public Filter getFilter() {
    return filter;
  }

  public void close(final boolean failed) throws Exception {
    callback.removeReadyListener(this);

    setStarted(false);

    LargeMessageDeliverer del = largeMessageDeliverer;

    if (del != null) {
      del.finish();
    }

    if (browseOnly) {
      browserDeliverer.close();
    } else {
      messageQueue.removeConsumer(this);
    }

    session.removeConsumer(id);

    LinkedList<MessageReference> refs = cancelRefs(failed, false, null);

    Iterator<MessageReference> iter = refs.iterator();

    Transaction tx = new TransactionImpl(storageManager);

    while (iter.hasNext()) {
      MessageReference ref = iter.next();

      ref.getQueue().cancel(tx, ref);
    }

    tx.rollback();

    if (!browseOnly) {
      TypedProperties props = new TypedProperties();

      props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS, binding.getAddress());

      props.putSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME, binding.getClusterName());

      props.putSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME, binding.getRoutingName());

      props.putSimpleStringProperty(
          ManagementHelper.HDR_FILTERSTRING, filter == null ? null : filter.getFilterString());

      props.putIntProperty(ManagementHelper.HDR_DISTANCE, binding.getDistance());

      props.putIntProperty(ManagementHelper.HDR_CONSUMER_COUNT, messageQueue.getConsumerCount());

      // HORNETQ-946
      props.putSimpleStringProperty(
          ManagementHelper.HDR_USER, SimpleString.toSimpleString(session.getUsername()));

      props.putSimpleStringProperty(
          ManagementHelper.HDR_REMOTE_ADDRESS,
          SimpleString.toSimpleString(
              ((ServerSessionImpl) session).getRemotingConnection().getRemoteAddress()));

      props.putSimpleStringProperty(
          ManagementHelper.HDR_SESSION_NAME, SimpleString.toSimpleString(session.getName()));

      Notification notification = new Notification(null, NotificationType.CONSUMER_CLOSED, props);

      managementService.sendNotification(notification);
    }
  }

  /**
   * Prompt delivery and send a "forced delivery" message to the consumer.
   *
   * <p>When the consumer receives such a "forced delivery" message, it discards it and knows that
   * there are no other messages to be delivered.
   */
  public synchronized void forceDelivery(final long sequence) {
    promptDelivery();

    // JBPAPP-6030 - Using the executor to avoid distributed dead locks
    messageQueue
        .getExecutor()
        .execute(
            new Runnable() {
              public void run() {
                try {
                  // We execute this on the same executor to make sure the force delivery message is
                  // written after
                  // any delivery is completed

                  synchronized (lock) {
                    if (transferring) {
                      // Case it's transferring (reattach), we will retry later
                      messageQueue
                          .getExecutor()
                          .execute(
                              new Runnable() {
                                public void run() {
                                  forceDelivery(sequence);
                                }
                              });
                    } else {
                      ServerMessage forcedDeliveryMessage =
                          new ServerMessageImpl(storageManager.generateUniqueID(), 50);

                      forcedDeliveryMessage.putLongProperty(
                          ClientConsumerImpl.FORCED_DELIVERY_MESSAGE, sequence);
                      forcedDeliveryMessage.setAddress(messageQueue.getName());

                      callback.sendMessage(forcedDeliveryMessage, id, 0);
                    }
                  }
                } catch (Exception e) {
                  HornetQServerLogger.LOGGER.errorSendingForcedDelivery(e);
                }
              }
            });
  }

  public LinkedList<MessageReference> cancelRefs(
      final boolean failed, final boolean lastConsumedAsDelivered, final Transaction tx)
      throws Exception {
    boolean performACK = lastConsumedAsDelivered;

    try {
      if (largeMessageDeliverer != null) {
        largeMessageDeliverer.finish();
      }
    } catch (Throwable e) {
      HornetQServerLogger.LOGGER.errorResttingLargeMessage(e, largeMessageDeliverer);
    } finally {
      largeMessageDeliverer = null;
    }

    LinkedList<MessageReference> refs = new LinkedList<MessageReference>();

    if (!deliveringRefs.isEmpty()) {
      for (MessageReference ref : deliveringRefs) {
        if (isTrace) {
          HornetQServerLogger.LOGGER.trace(
              "Cancelling reference for messageID = "
                  + ref.getMessage().getMessageID()
                  + ", ref = "
                  + ref);
        }
        if (performACK) {
          acknowledge(false, tx, ref.getMessage().getMessageID());

          performACK = false;
        } else {
          if (!failed) {
            // We don't decrement delivery count if the client failed, since there's a possibility
            // that refs
            // were actually delivered but we just didn't get any acks for them
            // before failure
            ref.decrementDeliveryCount();
          }

          refs.add(ref);
        }
      }

      deliveringRefs.clear();
    }

    return refs;
  }

  public void setStarted(final boolean started) {
    synchronized (lock) {
      // This is to make sure that the delivery process has finished any pending delivery
      // otherwise a message may sneak in on the client while we are trying to stop the consumer
      lockDelivery.writeLock().lock();
      try {
        this.started = browseOnly || started;
      } finally {
        lockDelivery.writeLock().unlock();
      }
    }

    // Outside the lock
    if (started) {
      promptDelivery();
    }
  }

  public void setTransferring(final boolean transferring) {
    synchronized (lock) {
      // This is to make sure that the delivery process has finished any pending delivery
      // otherwise a message may sneak in on the client while we are trying to stop the consumer
      lockDelivery.writeLock().lock();
      try {
        this.transferring = transferring;
      } finally {
        lockDelivery.writeLock().unlock();
      }
    }

    // Outside the lock
    if (transferring) {
      // And we must wait for any force delivery to be executed - this is executed async so we add a
      // future to the
      // executor and
      // wait for it to complete

      FutureLatch future = new FutureLatch();

      messageQueue.getExecutor().execute(future);

      boolean ok = future.await(10000);

      if (!ok) {
        HornetQServerLogger.LOGGER.errorTransferringConsumer();
      }
    }

    if (!transferring) {
      promptDelivery();
    }
  }

  public void receiveCredits(final int credits) throws Exception {
    if (credits == -1) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this + ":: FlowControl::Received disable flow control message");
      }
      // No flow control
      availableCredits = null;

      // There may be messages already in the queue
      promptDelivery();
    } else if (credits == 0) {
      // reset, used on slow consumers
      HornetQServerLogger.LOGGER.debug(
          this + ":: FlowControl::Received reset flow control message");
      availableCredits.set(0);
    } else {
      int previous = availableCredits.getAndAdd(credits);

      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this
                + "::FlowControl::Received "
                + credits
                + " credits, previous value = "
                + previous
                + " currentValue = "
                + availableCredits.get());
      }

      if (previous <= 0 && previous + credits > 0) {
        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              this + "::calling promptDelivery from receiving credits");
        }
        promptDelivery();
      }
    }
  }

  public Queue getQueue() {
    return messageQueue;
  }

  public void acknowledge(final boolean autoCommitAcks, Transaction tx, final long messageID)
      throws Exception {
    if (browseOnly) {
      return;
    }

    // Acknowledge acknowledges all refs delivered by the consumer up to and including the one
    // explicitly
    // acknowledged

    // We use a transaction here as if the message is not found, we should rollback anything done
    // This could eventually happen on retries during transactions, and we need to make sure we
    // don't ACK things we are not supposed to acknowledge

    boolean startedTransaction = false;

    if (tx == null || autoCommitAcks) {
      startedTransaction = true;
      tx = new TransactionImpl(storageManager);
    }

    try {

      MessageReference ref;
      do {
        ref = deliveringRefs.poll();

        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              "ACKing ref " + ref + " on tx= " + tx + ", consumer=" + this);
        }

        if (ref == null) {
          throw HornetQMessageBundle.BUNDLE.consumerNoReference(
              id, messageID, messageQueue.getName());
        }

        ref.getQueue().acknowledge(tx, ref);
      } while (ref.getMessage().getMessageID() != messageID);

      if (startedTransaction) {
        tx.commit();
      }
    } catch (HornetQException e) {
      if (startedTransaction) {
        tx.rollback();
      } else {
        tx.markAsRollbackOnly(e);
      }
      throw e;
    } catch (Throwable e) {
      HornetQServerLogger.LOGGER.errorAckingMessage((Exception) e);
      HornetQException hqex = new HornetQIllegalStateException(e.getMessage());
      if (startedTransaction) {
        tx.rollback();
      } else {
        tx.markAsRollbackOnly(hqex);
      }
      throw hqex;
    }
  }

  public void individualAcknowledge(
      final boolean autoCommitAcks, final Transaction tx, final long messageID) throws Exception {
    if (browseOnly) {
      return;
    }

    MessageReference ref = removeReferenceByID(messageID);

    if (ref == null) {
      throw new IllegalStateException("Cannot find ref to ack " + messageID);
    }

    if (autoCommitAcks) {
      ref.getQueue().acknowledge(ref);
    } else {
      ref.getQueue().acknowledge(tx, ref);
    }
  }

  public MessageReference removeReferenceByID(final long messageID) throws Exception {
    if (browseOnly) {
      return null;
    }

    // Expiries can come in out of sequence with respect to delivery order

    Iterator<MessageReference> iter = deliveringRefs.iterator();

    MessageReference ref = null;

    while (iter.hasNext()) {
      MessageReference theRef = iter.next();

      if (theRef.getMessage().getMessageID() == messageID) {
        iter.remove();

        ref = theRef;

        break;
      }
    }

    return ref;
  }

  public void readyForWriting(final boolean ready) {
    if (ready) {
      writeReady.set(true);

      promptDelivery();
    } else {
      writeReady.set(false);
    }
  }

  /** To be used on tests only */
  public AtomicInteger getAvailableCredits() {
    return availableCredits;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "ServerConsumerImpl [id=" + id + ", filter=" + filter + ", binding=" + binding + "]";
  }

  public String toManagementString() {
    return "ServerConsumer [id="
        + id
        + ", filter="
        + filter
        + ", binding="
        + binding.toManagementString()
        + "]";
  }

  // Private --------------------------------------------------------------------------------------

  private void promptDelivery() {
    // largeMessageDeliverer is always set inside a lock
    // if we don't acquire a lock, we will have NPE eventually
    if (largeMessageDeliverer != null) {
      resumeLargeMessage();
    } else {
      forceDelivery();
    }
  }

  private void forceDelivery() {
    if (browseOnly) {
      messageQueue.getExecutor().execute(browserDeliverer);
    } else {
      messageQueue.deliverAsync();
    }
  }

  private void resumeLargeMessage() {
    messageQueue.getExecutor().execute(resumeLargeMessageRunnable);
  }

  /**
   * @param ref
   * @param message
   */
  private void deliverStandardMessage(final MessageReference ref, final ServerMessage message) {
    int packetSize = callback.sendMessage(message, id, ref.getDeliveryCount());

    if (availableCredits != null) {
      availableCredits.addAndGet(-packetSize);

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            this
                + "::FlowControl::delivery standard taking "
                + packetSize
                + " from credits, available now is "
                + availableCredits);
      }
    }
  }

  // Inner classes
  // ------------------------------------------------------------------------

  private final Runnable resumeLargeMessageRunnable =
      new Runnable() {
        public void run() {
          synchronized (lock) {
            try {
              if (largeMessageDeliverer == null || largeMessageDeliverer.deliver()) {
                forceDelivery();
              }
            } catch (Exception e) {
              HornetQServerLogger.LOGGER.errorRunningLargeMessageDeliverer(e);
            }
          }
        }
      };

  /**
   * Internal encapsulation of the logic on sending LargeMessages. This Inner class was created to
   * avoid a bunch of loose properties about the current LargeMessage being sent
   */
  private final class LargeMessageDeliverer {
    private long sizePendingLargeMessage;

    private LargeServerMessage largeMessage;

    private final MessageReference ref;

    private boolean sentInitialPacket = false;

    /** The current position on the message being processed */
    private long positionPendingLargeMessage;

    private BodyEncoder context;

    public LargeMessageDeliverer(final LargeServerMessage message, final MessageReference ref)
        throws Exception {
      largeMessage = message;

      largeMessage.incrementDelayDeletionCount();

      this.ref = ref;
    }

    public boolean deliver() throws Exception {
      lockDelivery.readLock().lock();
      try {
        if (largeMessage == null) {
          return true;
        }

        if (availableCredits != null && availableCredits.get() <= 0) {
          if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
            HornetQServerLogger.LOGGER.trace(
                this
                    + "::FlowControl::delivery largeMessage interrupting as there are no more credits, available="
                    + availableCredits);
          }

          return false;
        }

        if (!sentInitialPacket) {
          context = largeMessage.getBodyEncoder();

          sizePendingLargeMessage = context.getLargeBodySize();

          context.open();

          sentInitialPacket = true;

          int packetSize =
              callback.sendLargeMessage(
                  largeMessage, id, context.getLargeBodySize(), ref.getDeliveryCount());

          if (availableCredits != null) {
            availableCredits.addAndGet(-packetSize);

            if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
              HornetQServerLogger.LOGGER.trace(
                  this
                      + "::FlowControl::"
                      + " deliver initialpackage with "
                      + packetSize
                      + " delivered, available now = "
                      + availableCredits);
            }
          }

          // Execute the rest of the large message on a different thread so as not to tie up the
          // delivery thread
          // for too long

          resumeLargeMessage();

          return false;
        } else {
          if (availableCredits != null && availableCredits.get() <= 0) {
            if (ServerConsumerImpl.isTrace) {
              HornetQServerLogger.LOGGER.trace(
                  this
                      + "::FlowControl::deliverLargeMessage Leaving loop of send LargeMessage because of credits, available="
                      + availableCredits);
            }

            return false;
          }

          int localChunkLen = 0;

          localChunkLen =
              (int)
                  Math.min(
                      sizePendingLargeMessage - positionPendingLargeMessage, minLargeMessageSize);

          HornetQBuffer bodyBuffer = HornetQBuffers.fixedBuffer(localChunkLen);

          context.encode(bodyBuffer, localChunkLen);

          byte[] body = bodyBuffer.toByteBuffer().array();

          int packetSize =
              callback.sendLargeMessageContinuation(
                  id,
                  body,
                  positionPendingLargeMessage + localChunkLen < sizePendingLargeMessage,
                  false);

          int chunkLen = body.length;

          if (availableCredits != null) {
            availableCredits.addAndGet(-packetSize);

            if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
              HornetQServerLogger.LOGGER.trace(
                  this
                      + "::FlowControl::largeMessage deliver continuation, packetSize="
                      + packetSize
                      + " available now="
                      + availableCredits);
            }
          }

          positionPendingLargeMessage += chunkLen;

          if (positionPendingLargeMessage < sizePendingLargeMessage) {
            resumeLargeMessage();

            return false;
          }
        }

        if (ServerConsumerImpl.isTrace) {
          HornetQServerLogger.LOGGER.trace("Finished deliverLargeMessage");
        }

        finish();

        return true;
      } finally {
        lockDelivery.readLock().unlock();
      }
    }

    public void finish() throws Exception {
      synchronized (lock) {
        if (largeMessage == null) {
          // handleClose could be calling close while handle is also calling finish.
          // As a result one of them could get here after the largeMessage is already gone.
          // On that case we just ignore this call
          return;
        }
        if (context != null) {
          context.close();
        }

        largeMessage.releaseResources();

        largeMessage.decrementDelayDeletionCount();

        if (preAcknowledge && !browseOnly) {
          // PreAck will have an extra reference
          largeMessage.decrementDelayDeletionCount();
        }

        largeMessageDeliverer = null;

        largeMessage = null;
      }
    }
  }

  private class BrowserDeliverer implements Runnable {
    private MessageReference current = null;

    public BrowserDeliverer(final LinkedListIterator<MessageReference> iterator) {
      this.iterator = iterator;
    }

    private final LinkedListIterator<MessageReference> iterator;

    public synchronized void close() {
      iterator.close();
    }

    public synchronized void run() {
      // if the reference was busy during the previous iteration, handle it now
      if (current != null) {
        try {
          HandleStatus status = handle(current);

          if (status == HandleStatus.BUSY) {
            return;
          }

          if (status == HandleStatus.HANDLED) {
            proceedDeliver(current);
          }

          current = null;
        } catch (Exception e) {
          HornetQServerLogger.LOGGER.errorBrowserHandlingMessage(e, current);
          return;
        }
      }

      MessageReference ref = null;
      HandleStatus status;

      while (true) {
        try {
          ref = null;
          synchronized (messageQueue) {
            if (!iterator.hasNext()) {
              break;
            }

            ref = iterator.next();

            status = handle(ref);
          }

          if (status == HandleStatus.HANDLED) {
            proceedDeliver(ref);
          } else if (status == HandleStatus.BUSY) {
            // keep a reference on the current message reference
            // to handle it next time the browser deliverer is executed
            current = ref;
            break;
          }
        } catch (Exception e) {
          HornetQServerLogger.LOGGER.errorBrowserHandlingMessage(e, ref);
          break;
        }
      }
    }
  }
}
  public void acknowledge(final boolean autoCommitAcks, Transaction tx, final long messageID)
      throws Exception {
    if (browseOnly) {
      return;
    }

    // Acknowledge acknowledges all refs delivered by the consumer up to and including the one
    // explicitly
    // acknowledged

    // We use a transaction here as if the message is not found, we should rollback anything done
    // This could eventually happen on retries during transactions, and we need to make sure we
    // don't ACK things we are not supposed to acknowledge

    boolean startedTransaction = false;

    if (tx == null || autoCommitAcks) {
      startedTransaction = true;
      tx = new TransactionImpl(storageManager);
    }

    try {

      MessageReference ref;
      do {
        ref = deliveringRefs.poll();

        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              "ACKing ref " + ref + " on tx= " + tx + ", consumer=" + this);
        }

        if (ref == null) {
          throw HornetQMessageBundle.BUNDLE.consumerNoReference(
              id, messageID, messageQueue.getName());
        }

        ref.getQueue().acknowledge(tx, ref);
      } while (ref.getMessage().getMessageID() != messageID);

      if (startedTransaction) {
        tx.commit();
      }
    } catch (HornetQException e) {
      if (startedTransaction) {
        tx.rollback();
      } else {
        tx.markAsRollbackOnly(e);
      }
      throw e;
    } catch (Throwable e) {
      HornetQServerLogger.LOGGER.errorAckingMessage((Exception) e);
      HornetQException hqex = new HornetQIllegalStateException(e.getMessage());
      if (startedTransaction) {
        tx.rollback();
      } else {
        tx.markAsRollbackOnly(hqex);
      }
      throw hqex;
    }
  }
  public HandleStatus handle(final MessageReference ref) throws Exception {
    if (availableCredits != null && availableCredits.get() <= 0) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this
                + " is busy for the lack of credits. Current credits = "
                + availableCredits
                + " Can't receive reference "
                + ref);
      }

      return HandleStatus.BUSY;
    }

    // TODO - https://jira.jboss.org/browse/HORNETQ-533
    // if (!writeReady.get())
    // {
    // return HandleStatus.BUSY;
    // }

    synchronized (lock) {
      // If the consumer is stopped then we don't accept the message, it
      // should go back into the
      // queue for delivery later.
      if (!started || transferring) {
        return HandleStatus.BUSY;
      }

      // If there is a pendingLargeMessage we can't take another message
      // This has to be checked inside the lock as the set to null is done inside the lock
      if (largeMessageDeliverer != null) {
        if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
          HornetQServerLogger.LOGGER.debug(
              this
                  + " is busy delivering large message "
                  + largeMessageDeliverer
                  + ", can't deliver reference "
                  + ref);
        }
        return HandleStatus.BUSY;
      }
      final ServerMessage message = ref.getMessage();

      if (filter != null && !filter.match(message)) {
        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              "Reference " + ref + " is a noMatch on consumer " + this);
        }
        return HandleStatus.NO_MATCH;
      }

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace("Handling reference " + ref);
      }

      if (!browseOnly) {
        if (!preAcknowledge) {
          deliveringRefs.add(ref);
        }

        ref.handled();

        ref.incrementDeliveryCount();

        // If updateDeliveries = false (set by strict-update),
        // the updateDeliveryCount would still be updated after c
        if (strictUpdateDeliveryCount && !ref.isPaged()) {
          if (ref.getMessage().isDurable()
              && ref.getQueue().isDurable()
              && !ref.getQueue().isInternalQueue()
              && !ref.isPaged()) {
            storageManager.updateDeliveryCount(ref);
          }
        }

        if (preAcknowledge) {
          if (message.isLargeMessage()) {
            // we must hold one reference, or the file will be deleted before it could be delivered
            ((LargeServerMessage) message).incrementDelayDeletionCount();
          }

          // With pre-ack, we ack *before* sending to the client
          ref.getQueue().acknowledge(ref);
        }
      }

      if (message.isLargeMessage()) {
        largeMessageDeliverer = new LargeMessageDeliverer((LargeServerMessage) message, ref);
      }

      lockDelivery.readLock().lock();

      return HandleStatus.HANDLED;
    }
  }
/**
 * A ClusterConnectionImpl
 *
 * @author <a href="mailto:[email protected]">Tim Fox</a>
 * @author Clebert Suconic
 */
public final class ClusterConnectionImpl
    implements ClusterConnection, AfterConnectInternalListener {
  private static final boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();

  private final ExecutorFactory executorFactory;

  private final Executor executor;

  private final HornetQServer server;

  private final PostOffice postOffice;

  private final ManagementService managementService;

  private final SimpleString name;

  private final SimpleString address;

  private final long clientFailureCheckPeriod;

  private final long connectionTTL;

  private final long retryInterval;

  private final long callTimeout;

  private final long callFailoverTimeout;

  private final double retryIntervalMultiplier;

  private final long maxRetryInterval;

  private final int reconnectAttempts;

  private final boolean useDuplicateDetection;

  private final boolean routeWhenNoConsumers;

  private final int confirmationWindowSize;

  /**
   * Guard for the field {@link #records}. Note that the field is {@link ConcurrentHashMap}, however
   * we need the guard to synchronize multiple step operations during topology updates.
   */
  private final Object recordsGuard = new Object();

  private final Map<String, MessageFlowRecord> records =
      new ConcurrentHashMap<String, MessageFlowRecord>();

  private final ScheduledExecutorService scheduledExecutor;

  private final int maxHops;

  private final NodeManager nodeManager;

  private volatile boolean started;

  private final String clusterUser;

  private final String clusterPassword;

  private final ClusterConnector clusterConnector;

  private ServerLocatorInternal serverLocator;

  private final TransportConfiguration connector;

  private final boolean allowDirectConnectionsOnly;

  private final Set<TransportConfiguration> allowableConnections =
      new HashSet<TransportConfiguration>();

  private final ClusterManager manager;

  private final int minLargeMessageSize;

  // Stuff that used to be on the ClusterManager

  private final Topology topology = new Topology(this);

  private volatile boolean stopping = false;

  private LiveNotifier liveNotifier = null;

  private final long clusterNotificationInterval;

  private final int clusterNotificationAttempts;

  /**
   * @param staticTranspConfigs notice if {@code null} this is a cluster which won't connect to
   *     anyone, but that can still accept incoming connections.
   * @param clusterNotificationInterval
   */
  public ClusterConnectionImpl(
      final ClusterManager manager,
      final TransportConfiguration[] staticTranspConfigs,
      final TransportConfiguration connector,
      final SimpleString name,
      final SimpleString address,
      final int minLargeMessageSize,
      final long clientFailureCheckPeriod,
      final long connectionTTL,
      final long retryInterval,
      final double retryIntervalMultiplier,
      final long maxRetryInterval,
      final int reconnectAttempts,
      final long callTimeout,
      final long callFailoverTimeout,
      final boolean useDuplicateDetection,
      final boolean routeWhenNoConsumers,
      final int confirmationWindowSize,
      final ExecutorFactory executorFactory,
      final HornetQServer server,
      final PostOffice postOffice,
      final ManagementService managementService,
      final ScheduledExecutorService scheduledExecutor,
      final int maxHops,
      final NodeManager nodeManager,
      final String clusterUser,
      final String clusterPassword,
      final boolean allowDirectConnectionsOnly,
      final long clusterNotificationInterval,
      final int clusterNotificationAttempts)
      throws Exception {
    this.nodeManager = nodeManager;

    this.connector = connector;

    this.name = name;

    this.address = address;

    this.clientFailureCheckPeriod = clientFailureCheckPeriod;

    this.connectionTTL = connectionTTL;

    this.retryInterval = retryInterval;

    this.retryIntervalMultiplier = retryIntervalMultiplier;

    this.maxRetryInterval = maxRetryInterval;

    this.reconnectAttempts = reconnectAttempts;

    this.useDuplicateDetection = useDuplicateDetection;

    this.routeWhenNoConsumers = routeWhenNoConsumers;

    this.confirmationWindowSize = confirmationWindowSize;

    this.executorFactory = executorFactory;

    this.clusterNotificationInterval = clusterNotificationInterval;

    this.clusterNotificationAttempts = clusterNotificationAttempts;

    this.executor = executorFactory.getExecutor();

    this.topology.setExecutor(executor);

    this.server = server;

    this.postOffice = postOffice;

    this.managementService = managementService;

    this.scheduledExecutor = scheduledExecutor;

    this.maxHops = maxHops;

    this.clusterUser = clusterUser;

    this.clusterPassword = clusterPassword;

    this.allowDirectConnectionsOnly = allowDirectConnectionsOnly;

    this.manager = manager;

    this.callTimeout = callTimeout;

    this.callFailoverTimeout = callFailoverTimeout;

    this.minLargeMessageSize = minLargeMessageSize;

    clusterConnector = new StaticClusterConnector(staticTranspConfigs);

    if (staticTranspConfigs != null && staticTranspConfigs.length > 0) {
      // a cluster connection will connect to other nodes only if they are directly connected
      // through a static list of connectors or broadcasting using UDP.
      if (allowDirectConnectionsOnly) {
        allowableConnections.addAll(Arrays.asList(staticTranspConfigs));
      }
    }
  }

  /**
   * @param dg discovery group, notice if {@code null} this is a cluster which won't connect to
   *     anyone, but that can still accept incoming connections.
   * @param clusterNotificationInterval
   */
  public ClusterConnectionImpl(
      final ClusterManager manager,
      DiscoveryGroupConfiguration dg,
      final TransportConfiguration connector,
      final SimpleString name,
      final SimpleString address,
      final int minLargeMessageSize,
      final long clientFailureCheckPeriod,
      final long connectionTTL,
      final long retryInterval,
      final double retryIntervalMultiplier,
      final long maxRetryInterval,
      final int reconnectAttempts,
      final long callTimeout,
      final long callFailoverTimeout,
      final boolean useDuplicateDetection,
      final boolean routeWhenNoConsumers,
      final int confirmationWindowSize,
      final ExecutorFactory executorFactory,
      final HornetQServer server,
      final PostOffice postOffice,
      final ManagementService managementService,
      final ScheduledExecutorService scheduledExecutor,
      final int maxHops,
      final NodeManager nodeManager,
      final String clusterUser,
      final String clusterPassword,
      final boolean allowDirectConnectionsOnly,
      final long clusterNotificationInterval,
      final int clusterNotificationAttempts)
      throws Exception {
    this.nodeManager = nodeManager;

    this.connector = connector;

    this.name = name;

    this.address = address;

    this.clientFailureCheckPeriod = clientFailureCheckPeriod;

    this.connectionTTL = connectionTTL;

    this.retryInterval = retryInterval;

    this.retryIntervalMultiplier = retryIntervalMultiplier;

    this.maxRetryInterval = maxRetryInterval;

    this.minLargeMessageSize = minLargeMessageSize;

    this.reconnectAttempts = reconnectAttempts;

    this.callTimeout = callTimeout;

    this.callFailoverTimeout = callFailoverTimeout;

    this.useDuplicateDetection = useDuplicateDetection;

    this.routeWhenNoConsumers = routeWhenNoConsumers;

    this.confirmationWindowSize = confirmationWindowSize;

    this.executorFactory = executorFactory;

    this.clusterNotificationInterval = clusterNotificationInterval;

    this.clusterNotificationAttempts = clusterNotificationAttempts;

    this.executor = executorFactory.getExecutor();

    this.topology.setExecutor(executor);

    this.server = server;

    this.postOffice = postOffice;

    this.managementService = managementService;

    this.scheduledExecutor = scheduledExecutor;

    this.maxHops = maxHops;

    this.clusterUser = clusterUser;

    this.clusterPassword = clusterPassword;

    this.allowDirectConnectionsOnly = allowDirectConnectionsOnly;

    clusterConnector = new DiscoveryClusterConnector(dg);

    this.manager = manager;
  }

  public void start() throws Exception {
    synchronized (this) {
      if (started) {
        return;
      }

      stopping = false;
      started = true;
      activate();
    }
  }

  public void flushExecutor() {
    FutureLatch future = new FutureLatch();
    executor.execute(future);
    if (!future.await(10000)) {
      server.threadDump("Couldn't finish executor on " + this);
    }
  }

  public void stop() throws Exception {
    if (!started) {
      return;
    }
    stopping = true;
    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(this + "::stopping ClusterConnection");
    }

    if (serverLocator != null) {
      serverLocator.removeClusterTopologyListener(this);
    }

    HornetQServerLogger.LOGGER.debug(
        "Cluster connection being stopped for node"
            + nodeManager.getNodeId()
            + ", server = "
            + this.server
            + " serverLocator = "
            + serverLocator);

    synchronized (this) {
      for (MessageFlowRecord record : records.values()) {
        try {
          record.close();
        } catch (Exception ignore) {
        }
      }
    }

    if (managementService != null) {
      TypedProperties props = new TypedProperties();
      props.putSimpleStringProperty(new SimpleString("name"), name);
      Notification notification =
          new Notification(
              nodeManager.getNodeId().toString(),
              NotificationType.CLUSTER_CONNECTION_STOPPED,
              props);
      managementService.sendNotification(notification);
    }
    executor.execute(
        new Runnable() {
          public void run() {
            synchronized (ClusterConnectionImpl.this) {
              closeLocator(serverLocator);
              serverLocator = null;
            }
          }
        });

    started = false;
  }

  /** @param locator */
  private void closeLocator(final ServerLocatorInternal locator) {
    if (locator != null) locator.close();
  }

  private TopologyMember getLocalMember() {
    return topology.getMember(manager.getNodeId());
  }

  public void addClusterTopologyListener(final ClusterTopologyListener listener) {
    topology.addClusterTopologyListener(listener);
  }

  public void removeClusterTopologyListener(final ClusterTopologyListener listener) {
    topology.removeClusterTopologyListener(listener);
  }

  public Topology getTopology() {
    return topology;
  }

  public void nodeAnnounced(
      final long uniqueEventID,
      final String nodeID,
      final String nodeName,
      final Pair<TransportConfiguration, TransportConfiguration> connectorPair,
      final boolean backup) {
    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(
          this + "::NodeAnnounced, backup=" + backup + nodeID + connectorPair);
    }

    TransportConfiguration live = connectorPair.getA();
    TransportConfiguration backupTC = connectorPair.getB();
    TopologyMemberImpl newMember = new TopologyMemberImpl(nodeID, nodeName, live, backupTC);
    newMember.setUniqueEventID(uniqueEventID);
    if (backup) {
      topology.updateBackup(new TopologyMemberImpl(nodeID, nodeName, live, backupTC));
    } else {
      topology.updateMember(uniqueEventID, nodeID, newMember);
    }
  }

  @Override
  public void onConnection(ClientSessionFactoryInternal sf) {
    TopologyMember localMember = getLocalMember();
    if (localMember != null) {
      sf.sendNodeAnnounce(
          localMember.getUniqueEventID(),
          manager.getNodeId(),
          manager.getNodeGroupName(),
          false,
          localMember.getLive(),
          localMember.getBackup());
    } else {
      HornetQServerLogger.LOGGER.noLocalMemborOnClusterConnection(this);
    }

    // TODO: shouldn't we send the current time here? and change the current topology?
    // sf.sendNodeAnnounce(System.currentTimeMillis(),
    // manager.getNodeId(),
    // false,
    // localMember.getConnector().a,
    // localMember.getConnector().b);
  }

  public boolean isStarted() {
    return started;
  }

  public SimpleString getName() {
    return name;
  }

  public String getNodeID() {
    return nodeManager.getNodeId().toString();
  }

  public HornetQServer getServer() {
    return server;
  }

  public boolean isNodeActive(String nodeId) {
    MessageFlowRecord rec = records.get(nodeId);
    if (rec == null) {
      return false;
    }
    return rec.getBridge().isConnected();
  }

  public Map<String, String> getNodes() {
    synchronized (recordsGuard) {
      Map<String, String> nodes = new HashMap<String, String>();
      for (Entry<String, MessageFlowRecord> entry : records.entrySet()) {
        RemotingConnection fwdConnection = entry.getValue().getBridge().getForwardingConnection();
        if (fwdConnection != null) {
          nodes.put(entry.getKey(), fwdConnection.getRemoteAddress());
        }
      }
      return nodes;
    }
  }

  private synchronized void activate() throws Exception {
    if (!started) {
      return;
    }

    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(
          "Activating cluster connection nodeID="
              + nodeManager.getNodeId()
              + " for server="
              + this.server);
    }

    liveNotifier = new LiveNotifier();
    liveNotifier.updateAsLive();
    liveNotifier.schedule();

    serverLocator = clusterConnector.createServerLocator();

    if (serverLocator != null) {

      if (!useDuplicateDetection) {
        HornetQServerLogger.LOGGER.debug(
            "DuplicateDetection is disabled, sending clustered messages blocked");
      }

      final TopologyMember currentMember = topology.getMember(manager.getNodeId());

      if (currentMember == null) {
        // sanity check only
        throw new IllegalStateException(
            "InternalError! The ClusterConnection doesn't know about its own node = " + this);
      }

      serverLocator.setNodeID(nodeManager.getNodeId().toString());
      serverLocator.setIdentity("(main-ClusterConnection::" + server.toString() + ")");
      serverLocator.setReconnectAttempts(0);
      serverLocator.setClusterConnection(true);
      serverLocator.setClusterTransportConfiguration(connector);
      serverLocator.setInitialConnectAttempts(-1);
      serverLocator.setClientFailureCheckPeriod(clientFailureCheckPeriod);
      serverLocator.setConnectionTTL(connectionTTL);
      serverLocator.setConfirmationWindowSize(confirmationWindowSize);
      // if not using duplicate detection, we will send blocked
      serverLocator.setBlockOnDurableSend(!useDuplicateDetection);
      serverLocator.setBlockOnNonDurableSend(!useDuplicateDetection);
      serverLocator.setCallTimeout(callTimeout);
      serverLocator.setCallFailoverTimeout(callFailoverTimeout);
      // No producer flow control on the bridges, as we don't want to lock the queues
      serverLocator.setProducerWindowSize(-1);

      if (retryInterval > 0) {
        this.serverLocator.setRetryInterval(retryInterval);
      }

      addClusterTopologyListener(this);

      serverLocator.setAfterConnectionInternalListener(this);

      serverLocator.start(server.getExecutorFactory().getExecutor());
    }

    if (managementService != null) {
      TypedProperties props = new TypedProperties();
      props.putSimpleStringProperty(new SimpleString("name"), name);
      Notification notification =
          new Notification(
              nodeManager.getNodeId().toString(),
              NotificationType.CLUSTER_CONNECTION_STARTED,
              props);
      HornetQServerLogger.LOGGER.debug("sending notification: " + notification);
      managementService.sendNotification(notification);
    }
  }

  public TransportConfiguration getConnector() {
    return connector;
  }

  // ClusterTopologyListener implementation
  // ------------------------------------------------------------------

  public void nodeDown(final long eventUID, final String nodeID) {
    if (stopping) {
      return;
    }
    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(
          this + " receiving nodeDown for nodeID=" + nodeID, new Exception("trace"));
    }
    if (nodeID.equals(nodeManager.getNodeId().toString())) {
      return;
    }

    // Remove the flow record for that node

    MessageFlowRecord record = records.remove(nodeID);

    if (record != null) {
      try {
        if (isTrace) {
          HornetQServerLogger.LOGGER.trace("Closing clustering record " + record);
        }
        record.close();
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorClosingFlowRecord(e);
      }
    }
  }

  @Override
  public void nodeUP(final TopologyMember topologyMember, final boolean last) {
    if (stopping) {
      return;
    }
    final String nodeID = topologyMember.getNodeId();
    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      String ClusterTestBase = "receiving nodeUP for nodeID=";
      HornetQServerLogger.LOGGER.debug(
          this + ClusterTestBase + nodeID + " connectionPair=" + topologyMember);
    }
    // discard notifications about ourselves unless its from our backup

    if (nodeID.equals(nodeManager.getNodeId().toString())) {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            this
                + "::informing about backup to itself, nodeUUID="
                + nodeManager.getNodeId()
                + ", connectorPair="
                + topologyMember
                + ", this = "
                + this);
      }
      return;
    }

    // if the node is more than 1 hop away, we do not create a bridge for direct cluster connection
    if (allowDirectConnectionsOnly && !allowableConnections.contains(topologyMember.getLive())) {
      return;
    }

    // FIXME required to prevent cluster connections w/o discovery group
    // and empty static connectors to create bridges... ulgy!
    if (serverLocator == null) {
      return;
    }
    /*we don't create bridges to backups*/
    if (topologyMember.getLive() == null) {
      if (isTrace) {
        HornetQServerLogger.LOGGER.trace(
            this
                + " ignoring call with nodeID="
                + nodeID
                + ", topologyMember="
                + topologyMember
                + ", last="
                + last);
      }
      return;
    }

    synchronized (recordsGuard) {
      try {
        MessageFlowRecord record = records.get(nodeID);

        if (record == null) {
          if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
            HornetQServerLogger.LOGGER.debug(
                this
                    + "::Creating record for nodeID="
                    + nodeID
                    + ", topologyMember="
                    + topologyMember);
          }

          // New node - create a new flow record

          final SimpleString queueName = new SimpleString("sf." + name + "." + nodeID);

          Binding queueBinding = postOffice.getBinding(queueName);

          Queue queue;

          if (queueBinding != null) {
            queue = (Queue) queueBinding.getBindable();
          } else {
            // Add binding in storage so the queue will get reloaded on startup and we can find it -
            // it's never
            // actually routed to at that address though
            queue = server.createQueue(queueName, queueName, null, true, false);
          }

          createNewRecord(
              topologyMember.getUniqueEventID(),
              nodeID,
              topologyMember.getLive(),
              queueName,
              queue,
              true);
        } else {
          if (isTrace) {
            HornetQServerLogger.LOGGER.trace(
                this
                    + " ignored nodeUp record for "
                    + topologyMember
                    + " on nodeID="
                    + nodeID
                    + " as the record already existed");
          }
        }
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorUpdatingTopology(e);
      }
    }
  }

  public synchronized void informClusterOfBackup() {
    String nodeID = server.getNodeID().toString();

    TopologyMemberImpl localMember = new TopologyMemberImpl(nodeID, null, null, connector);

    topology.updateAsLive(nodeID, localMember);
  }

  private void createNewRecord(
      final long eventUID,
      final String targetNodeID,
      final TransportConfiguration connector,
      final SimpleString queueName,
      final Queue queue,
      final boolean start)
      throws Exception {
    String nodeId;

    synchronized (this) {
      if (!started) {
        return;
      }

      if (serverLocator == null) {
        return;
      }

      nodeId = serverLocator.getNodeID();
    }

    final ServerLocatorInternal targetLocator = new ServerLocatorImpl(topology, true, connector);

    targetLocator.setReconnectAttempts(0);

    targetLocator.setInitialConnectAttempts(0);
    targetLocator.setClientFailureCheckPeriod(clientFailureCheckPeriod);
    targetLocator.setConnectionTTL(connectionTTL);
    targetLocator.setInitialConnectAttempts(0);

    targetLocator.setConfirmationWindowSize(confirmationWindowSize);
    targetLocator.setBlockOnDurableSend(!useDuplicateDetection);
    targetLocator.setBlockOnNonDurableSend(!useDuplicateDetection);

    targetLocator.setRetryInterval(retryInterval);
    targetLocator.setMaxRetryInterval(maxRetryInterval);
    targetLocator.setRetryIntervalMultiplier(retryIntervalMultiplier);
    targetLocator.setMinLargeMessageSize(minLargeMessageSize);

    // No producer flow control on the bridges, as we don't want to lock the queues
    targetLocator.setProducerWindowSize(-1);

    targetLocator.setAfterConnectionInternalListener(this);

    targetLocator.setNodeID(nodeId);

    targetLocator.setClusterTransportConfiguration(
        serverLocator.getClusterTransportConfiguration());

    if (retryInterval > 0) {
      targetLocator.setRetryInterval(retryInterval);
    }

    targetLocator.disableFinalizeCheck();
    targetLocator.addIncomingInterceptor(
        new IncomingInterceptorLookingForExceptionMessage(manager, executorFactory.getExecutor()));
    MessageFlowRecordImpl record =
        new MessageFlowRecordImpl(
            targetLocator, eventUID, targetNodeID, connector, queueName, queue);

    ClusterConnectionBridge bridge =
        new ClusterConnectionBridge(
            this,
            manager,
            targetLocator,
            serverLocator,
            reconnectAttempts,
            retryInterval,
            retryIntervalMultiplier,
            maxRetryInterval,
            nodeManager.getUUID(),
            record.getEventUID(),
            record.getTargetNodeID(),
            record.getQueueName(),
            record.getQueue(),
            executorFactory.getExecutor(),
            null,
            null,
            scheduledExecutor,
            null,
            useDuplicateDetection,
            clusterUser,
            clusterPassword,
            server.getStorageManager(),
            managementService.getManagementAddress(),
            managementService.getManagementNotificationAddress(),
            record,
            record.getConnector());

    targetLocator.setIdentity(
        "(Cluster-connection-bridge::" + bridge.toString() + "::" + this.toString() + ")");

    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(
          "creating record between " + this.connector + " and " + connector + bridge);
    }

    record.setBridge(bridge);

    records.put(targetNodeID, record);

    if (start) {
      bridge.start();
    }
  }

  // Inner classes
  // -----------------------------------------------------------------------------------

  private class MessageFlowRecordImpl implements MessageFlowRecord {
    private BridgeImpl bridge;

    private final long eventUID;

    private final String targetNodeID;

    private final TransportConfiguration connector;

    private final ServerLocatorInternal targetLocator;

    private final SimpleString queueName;

    private boolean disconnected = false;

    private final Queue queue;

    private final Map<SimpleString, RemoteQueueBinding> bindings =
        new HashMap<SimpleString, RemoteQueueBinding>();

    private volatile boolean isClosed = false;

    private volatile boolean firstReset = false;

    public MessageFlowRecordImpl(
        final ServerLocatorInternal targetLocator,
        final long eventUID,
        final String targetNodeID,
        final TransportConfiguration connector,
        final SimpleString queueName,
        final Queue queue) {
      this.targetLocator = targetLocator;
      this.queue = queue;
      this.targetNodeID = targetNodeID;
      this.connector = connector;
      this.queueName = queueName;
      this.eventUID = eventUID;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
      return "MessageFlowRecordImpl [nodeID="
          + targetNodeID
          + ", connector="
          + connector
          + ", queueName="
          + queueName
          + ", queue="
          + queue
          + ", isClosed="
          + isClosed
          + ", firstReset="
          + firstReset
          + "]";
    }

    public void serverDisconnected() {
      this.disconnected = true;
    }

    public String getAddress() {
      return address.toString();
    }

    /** @return the eventUID */
    public long getEventUID() {
      return eventUID;
    }

    /** @return the nodeID */
    public String getTargetNodeID() {
      return targetNodeID;
    }

    /** @return the connector */
    public TransportConfiguration getConnector() {
      return connector;
    }

    /** @return the queueName */
    public SimpleString getQueueName() {
      return queueName;
    }

    /** @return the queue */
    public Queue getQueue() {
      return queue;
    }

    public int getMaxHops() {
      return maxHops;
    }

    public void close() throws Exception {
      if (isTrace) {
        HornetQServerLogger.LOGGER.trace("Stopping bridge " + bridge);
      }

      isClosed = true;
      clearBindings();

      if (disconnected) {
        bridge.disconnect();
      }

      bridge.stop();

      bridge
          .getExecutor()
          .execute(
              new Runnable() {
                public void run() {
                  try {
                    if (disconnected) {
                      targetLocator.cleanup();
                    } else {
                      targetLocator.close();
                    }
                  } catch (Exception ignored) {
                    HornetQServerLogger.LOGGER.debug(ignored.getMessage(), ignored);
                  }
                }
              });
    }

    public boolean isClosed() {
      return isClosed;
    }

    public void reset() throws Exception {
      clearBindings();
    }

    public void setBridge(final BridgeImpl bridge) {
      this.bridge = bridge;
    }

    public Bridge getBridge() {
      return bridge;
    }

    public synchronized void onMessage(final ClientMessage message) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            "ClusterCommunication::Flow record on "
                + clusterConnector
                + " Receiving message "
                + message);
      }
      try {
        // Reset the bindings
        if (message.containsProperty(PostOfficeImpl.HDR_RESET_QUEUE_DATA)) {
          clearBindings();

          firstReset = true;

          return;
        }

        if (!firstReset) {
          return;
        }

        // TODO - optimised this by just passing int in header - but filter needs to be extended to
        // support IN with
        // a list of integers
        SimpleString type = message.getSimpleStringProperty(ManagementHelper.HDR_NOTIFICATION_TYPE);

        NotificationType ntype = NotificationType.valueOf(type.toString());

        switch (ntype) {
          case BINDING_ADDED:
            {
              doBindingAdded(message);

              break;
            }
          case BINDING_REMOVED:
            {
              doBindingRemoved(message);

              break;
            }
          case CONSUMER_CREATED:
            {
              doConsumerCreated(message);

              break;
            }
          case CONSUMER_CLOSED:
            {
              doConsumerClosed(message);

              break;
            }
          case PROPOSAL:
            {
              doProposalReceived(message);

              break;
            }
          case PROPOSAL_RESPONSE:
            {
              doProposalResponseReceived(message);

              break;
            }
          default:
            {
              throw HornetQMessageBundle.BUNDLE.invalidType(ntype);
            }
        }
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorHandlingMessage(e);
      }
    }

    /*
     * Inform the grouping handler of a proposal
     * */
    private synchronized void doProposalReceived(final ClientMessage message) throws Exception {
      if (!message.containsProperty(ManagementHelper.HDR_PROPOSAL_GROUP_ID)) {
        throw new IllegalStateException("proposal type is null");
      }

      SimpleString type = message.getSimpleStringProperty(ManagementHelper.HDR_PROPOSAL_GROUP_ID);

      SimpleString val = message.getSimpleStringProperty(ManagementHelper.HDR_PROPOSAL_VALUE);

      Integer hops = message.getIntProperty(ManagementHelper.HDR_DISTANCE);

      Response response = server.getGroupingHandler().receive(new Proposal(type, val), hops + 1);

      if (response != null) {
        server.getGroupingHandler().send(response, 0);
      }
    }

    /*
     * Inform the grouping handler of a response from a proposal
     *
     * */
    private synchronized void doProposalResponseReceived(final ClientMessage message)
        throws Exception {
      if (!message.containsProperty(ManagementHelper.HDR_PROPOSAL_GROUP_ID)) {
        throw new IllegalStateException("proposal type is null");
      }

      SimpleString type = message.getSimpleStringProperty(ManagementHelper.HDR_PROPOSAL_GROUP_ID);
      SimpleString val = message.getSimpleStringProperty(ManagementHelper.HDR_PROPOSAL_VALUE);
      SimpleString alt = message.getSimpleStringProperty(ManagementHelper.HDR_PROPOSAL_ALT_VALUE);
      Integer hops = message.getIntProperty(ManagementHelper.HDR_DISTANCE);
      Response response = new Response(type, val, alt);
      server.getGroupingHandler().proposed(response);
      server.getGroupingHandler().send(response, hops + 1);
    }

    private synchronized void clearBindings() throws Exception {
      HornetQServerLogger.LOGGER.debug(ClusterConnectionImpl.this + " clearing bindings");
      for (RemoteQueueBinding binding : new HashSet<RemoteQueueBinding>(bindings.values())) {
        removeBinding(binding.getClusterName());
      }
    }

    private synchronized void doBindingAdded(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(ClusterConnectionImpl.this + " Adding binding " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_DISTANCE)) {
        throw new IllegalStateException("distance is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_ADDRESS)) {
        throw new IllegalStateException("queueAddress is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_ROUTING_NAME)) {
        throw new IllegalStateException("routingName is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_BINDING_ID)) {
        throw new IllegalStateException("queueID is null");
      }

      Integer distance = message.getIntProperty(ManagementHelper.HDR_DISTANCE);

      SimpleString queueAddress = message.getSimpleStringProperty(ManagementHelper.HDR_ADDRESS);

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      SimpleString routingName = message.getSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME);

      SimpleString filterString =
          message.getSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING);

      Long queueID = message.getLongProperty(ManagementHelper.HDR_BINDING_ID);

      RemoteQueueBinding binding =
          new RemoteQueueBindingImpl(
              server.getStorageManager().generateUniqueID(),
              queueAddress,
              clusterName,
              routingName,
              queueID,
              filterString,
              queue,
              bridge.getName(),
              distance + 1);

      if (postOffice.getBinding(clusterName) != null) {
        // Sanity check - this means the binding has already been added via another bridge, probably
        // max
        // hops is too high
        // or there are multiple cluster connections for the same address

        HornetQServerLogger.LOGGER.remoteQueueAlreadyBoundOnClusterConnection(this, clusterName);

        return;
      }

      if (isTrace) {
        HornetQServerLogger.LOGGER.trace(
            "Adding binding " + clusterName + " into " + ClusterConnectionImpl.this);
      }

      bindings.put(clusterName, binding);

      try {
        postOffice.addBinding(binding);
      } catch (Exception ignore) {
      }

      Bindings theBindings = postOffice.getBindingsForAddress(queueAddress);

      theBindings.setRouteWhenNoConsumers(routeWhenNoConsumers);
    }

    private void doBindingRemoved(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            ClusterConnectionImpl.this + " Removing binding " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      removeBinding(clusterName);
    }

    private synchronized void removeBinding(final SimpleString clusterName) throws Exception {
      RemoteQueueBinding binding = bindings.remove(clusterName);

      if (binding == null) {
        throw new IllegalStateException("Cannot find binding for queue " + clusterName);
      }

      postOffice.removeBinding(binding.getUniqueName(), null);
    }

    private synchronized void doConsumerCreated(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            ClusterConnectionImpl.this + " Consumer created " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_DISTANCE)) {
        throw new IllegalStateException("distance is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      Integer distance = message.getIntProperty(ManagementHelper.HDR_DISTANCE);

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      message.putIntProperty(ManagementHelper.HDR_DISTANCE, distance + 1);

      SimpleString filterString =
          message.getSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING);

      RemoteQueueBinding binding = bindings.get(clusterName);

      if (binding == null) {
        throw new IllegalStateException(
            "Cannot find binding for " + clusterName + " on " + ClusterConnectionImpl.this);
      }

      binding.addConsumer(filterString);

      // Need to propagate the consumer add
      TypedProperties props = new TypedProperties();

      props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS, binding.getAddress());

      props.putSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME, clusterName);

      props.putSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME, binding.getRoutingName());

      props.putIntProperty(ManagementHelper.HDR_DISTANCE, distance + 1);

      Queue theQueue = (Queue) binding.getBindable();

      props.putIntProperty(ManagementHelper.HDR_CONSUMER_COUNT, theQueue.getConsumerCount());

      if (filterString != null) {
        props.putSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING, filterString);
      }

      Notification notification = new Notification(null, CONSUMER_CREATED, props);

      managementService.sendNotification(notification);
    }

    private synchronized void doConsumerClosed(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            ClusterConnectionImpl.this + " Consumer closed " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_DISTANCE)) {
        throw new IllegalStateException("distance is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      Integer distance = message.getIntProperty(ManagementHelper.HDR_DISTANCE);

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      message.putIntProperty(ManagementHelper.HDR_DISTANCE, distance + 1);

      SimpleString filterString =
          message.getSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING);

      RemoteQueueBinding binding = bindings.get(clusterName);

      if (binding == null) {
        throw new IllegalStateException("Cannot find binding for " + clusterName);
      }

      binding.removeConsumer(filterString);

      // Need to propagate the consumer close
      TypedProperties props = new TypedProperties();

      props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS, binding.getAddress());

      props.putSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME, clusterName);

      props.putSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME, binding.getRoutingName());

      props.putIntProperty(ManagementHelper.HDR_DISTANCE, distance + 1);

      Queue theQueue = (Queue) binding.getBindable();

      props.putIntProperty(ManagementHelper.HDR_CONSUMER_COUNT, theQueue.getConsumerCount());

      if (filterString != null) {
        props.putSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING, filterString);
      }
      Notification notification = new Notification(null, CONSUMER_CLOSED, props);

      managementService.sendNotification(notification);
    }
  }

  // for testing only
  public Map<String, MessageFlowRecord> getRecords() {
    return records;
  }

  @Override
  public String toString() {
    return "ClusterConnectionImpl@"
        + System.identityHashCode(this)
        + "[nodeUUID="
        + nodeManager.getNodeId()
        + ", connector="
        + connector
        + ", address="
        + address
        + ", server="
        + server
        + "]";
  }

  public String describe() {
    StringWriter str = new StringWriter();
    PrintWriter out = new PrintWriter(str);

    out.println(this);
    out.println("***************************************");
    out.println(name + " connected to");
    for (MessageFlowRecord messageFlow : records.values()) {
      out.println("\t Bridge = " + messageFlow.getBridge());
      out.println("\t Flow Record = " + messageFlow);
    }
    out.println("***************************************");

    return str.toString();
  }

  private interface ClusterConnector {
    ServerLocatorInternal createServerLocator();
  }

  private final class StaticClusterConnector implements ClusterConnector {
    private final TransportConfiguration[] tcConfigs;

    public StaticClusterConnector(TransportConfiguration[] tcConfigs) {
      this.tcConfigs = tcConfigs;
    }

    public ServerLocatorInternal createServerLocator() {
      if (tcConfigs != null && tcConfigs.length > 0) {
        if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
          HornetQServerLogger.LOGGER.debug(
              ClusterConnectionImpl.this
                  + "Creating a serverLocator for "
                  + Arrays.toString(tcConfigs));
        }
        ServerLocatorImpl locator = new ServerLocatorImpl(topology, true, tcConfigs);
        locator.setClusterConnection(true);
        return locator;
      }
      return null;
    }

    @Override
    public String toString() {
      return "StaticClusterConnector [tcConfigs=" + Arrays.toString(tcConfigs) + "]";
    }
  }

  private final class DiscoveryClusterConnector implements ClusterConnector {
    private final DiscoveryGroupConfiguration dg;

    public DiscoveryClusterConnector(DiscoveryGroupConfiguration dg) {
      this.dg = dg;
    }

    public ServerLocatorInternal createServerLocator() {
      return new ServerLocatorImpl(topology, true, dg);
    }
  }

  @Override
  public boolean verify(String clusterUser0, String clusterPassword0) {
    return clusterUser.equals(clusterUser0) && clusterPassword.equals(clusterPassword0);
  }

  private final class LiveNotifier implements Runnable {
    int notificationsSent = 0;

    @Override
    public void run() {
      resendLive();

      schedule();
    }

    public void schedule() {
      if (started && !stopping && notificationsSent++ < clusterNotificationAttempts) {
        scheduledExecutor.schedule(this, clusterNotificationInterval, TimeUnit.MILLISECONDS);
      }
    }

    public void updateAsLive() {
      if (!stopping && started) {
        topology.updateAsLive(
            manager.getNodeId(),
            new TopologyMemberImpl(
                manager.getNodeId(), manager.getNodeGroupName(), connector, null));
      }
    }

    public void resendLive() {
      if (!stopping && started) {
        topology.resendNode(manager.getNodeId());
      }
    }
  }
}
Exemple #16
0
  private void deployClusterConnection(final ClusterConnectionConfiguration config)
      throws Exception {
    if (config.getName() == null) {
      HornetQServerLogger.LOGGER.clusterConnectionNotUnique();

      return;
    }

    if (config.getAddress() == null) {
      HornetQServerLogger.LOGGER.clusterConnectionNoForwardAddress();

      return;
    }

    TransportConfiguration connector =
        configuration.getConnectorConfigurations().get(config.getConnectorName());

    if (connector == null) {
      HornetQServerLogger.LOGGER.clusterConnectionNoConnector(config.getConnectorName());
      return;
    }

    if (clusterConnections.containsKey(config.getName())) {
      HornetQServerLogger.LOGGER.clusterConnectionAlreadyExists(config.getConnectorName());
      return;
    }

    ClusterConnectionImpl clusterConnection;
    DiscoveryGroupConfiguration dg;

    if (config.getDiscoveryGroupName() != null) {
      dg = configuration.getDiscoveryGroupConfigurations().get(config.getDiscoveryGroupName());

      if (dg == null) {
        HornetQServerLogger.LOGGER.clusterConnectionNoDiscoveryGroup(
            config.getDiscoveryGroupName());
        return;
      }

      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this
                + " Starting a Discovery Group Cluster Connection, name="
                + config.getDiscoveryGroupName()
                + ", dg="
                + dg);
      }

      clusterConnection =
          new ClusterConnectionImpl(
              this,
              dg,
              connector,
              new SimpleString(config.getName()),
              new SimpleString(config.getAddress()),
              config.getMinLargeMessageSize(),
              config.getClientFailureCheckPeriod(),
              config.getConnectionTTL(),
              config.getRetryInterval(),
              config.getRetryIntervalMultiplier(),
              config.getMaxRetryInterval(),
              config.getReconnectAttempts(),
              config.getCallTimeout(),
              config.getCallFailoverTimeout(),
              config.isDuplicateDetection(),
              config.isForwardWhenNoConsumers(),
              config.getConfirmationWindowSize(),
              executorFactory,
              threadPool,
              server,
              postOffice,
              managementService,
              scheduledExecutor,
              config.getMaxHops(),
              nodeManager,
              backup,
              server.getConfiguration().getClusterUser(),
              server.getConfiguration().getClusterPassword(),
              config.isAllowDirectConnectionsOnly(),
              config.getClusterNotificationInterval(),
              config.getClusterNotificationAttempts());
    } else {
      TransportConfiguration[] tcConfigs =
          config.getStaticConnectors() != null
              ? connectorNameListToArray(config.getStaticConnectors())
              : null;

      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this + " defining cluster connection towards " + Arrays.toString(tcConfigs));
      }

      clusterConnection =
          new ClusterConnectionImpl(
              this,
              tcConfigs,
              connector,
              new SimpleString(config.getName()),
              new SimpleString(config.getAddress()),
              config.getMinLargeMessageSize(),
              config.getClientFailureCheckPeriod(),
              config.getConnectionTTL(),
              config.getRetryInterval(),
              config.getRetryIntervalMultiplier(),
              config.getMaxRetryInterval(),
              config.getReconnectAttempts(),
              config.getCallTimeout(),
              config.getCallFailoverTimeout(),
              config.isDuplicateDetection(),
              config.isForwardWhenNoConsumers(),
              config.getConfirmationWindowSize(),
              executorFactory,
              threadPool,
              server,
              postOffice,
              managementService,
              scheduledExecutor,
              config.getMaxHops(),
              nodeManager,
              backup,
              server.getConfiguration().getClusterUser(),
              server.getConfiguration().getClusterPassword(),
              config.isAllowDirectConnectionsOnly(),
              config.getClusterNotificationInterval(),
              config.getClusterNotificationAttempts());
    }

    if (defaultClusterConnection == null) {
      defaultClusterConnection = clusterConnection;
    }

    managementService.registerCluster(clusterConnection, config);

    clusterConnections.put(config.getName(), clusterConnection);

    if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
      HornetQServerLogger.LOGGER.trace(
          "ClusterConnection.start at " + clusterConnection, new Exception("trace"));
    }
  }
    private synchronized void doBindingAdded(final ClientMessage message) throws Exception {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(ClusterConnectionImpl.this + " Adding binding " + message);
      }
      if (!message.containsProperty(ManagementHelper.HDR_DISTANCE)) {
        throw new IllegalStateException("distance is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_ADDRESS)) {
        throw new IllegalStateException("queueAddress is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_CLUSTER_NAME)) {
        throw new IllegalStateException("clusterName is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_ROUTING_NAME)) {
        throw new IllegalStateException("routingName is null");
      }

      if (!message.containsProperty(ManagementHelper.HDR_BINDING_ID)) {
        throw new IllegalStateException("queueID is null");
      }

      Integer distance = message.getIntProperty(ManagementHelper.HDR_DISTANCE);

      SimpleString queueAddress = message.getSimpleStringProperty(ManagementHelper.HDR_ADDRESS);

      SimpleString clusterName = message.getSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME);

      SimpleString routingName = message.getSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME);

      SimpleString filterString =
          message.getSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING);

      Long queueID = message.getLongProperty(ManagementHelper.HDR_BINDING_ID);

      RemoteQueueBinding binding =
          new RemoteQueueBindingImpl(
              server.getStorageManager().generateUniqueID(),
              queueAddress,
              clusterName,
              routingName,
              queueID,
              filterString,
              queue,
              bridge.getName(),
              distance + 1);

      if (postOffice.getBinding(clusterName) != null) {
        // Sanity check - this means the binding has already been added via another bridge, probably
        // max
        // hops is too high
        // or there are multiple cluster connections for the same address

        HornetQServerLogger.LOGGER.remoteQueueAlreadyBoundOnClusterConnection(this, clusterName);

        return;
      }

      if (isTrace) {
        HornetQServerLogger.LOGGER.trace(
            "Adding binding " + clusterName + " into " + ClusterConnectionImpl.this);
      }

      bindings.put(clusterName, binding);

      try {
        postOffice.addBinding(binding);
      } catch (Exception ignore) {
      }

      Bindings theBindings = postOffice.getBindingsForAddress(queueAddress);

      theBindings.setRouteWhenNoConsumers(routeWhenNoConsumers);
    }
/**
 * @author <a href="mailto:[email protected]">Jeff Mesnil</a>
 * @author <a href="mailto:[email protected]">Andy Taylor</a>
 * @author <a href="mailto:[email protected]">Tim Fox</a>
 */
public class RemotingServiceImpl implements RemotingService, ConnectionLifeCycleListener {
  // Constants -----------------------------------------------------

  private static final boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();

  public static final long CONNECTION_TTL_CHECK_INTERVAL = 2000;

  // Attributes ----------------------------------------------------

  private volatile boolean started = false;

  private final Set<TransportConfiguration> acceptorsConfig;

  private final List<Interceptor> incomingInterceptors = new CopyOnWriteArrayList<Interceptor>();

  private final List<Interceptor> outgoingInterceptors = new CopyOnWriteArrayList<Interceptor>();

  private final Set<Acceptor> acceptors = new HashSet<Acceptor>();

  private final Map<Object, ConnectionEntry> connections =
      new ConcurrentHashMap<Object, ConnectionEntry>();

  private final HornetQServer server;

  private final ManagementService managementService;

  private ExecutorService threadPool;

  private final ScheduledExecutorService scheduledThreadPool;

  private FailureCheckAndFlushThread failureCheckAndFlushThread;

  private final ClusterManager clusterManager;

  private final Map<String, ProtocolManager> protocolMap = new ConcurrentHashMap();

  private HornetQPrincipal defaultInvmSecurityPrincipal;

  // Static --------------------------------------------------------

  // Constructors --------------------------------------------------

  public RemotingServiceImpl(
      final ClusterManager clusterManager,
      final Configuration config,
      final HornetQServer server,
      final ManagementService managementService,
      final ScheduledExecutorService scheduledThreadPool,
      List<ProtocolManagerFactory> protocolManagerFactories) {
    acceptorsConfig = config.getAcceptorConfigurations();

    this.server = server;

    this.clusterManager = clusterManager;

    for (String interceptorClass : config.getIncomingInterceptorClassNames()) {
      try {
        incomingInterceptors.add((Interceptor) safeInitNewInstance(interceptorClass));
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorCreatingRemotingInterceptor(e, interceptorClass);
      }
    }

    for (String interceptorClass : config.getOutgoingInterceptorClassNames()) {
      try {
        outgoingInterceptors.add((Interceptor) safeInitNewInstance(interceptorClass));
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorCreatingRemotingInterceptor(e, interceptorClass);
      }
    }
    this.managementService = managementService;

    this.scheduledThreadPool = scheduledThreadPool;

    CoreProtocolManagerFactory coreProtocolManagerFactory = new CoreProtocolManagerFactory();
    // i know there is only 1
    HornetQServerLogger.LOGGER.addingProtocolSupport(coreProtocolManagerFactory.getProtocols()[0]);
    this.protocolMap.put(
        coreProtocolManagerFactory.getProtocols()[0],
        coreProtocolManagerFactory.createProtocolManager(
            server, incomingInterceptors, outgoingInterceptors));

    if (config.isResolveProtocols()) {
      ServiceLoader<ProtocolManagerFactory> serviceLoader =
          ServiceLoader.load(ProtocolManagerFactory.class, this.getClass().getClassLoader());
      if (serviceLoader != null) {
        for (ProtocolManagerFactory next : serviceLoader) {
          String[] protocols = next.getProtocols();
          for (String protocol : protocols) {
            HornetQServerLogger.LOGGER.addingProtocolSupport(protocol);
            protocolMap.put(
                protocol,
                next.createProtocolManager(server, incomingInterceptors, outgoingInterceptors));
          }
        }
      }
    }

    if (protocolManagerFactories != null) {
      for (ProtocolManagerFactory protocolManagerFactory : protocolManagerFactories) {
        String[] protocols = protocolManagerFactory.getProtocols();
        for (String protocol : protocols) {
          HornetQServerLogger.LOGGER.addingProtocolSupport(protocol);
          protocolMap.put(
              protocol,
              protocolManagerFactory.createProtocolManager(
                  server, incomingInterceptors, outgoingInterceptors));
        }
      }
    }
  }

  // RemotingService implementation -------------------------------

  public synchronized void start() throws Exception {
    if (started) {
      return;
    }

    ClassLoader tccl =
        AccessController.doPrivileged(
            new PrivilegedAction<ClassLoader>() {
              public ClassLoader run() {
                return Thread.currentThread().getContextClassLoader();
              }
            });

    // The remoting service maintains it's own thread pool for handling remoting traffic
    // If OIO each connection will have it's own thread
    // If NIO these are capped at nio-remoting-threads which defaults to num cores * 3
    // This needs to be a different thread pool to the main thread pool especially for OIO where we
    // may need
    // to support many hundreds of connections, but the main thread pool must be kept small for
    // better performance

    ThreadFactory tFactory =
        new HornetQThreadFactory(
            "HornetQ-remoting-threads-" + server.toString() + "-" + System.identityHashCode(this),
            false,
            tccl);

    threadPool = Executors.newCachedThreadPool(tFactory);

    ClassLoader loader = Thread.currentThread().getContextClassLoader();

    for (TransportConfiguration info : acceptorsConfig) {
      try {
        Class<?> clazz = loader.loadClass(info.getFactoryClassName());

        AcceptorFactory factory = (AcceptorFactory) clazz.newInstance();

        // Check valid properties

        if (info.getParams() != null) {
          Set<String> invalid =
              ConfigurationHelper.checkKeys(
                  factory.getAllowableProperties(), info.getParams().keySet());

          if (!invalid.isEmpty()) {
            HornetQServerLogger.LOGGER.invalidAcceptorKeys(
                ConfigurationHelper.stringSetToCommaListString(invalid));

            continue;
          }
        }

        String protocol =
            ConfigurationHelper.getStringProperty(
                TransportConstants.PROTOCOL_PROP_NAME,
                TransportConstants.DEFAULT_PROTOCOL,
                info.getParams());

        ProtocolManager manager = protocolMap.get(protocol);
        if (manager == null) {
          throw HornetQMessageBundle.BUNDLE.noProtocolManagerFound(protocol);
        }
        ClusterConnection clusterConnection = lookupClusterConnection(info);

        Acceptor acceptor =
            factory.createAcceptor(
                clusterConnection,
                info.getParams(),
                new DelegatingBufferHandler(),
                manager,
                this,
                threadPool,
                scheduledThreadPool,
                manager);

        if (defaultInvmSecurityPrincipal != null && acceptor.isUnsecurable()) {
          acceptor.setDefaultHornetQPrincipal(defaultInvmSecurityPrincipal);
        }

        acceptors.add(acceptor);

        if (managementService != null) {
          acceptor.setNotificationService(managementService);

          managementService.registerAcceptor(acceptor, info);
        }
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorCreatingAcceptor(e, info.getFactoryClassName());
      }
    }

    for (Acceptor a : acceptors) {
      a.start();
    }

    // This thread checks connections that need to be closed, and also flushes confirmations
    failureCheckAndFlushThread =
        new FailureCheckAndFlushThread(RemotingServiceImpl.CONNECTION_TTL_CHECK_INTERVAL);

    failureCheckAndFlushThread.start();

    started = true;
  }

  public synchronized void allowInvmSecurityOverride(HornetQPrincipal principal) {
    defaultInvmSecurityPrincipal = principal;
    for (Acceptor acceptor : acceptors) {
      if (acceptor.isUnsecurable()) {
        acceptor.setDefaultHornetQPrincipal(principal);
      }
    }
  }

  public synchronized void freeze(final CoreRemotingConnection connectionToKeepOpen) {
    if (!started) return;
    failureCheckAndFlushThread.close(false);

    for (Acceptor acceptor : acceptors) {
      try {
        acceptor.pause();
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorStoppingAcceptor();
      }
    }
    HashMap<Object, ConnectionEntry> connectionEntries =
        new HashMap<Object, ConnectionEntry>(connections);

    // Now we ensure that no connections will process any more packets after this method is
    // complete then send a disconnect packet
    for (Entry<Object, ConnectionEntry> entry : connectionEntries.entrySet()) {
      RemotingConnection conn = entry.getValue().connection;

      if (conn.equals(connectionToKeepOpen)) continue;

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace("Sending connection.disconnection packet to " + conn);
      }

      if (!conn.isClient()) {
        conn.disconnect(false);
        connections.remove(entry.getKey());
      }
    }
  }

  public void stop(final boolean criticalError) throws Exception {
    if (!started) {
      return;
    }

    failureCheckAndFlushThread.close(criticalError);

    // We need to stop them accepting first so no new connections are accepted after we send the
    // disconnect message
    for (Acceptor acceptor : acceptors) {
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug("Pausing acceptor " + acceptor);
      }
      acceptor.pause();
    }

    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug("Sending disconnect on live connections");
    }

    HashSet<ConnectionEntry> connectionEntries = new HashSet<ConnectionEntry>(connections.values());

    // Now we ensure that no connections will process any more packets after this method is complete
    // then send a disconnect packet
    for (ConnectionEntry entry : connectionEntries) {
      RemotingConnection conn = entry.connection;

      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace("Sending connection.disconnection packet to " + conn);
      }

      conn.disconnect(criticalError);
    }

    for (Acceptor acceptor : acceptors) {
      acceptor.stop();
    }

    acceptors.clear();

    connections.clear();

    if (managementService != null) {
      managementService.unregisterAcceptors();
    }

    threadPool.shutdown();

    if (!criticalError) {
      boolean ok = threadPool.awaitTermination(10000, TimeUnit.MILLISECONDS);

      if (!ok) {
        HornetQServerLogger.LOGGER.timeoutRemotingThreadPool();
      }
    }

    started = false;
  }

  public boolean isStarted() {
    return started;
  }

  public RemotingConnection removeConnection(final Object remotingConnectionID) {
    ConnectionEntry entry = connections.remove(remotingConnectionID);

    if (entry != null) {
      return entry.connection;
    } else {
      HornetQServerLogger.LOGGER.errorRemovingConnection();

      return null;
    }
  }

  public synchronized Set<RemotingConnection> getConnections() {
    Set<RemotingConnection> conns = new HashSet<RemotingConnection>();

    for (ConnectionEntry entry : connections.values()) {
      conns.add(entry.connection);
    }

    return conns;
  }

  // ConnectionLifeCycleListener implementation -----------------------------------

  private ProtocolManager getProtocolManager(String protocol) {
    return protocolMap.get(protocol);
  }

  public void connectionCreated(
      final HornetQComponent component, final Connection connection, final String protocol) {
    if (server == null) {
      throw new IllegalStateException(
          "Unable to create connection, server hasn't finished starting up");
    }

    ProtocolManager pmgr = this.getProtocolManager(protocol.toString());

    if (pmgr == null) {
      throw HornetQMessageBundle.BUNDLE.unknownProtocol(protocol);
    }

    ConnectionEntry entry = pmgr.createConnectionEntry((Acceptor) component, connection);

    if (isTrace) {
      HornetQServerLogger.LOGGER.trace("Connection created " + connection);
    }

    connections.put(connection.getID(), entry);
  }

  public void connectionDestroyed(final Object connectionID) {

    if (isTrace) {
      HornetQServerLogger.LOGGER.trace(
          "Connection removed " + connectionID + " from server " + this.server,
          new Exception("trace"));
    }

    ConnectionEntry conn = connections.get(connectionID);

    if (conn != null) {
      // Bit of a hack - find a better way to do this

      List<FailureListener> failureListeners = conn.connection.getFailureListeners();

      boolean empty = true;

      for (FailureListener listener : failureListeners) {
        if (listener instanceof ServerSessionImpl) {
          empty = false;

          break;
        }
      }

      // We only destroy the connection if the connection has no sessions attached to it
      // Otherwise it means the connection has died without the sessions being closed first
      // so we need to keep them for ttl, in case re-attachment occurs
      if (empty) {
        connections.remove(connectionID);

        conn.connection.destroy();
      }
    }
  }

  public void connectionException(final Object connectionID, final HornetQException me) {
    // We DO NOT call fail on connection exception, otherwise in event of real connection failure,
    // the
    // connection will be failed, the session will be closed and won't be able to reconnect

    // E.g. if live server fails, then this handler wil be called on backup server for the server
    // side replicating connection.
    // If the connection fail() is called then the sessions on the backup will get closed.

    // Connections should only fail when TTL is exceeded
  }

  public void connectionReadyForWrites(final Object connectionID, final boolean ready) {}

  @Override
  public void addIncomingInterceptor(final Interceptor interceptor) {
    incomingInterceptors.add(interceptor);
  }

  @Override
  public boolean removeIncomingInterceptor(final Interceptor interceptor) {
    return incomingInterceptors.remove(interceptor);
  }

  @Override
  public void addOutgoingInterceptor(final Interceptor interceptor) {
    outgoingInterceptors.add(interceptor);
  }

  @Override
  public boolean removeOutgoingInterceptor(final Interceptor interceptor) {
    return outgoingInterceptors.remove(interceptor);
  }

  private ClusterConnection lookupClusterConnection(TransportConfiguration acceptorConfig) {
    String clusterConnectionName =
        (String)
            acceptorConfig
                .getParams()
                .get(org.hornetq.core.remoting.impl.netty.TransportConstants.CLUSTER_CONNECTION);

    ClusterConnection clusterConnection = null;
    if (clusterConnectionName != null) {
      clusterConnection = clusterManager.getClusterConnection(clusterConnectionName);
    }

    // if not found we will still use the default name, even if a name was provided
    if (clusterConnection == null) {
      clusterConnection = clusterManager.getDefaultConnection(acceptorConfig);
    }

    return clusterConnection;
  }

  // Inner classes -------------------------------------------------

  private final class DelegatingBufferHandler implements BufferHandler {
    public void bufferReceived(final Object connectionID, final HornetQBuffer buffer) {
      ConnectionEntry conn = connections.get(connectionID);

      if (conn != null) {
        conn.connection.bufferReceived(connectionID, buffer);
      } else {
        if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
          HornetQServerLogger.LOGGER.trace(
              "ConnectionID = " + connectionID + " was already closed, so ignoring packet");
        }
      }
    }
  }

  private final class FailureCheckAndFlushThread extends Thread {
    private final long pauseInterval;

    private volatile boolean closed;
    private final CountDownLatch latch = new CountDownLatch(1);

    FailureCheckAndFlushThread(final long pauseInterval) {
      super("hornetq-failure-check-thread");

      this.pauseInterval = pauseInterval;
    }

    public void close(final boolean criticalError) {
      closed = true;

      latch.countDown();

      if (!criticalError) {
        try {
          join();
        } catch (InterruptedException e) {
          throw new HornetQInterruptedException(e);
        }
      }
    }

    @Override
    public void run() {
      while (!closed) {
        try {
          long now = System.currentTimeMillis();

          Set<Object> idsToRemove = new HashSet<Object>();

          for (ConnectionEntry entry : connections.values()) {
            RemotingConnection conn = entry.connection;

            boolean flush = true;

            if (entry.ttl != -1) {
              if (!conn.checkDataReceived()) {
                if (now >= entry.lastCheck + entry.ttl) {
                  idsToRemove.add(conn.getID());

                  flush = false;
                }
              } else {
                entry.lastCheck = now;
              }
            }

            if (flush) {
              conn.flush();
            }
          }

          for (Object id : idsToRemove) {
            RemotingConnection conn = removeConnection(id);
            if (conn != null) {
              conn.fail(HornetQMessageBundle.BUNDLE.clientExited(conn.getRemoteAddress()));
            }
          }

          if (latch.await(pauseInterval, TimeUnit.MILLISECONDS)) return;
        } catch (Throwable e) {
          HornetQServerLogger.LOGGER.errorOnFailureCheck(e);
        }
      }
    }
  }

  private static Object safeInitNewInstance(final String className) {
    return AccessController.doPrivileged(
        new PrivilegedAction<Object>() {
          public Object run() {
            return ClassloadingUtil.newInstanceFromClassLoader(className);
          }
        });
  }
}
  public void cleanup() {
    ArrayList<Page> depagedPages = new ArrayList<Page>();

    while (true) {
      if (pagingStore.lock(100)) {
        break;
      }
      if (!pagingStore.isStarted()) return;
    }

    synchronized (this) {
      try {
        if (!pagingStore.isStarted()) {
          return;
        }

        if (pagingStore.getNumberOfPages() == 0) {
          return;
        }

        if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
          HornetQServerLogger.LOGGER.debug(
              "Asserting cleanup for address " + this.pagingStore.getAddress());
        }

        ArrayList<PageSubscription> cursorList = cloneSubscriptions();

        long minPage = checkMinPage(cursorList);

        // if the current page is being written...
        // on that case we need to move to verify it in a different way
        if (minPage == pagingStore.getCurrentWritingPage()
            && pagingStore.getCurrentPage().getNumberOfMessages() > 0) {
          boolean complete = true;

          for (PageSubscription cursor : cursorList) {
            if (!cursor.isComplete(minPage)) {
              if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
                HornetQServerLogger.LOGGER.debug(
                    "Cursor " + cursor + " was considered incomplete at page " + minPage);
              }

              complete = false;
              break;
            } else {
              if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
                HornetQServerLogger.LOGGER.debug(
                    "Cursor " + cursor + "was considered **complete** at page " + minPage);
              }
            }
          }

          if (!pagingStore.isStarted()) {
            return;
          }

          // All the pages on the cursor are complete.. so we will cleanup everything and store a
          // bookmark
          if (complete) {

            if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
              HornetQServerLogger.LOGGER.debug(
                  "Address "
                      + pagingStore.getAddress()
                      + " is leaving page mode as all messages are consumed and acknowledged from the page store");
            }

            pagingStore.forceAnotherPage();

            Page currentPage = pagingStore.getCurrentPage();

            storeBookmark(cursorList, currentPage);

            pagingStore.stopPaging();
          }
        }

        for (long i = pagingStore.getFirstPage(); i < minPage; i++) {
          Page page = pagingStore.depage();
          if (page == null) {
            break;
          }
          depagedPages.add(page);
        }

        if (pagingStore.getNumberOfPages() == 0
            || pagingStore.getNumberOfPages() == 1
                && pagingStore.getCurrentPage().getNumberOfMessages() == 0) {
          pagingStore.stopPaging();
        } else {
          if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
            HornetQServerLogger.LOGGER.trace(
                "Couldn't cleanup page on address "
                    + this.pagingStore.getAddress()
                    + " as numberOfPages == "
                    + pagingStore.getNumberOfPages()
                    + " and currentPage.numberOfMessages = "
                    + pagingStore.getCurrentPage().getNumberOfMessages());
          }
        }
      } catch (Exception ex) {
        HornetQServerLogger.LOGGER.problemCleaningPageAddress(ex, pagingStore.getAddress());
        return;
      } finally {
        pagingStore.unlock();
      }
    }

    try {
      for (Page depagedPage : depagedPages) {
        PageCache cache;
        PagedMessage[] pgdMessages;
        synchronized (softCache) {
          cache = softCache.get((long) depagedPage.getPageId());
        }

        if (isTrace) {
          HornetQServerLogger.LOGGER.trace(
              "Removing page " + depagedPage.getPageId() + " from page-cache");
        }

        if (cache == null) {
          // The page is not on cache any more
          // We need to read the page-file before deleting it
          // to make sure we remove any large-messages pending
          storageManager.beforePageRead();

          List<PagedMessage> pgdMessagesList = null;
          try {
            depagedPage.open();
            pgdMessagesList = depagedPage.read(storageManager);
          } finally {
            try {
              depagedPage.close();
            } catch (Exception e) {
            }

            storageManager.afterPageRead();
          }
          depagedPage.close();
          pgdMessages = pgdMessagesList.toArray(new PagedMessage[pgdMessagesList.size()]);
        } else {
          pgdMessages = cache.getMessages();
        }

        depagedPage.delete(pgdMessages);
        onDeletePage(depagedPage);

        synchronized (softCache) {
          softCache.remove((long) depagedPage.getPageId());
        }
      }
    } catch (Exception ex) {
      HornetQServerLogger.LOGGER.problemCleaningPageAddress(ex, pagingStore.getAddress());
      return;
    }
  }
/**
 * A PageProviderIMpl
 *
 * <p>TODO: this may be moved entirely into PagingStore as there's an one-to-one relationship here
 * However I want to keep this isolated as much as possible during development
 *
 * @author <a href="mailto:[email protected]">Clebert Suconic</a>
 */
public class PageCursorProviderImpl implements PageCursorProvider {
  // Constants -----------------------------------------------------

  boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();

  // Attributes ----------------------------------------------------

  /** As an optimization, avoid subsequent schedules as they are unnecessary */
  private final AtomicInteger scheduledCleanup = new AtomicInteger(0);

  private volatile boolean cleanupEnabled = true;

  private final PagingStore pagingStore;

  private final StorageManager storageManager;

  // This is the same executor used at the PageStoreImpl. One Executor per pageStore
  private final Executor executor;

  private final SoftValueHashMap<Long, PageCache> softCache;

  private final ConcurrentMap<Long, PageSubscription> activeCursors =
      new ConcurrentHashMap<Long, PageSubscription>();

  // Static --------------------------------------------------------

  // Constructors --------------------------------------------------

  public PageCursorProviderImpl(
      final PagingStore pagingStore,
      final StorageManager storageManager,
      final Executor executor,
      final int maxCacheSize) {
    this.pagingStore = pagingStore;
    this.storageManager = storageManager;
    this.executor = executor;
    this.softCache = new SoftValueHashMap<Long, PageCache>(maxCacheSize);
  }

  // Public --------------------------------------------------------

  public synchronized PageSubscription createSubscription(
      long cursorID, Filter filter, boolean persistent) {
    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(
          this.pagingStore.getAddress()
              + " creating subscription "
              + cursorID
              + " with filter "
              + filter,
          new Exception("trace"));
    }

    if (activeCursors.containsKey(cursorID)) {
      throw new IllegalStateException("Cursor " + cursorID + " had already been created");
    }

    PageSubscription activeCursor =
        new PageSubscriptionImpl(
            this, pagingStore, storageManager, executor, filter, cursorID, persistent);
    activeCursors.put(cursorID, activeCursor);
    return activeCursor;
  }

  public synchronized PageSubscription getSubscription(long cursorID) {
    return activeCursors.get(cursorID);
  }

  public PagedMessage getMessage(final PagePosition pos) {
    PageCache cache = getPageCache(pos.getPageNr());

    if (cache == null || pos.getMessageNr() >= cache.getNumberOfMessages()) {
      // sanity check, this should never happen unless there's a bug
      throw new IllegalStateException("Invalid messageNumber passed = " + pos + " on " + cache);
    }

    return cache.getMessage(pos.getMessageNr());
  }

  public PagedReference newReference(
      final PagePosition pos, final PagedMessage msg, final PageSubscription subscription) {
    return new PagedReferenceImpl(pos, msg, subscription);
  }

  public PageCache getPageCache(final long pageId) {
    try {
      boolean needToRead = false;
      PageCache cache = null;
      synchronized (softCache) {
        if (pageId > pagingStore.getCurrentWritingPage()) {
          return null;
        }

        cache = softCache.get(pageId);
        if (cache == null) {
          if (!pagingStore.checkPageFileExists((int) pageId)) {
            return null;
          }

          cache = createPageCache(pageId);
          needToRead = true;
          // anyone reading from this cache will have to wait reading to finish first
          // we also want only one thread reading this cache
          cache.lock();
          if (isTrace) {
            HornetQServerLogger.LOGGER.trace(
                "adding " + pageId + " into cursor = " + this.pagingStore.getAddress());
          }
          softCache.put(pageId, cache);
        }
      }

      // Reading is done outside of the synchronized block, however
      // the page stays locked until the entire reading is finished
      if (needToRead) {
        Page page = null;
        try {
          page = pagingStore.createPage((int) pageId);

          storageManager.beforePageRead();
          page.open();

          List<PagedMessage> pgdMessages = page.read(storageManager);
          cache.setMessages(pgdMessages.toArray(new PagedMessage[pgdMessages.size()]));
        } finally {
          try {
            if (page != null) {
              page.close();
            }
          } catch (Throwable ignored) {
          }
          storageManager.afterPageRead();
          cache.unlock();
        }
      }

      return cache;
    } catch (Exception e) {
      throw new RuntimeException(
          "Couldn't complete paging due to an IO Exception on Paging - " + e.getMessage(), e);
    }
  }

  public void addPageCache(PageCache cache) {
    synchronized (softCache) {
      softCache.put(cache.getPageId(), cache);
    }
  }

  public void setCacheMaxSize(final int size) {
    softCache.setMaxElements(size);
  }

  public int getCacheSize() {
    synchronized (softCache) {
      return softCache.size();
    }
  }

  public void clearCache() {
    synchronized (softCache) {
      softCache.clear();
    }
  }

  public void processReload() throws Exception {
    Collection<PageSubscription> cursorList = this.activeCursors.values();
    for (PageSubscription cursor : cursorList) {
      cursor.processReload();
    }

    if (!cursorList.isEmpty()) {
      // https://issues.jboss.org/browse/JBPAPP-10338 if you ack out of order,
      // the min page could be beyond the first page.
      // we have to reload any previously acked message
      long cursorsMinPage = checkMinPage(cursorList);

      // checkMinPage will return MaxValue if there aren't any pages or any cursors
      if (cursorsMinPage != Long.MAX_VALUE) {
        for (long startPage = pagingStore.getFirstPage(); startPage < cursorsMinPage; startPage++) {
          for (PageSubscription cursor : cursorList) {
            cursor.reloadPageInfo(startPage);
          }
        }
      }
    }

    cleanup();
  }

  public void stop() {
    for (PageSubscription cursor : activeCursors.values()) {
      cursor.stop();
    }

    waitForFuture();
  }

  private void waitForFuture() {
    FutureLatch future = new FutureLatch();

    executor.execute(future);

    while (!future.await(10000)) {
      HornetQServerLogger.LOGGER.timedOutStoppingPagingCursor(future, executor);
    }
  }

  public void flushExecutors() {
    for (PageSubscription cursor : activeCursors.values()) {
      cursor.flushExecutors();
    }
    waitForFuture();
  }

  public void close(PageSubscription cursor) {
    activeCursors.remove(cursor.getId());

    scheduleCleanup();
  }

  @Override
  public void scheduleCleanup() {

    if (!cleanupEnabled || scheduledCleanup.intValue() > 2) {
      // Scheduled cleanup was already scheduled before.. never mind!
      // or we have cleanup disabled
      return;
    }

    scheduledCleanup.incrementAndGet();

    executor.execute(
        new Runnable() {
          public void run() {
            storageManager.setContext(storageManager.newSingleThreadContext());
            try {
              cleanup();
            } finally {
              storageManager.clearContext();
              scheduledCleanup.decrementAndGet();
            }
          }
        });
  }

  /**
   * Delete everything associated with any queue on this address. This is to be called when the
   * address is about to be released from paging. Hence the PagingStore will be holding a write
   * lock, meaning no messages are going to be paged at this time. So, we shouldn't lock anything
   * after this method, to avoid dead locks between the writeLock and any synchronization with the
   * CursorProvider.
   */
  public void onPageModeCleared() {
    ArrayList<PageSubscription> subscriptions = cloneSubscriptions();

    Transaction tx = new TransactionImpl(storageManager);
    for (PageSubscription sub : subscriptions) {
      try {
        sub.onPageModeCleared(tx);
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.warn(
            "Error while cleaning paging on queue " + sub.getQueue().getName(), e);
      }
    }

    try {
      tx.commit();
    } catch (Exception e) {
      HornetQServerLogger.LOGGER.warn("Error while cleaning page, during the commit", e);
    }
  }

  public void disableCleanup() {
    this.cleanupEnabled = false;
  }

  public void resumeCleanup() {
    this.cleanupEnabled = true;
  }

  public void cleanup() {
    ArrayList<Page> depagedPages = new ArrayList<Page>();

    while (true) {
      if (pagingStore.lock(100)) {
        break;
      }
      if (!pagingStore.isStarted()) return;
    }

    synchronized (this) {
      try {
        if (!pagingStore.isStarted()) {
          return;
        }

        if (pagingStore.getNumberOfPages() == 0) {
          return;
        }

        if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
          HornetQServerLogger.LOGGER.debug(
              "Asserting cleanup for address " + this.pagingStore.getAddress());
        }

        ArrayList<PageSubscription> cursorList = cloneSubscriptions();

        long minPage = checkMinPage(cursorList);

        // if the current page is being written...
        // on that case we need to move to verify it in a different way
        if (minPage == pagingStore.getCurrentWritingPage()
            && pagingStore.getCurrentPage().getNumberOfMessages() > 0) {
          boolean complete = true;

          for (PageSubscription cursor : cursorList) {
            if (!cursor.isComplete(minPage)) {
              if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
                HornetQServerLogger.LOGGER.debug(
                    "Cursor " + cursor + " was considered incomplete at page " + minPage);
              }

              complete = false;
              break;
            } else {
              if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
                HornetQServerLogger.LOGGER.debug(
                    "Cursor " + cursor + "was considered **complete** at page " + minPage);
              }
            }
          }

          if (!pagingStore.isStarted()) {
            return;
          }

          // All the pages on the cursor are complete.. so we will cleanup everything and store a
          // bookmark
          if (complete) {

            if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
              HornetQServerLogger.LOGGER.debug(
                  "Address "
                      + pagingStore.getAddress()
                      + " is leaving page mode as all messages are consumed and acknowledged from the page store");
            }

            pagingStore.forceAnotherPage();

            Page currentPage = pagingStore.getCurrentPage();

            storeBookmark(cursorList, currentPage);

            pagingStore.stopPaging();
          }
        }

        for (long i = pagingStore.getFirstPage(); i < minPage; i++) {
          Page page = pagingStore.depage();
          if (page == null) {
            break;
          }
          depagedPages.add(page);
        }

        if (pagingStore.getNumberOfPages() == 0
            || pagingStore.getNumberOfPages() == 1
                && pagingStore.getCurrentPage().getNumberOfMessages() == 0) {
          pagingStore.stopPaging();
        } else {
          if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
            HornetQServerLogger.LOGGER.trace(
                "Couldn't cleanup page on address "
                    + this.pagingStore.getAddress()
                    + " as numberOfPages == "
                    + pagingStore.getNumberOfPages()
                    + " and currentPage.numberOfMessages = "
                    + pagingStore.getCurrentPage().getNumberOfMessages());
          }
        }
      } catch (Exception ex) {
        HornetQServerLogger.LOGGER.problemCleaningPageAddress(ex, pagingStore.getAddress());
        return;
      } finally {
        pagingStore.unlock();
      }
    }

    try {
      for (Page depagedPage : depagedPages) {
        PageCache cache;
        PagedMessage[] pgdMessages;
        synchronized (softCache) {
          cache = softCache.get((long) depagedPage.getPageId());
        }

        if (isTrace) {
          HornetQServerLogger.LOGGER.trace(
              "Removing page " + depagedPage.getPageId() + " from page-cache");
        }

        if (cache == null) {
          // The page is not on cache any more
          // We need to read the page-file before deleting it
          // to make sure we remove any large-messages pending
          storageManager.beforePageRead();

          List<PagedMessage> pgdMessagesList = null;
          try {
            depagedPage.open();
            pgdMessagesList = depagedPage.read(storageManager);
          } finally {
            try {
              depagedPage.close();
            } catch (Exception e) {
            }

            storageManager.afterPageRead();
          }
          depagedPage.close();
          pgdMessages = pgdMessagesList.toArray(new PagedMessage[pgdMessagesList.size()]);
        } else {
          pgdMessages = cache.getMessages();
        }

        depagedPage.delete(pgdMessages);
        onDeletePage(depagedPage);

        synchronized (softCache) {
          softCache.remove((long) depagedPage.getPageId());
        }
      }
    } catch (Exception ex) {
      HornetQServerLogger.LOGGER.problemCleaningPageAddress(ex, pagingStore.getAddress());
      return;
    }
  }

  /** @return */
  private synchronized ArrayList<PageSubscription> cloneSubscriptions() {
    ArrayList<PageSubscription> cursorList = new ArrayList<PageSubscription>();
    cursorList.addAll(activeCursors.values());
    return cursorList;
  }

  protected void onDeletePage(Page deletedPage) throws Exception {
    List<PageSubscription> subscriptions = cloneSubscriptions();
    for (PageSubscription subs : subscriptions) {
      subs.onDeletePage(deletedPage);
    }
  }

  /**
   * @param cursorList
   * @param currentPage
   * @throws Exception
   */
  protected void storeBookmark(ArrayList<PageSubscription> cursorList, Page currentPage)
      throws Exception {
    try {
      // First step: Move every cursor to the next bookmarked page (that was just created)
      for (PageSubscription cursor : cursorList) {
        cursor.confirmPosition(new PagePositionImpl(currentPage.getPageId(), -1));
      }

      while (!storageManager.waitOnOperations(5000)) {
        HornetQServerLogger.LOGGER.problemCompletingOperations(storageManager.getContext());
      }
    } finally {
      for (PageSubscription cursor : cursorList) {
        cursor.enableAutoCleanup();
      }
    }
  }

  public void printDebug() {
    System.out.println("Debug information for PageCursorProviderImpl:");
    for (PageCache cache : softCache.values()) {
      System.out.println("Cache " + cache);
    }
  }

  // Package protected ---------------------------------------------

  // Protected -----------------------------------------------------

  /* Protected as we may let test cases to instrument the test */
  protected PageCacheImpl createPageCache(final long pageId) throws Exception {
    return new PageCacheImpl(pagingStore.createPage((int) pageId));
  }

  // Private -------------------------------------------------------

  /** This method is synchronized because we want it to be atomic with the cursors being used */
  private long checkMinPage(Collection<PageSubscription> cursorList) {
    long minPage = Long.MAX_VALUE;

    for (PageSubscription cursor : cursorList) {
      long firstPage = cursor.getFirstPage();
      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            this.pagingStore.getAddress()
                + " has a cursor "
                + cursor
                + " with first page="
                + firstPage);
      }

      // the cursor will return -1 if the cursor is empty
      if (firstPage >= 0 && firstPage < minPage) {
        minPage = firstPage;
      }
    }

    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(this.pagingStore.getAddress() + " has minPage=" + minPage);
    }

    return minPage;
  }

  // Inner classes -------------------------------------------------

}
  @Override
  public void nodeUP(final TopologyMember topologyMember, final boolean last) {
    if (stopping) {
      return;
    }
    final String nodeID = topologyMember.getNodeId();
    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      String ClusterTestBase = "receiving nodeUP for nodeID=";
      HornetQServerLogger.LOGGER.debug(
          this + ClusterTestBase + nodeID + " connectionPair=" + topologyMember);
    }
    // discard notifications about ourselves unless its from our backup

    if (nodeID.equals(nodeManager.getNodeId().toString())) {
      if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
        HornetQServerLogger.LOGGER.trace(
            this
                + "::informing about backup to itself, nodeUUID="
                + nodeManager.getNodeId()
                + ", connectorPair="
                + topologyMember
                + ", this = "
                + this);
      }
      return;
    }

    // if the node is more than 1 hop away, we do not create a bridge for direct cluster connection
    if (allowDirectConnectionsOnly && !allowableConnections.contains(topologyMember.getLive())) {
      return;
    }

    // FIXME required to prevent cluster connections w/o discovery group
    // and empty static connectors to create bridges... ulgy!
    if (serverLocator == null) {
      return;
    }
    /*we don't create bridges to backups*/
    if (topologyMember.getLive() == null) {
      if (isTrace) {
        HornetQServerLogger.LOGGER.trace(
            this
                + " ignoring call with nodeID="
                + nodeID
                + ", topologyMember="
                + topologyMember
                + ", last="
                + last);
      }
      return;
    }

    synchronized (recordsGuard) {
      try {
        MessageFlowRecord record = records.get(nodeID);

        if (record == null) {
          if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
            HornetQServerLogger.LOGGER.debug(
                this
                    + "::Creating record for nodeID="
                    + nodeID
                    + ", topologyMember="
                    + topologyMember);
          }

          // New node - create a new flow record

          final SimpleString queueName = new SimpleString("sf." + name + "." + nodeID);

          Binding queueBinding = postOffice.getBinding(queueName);

          Queue queue;

          if (queueBinding != null) {
            queue = (Queue) queueBinding.getBindable();
          } else {
            // Add binding in storage so the queue will get reloaded on startup and we can find it -
            // it's never
            // actually routed to at that address though
            queue = server.createQueue(queueName, queueName, null, true, false);
          }

          createNewRecord(
              topologyMember.getUniqueEventID(),
              nodeID,
              topologyMember.getLive(),
              queueName,
              queue,
              true);
        } else {
          if (isTrace) {
            HornetQServerLogger.LOGGER.trace(
                this
                    + " ignored nodeUp record for "
                    + topologyMember
                    + " on nodeID="
                    + nodeID
                    + " as the record already existed");
          }
        }
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorUpdatingTopology(e);
      }
    }
  }
/**
 * Server side Session implementation
 *
 * @author <a href="mailto:[email protected]">Tim Fox</a>
 * @author <a href="mailto:[email protected]">Clebert Suconic</a>
 * @author <a href="mailto:[email protected]">Jeff Mesnil</a>
 * @author <a href="mailto:[email protected]>Andy Taylor</a>
 */
public class ServerSessionImpl implements ServerSession, FailureListener {
  // Constants -----------------------------------------------------------------------------

  private static final boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();

  // Static -------------------------------------------------------------------------------

  // Attributes ----------------------------------------------------------------------------

  private final String username;

  private final String password;

  private final int minLargeMessageSize;

  private final boolean autoCommitSends;

  private final boolean autoCommitAcks;

  private final boolean preAcknowledge;

  private final boolean strictUpdateDeliveryCount;

  private final RemotingConnection remotingConnection;

  private final Map<Long, ServerConsumer> consumers = new ConcurrentHashMap<Long, ServerConsumer>();

  private Transaction tx;

  private final boolean xa;

  private final StorageManager storageManager;

  private final ResourceManager resourceManager;

  public final PostOffice postOffice;

  private final SecurityStore securityStore;

  private final ManagementService managementService;

  private volatile boolean started = false;

  private final Map<SimpleString, TempQueueCleanerUpper> tempQueueCleannerUppers =
      new HashMap<SimpleString, TempQueueCleanerUpper>();

  private final String name;

  private final HornetQServer server;

  private final SimpleString managementAddress;

  // The current currentLargeMessage being processed
  private volatile LargeServerMessage currentLargeMessage;

  private final RoutingContext routingContext = new RoutingContextImpl(null);

  private final SessionCallback callback;

  private volatile SimpleString defaultAddress;

  private volatile int timeoutSeconds;

  private Map<String, String> metaData;

  private final OperationContext context;

  // Session's usage should be by definition single threaded, hence it's not needed to use a
  // concurrentHashMap here
  private final Map<SimpleString, Pair<UUID, AtomicLong>> targetAddressInfos =
      new HashMap<SimpleString, Pair<UUID, AtomicLong>>();

  private final long creationTime = System.currentTimeMillis();

  // to prevent session from being closed twice.
  // this can happen when a session close from client just
  // arrives while the connection failure is detected at the
  // server. Both the request and failure listener will
  // try to close one session from different threads
  // concurrently.
  private volatile boolean closed = false;

  // Constructors ---------------------------------------------------------------------------------

  public ServerSessionImpl(
      final String name,
      final String username,
      final String password,
      final int minLargeMessageSize,
      final boolean autoCommitSends,
      final boolean autoCommitAcks,
      final boolean preAcknowledge,
      final boolean strictUpdateDeliveryCount,
      final boolean xa,
      final RemotingConnection remotingConnection,
      final StorageManager storageManager,
      final PostOffice postOffice,
      final ResourceManager resourceManager,
      final SecurityStore securityStore,
      final ManagementService managementService,
      final HornetQServer server,
      final SimpleString managementAddress,
      final SimpleString defaultAddress,
      final SessionCallback callback,
      final OperationContext context)
      throws Exception {
    this.username = username;

    this.password = password;

    this.minLargeMessageSize = minLargeMessageSize;

    this.autoCommitSends = autoCommitSends;

    this.autoCommitAcks = autoCommitAcks;

    this.preAcknowledge = preAcknowledge;

    this.remotingConnection = remotingConnection;

    this.storageManager = storageManager;

    this.postOffice = postOffice;

    this.resourceManager = resourceManager;

    this.securityStore = securityStore;

    timeoutSeconds = resourceManager.getTimeoutSeconds();
    this.xa = xa;

    this.strictUpdateDeliveryCount = strictUpdateDeliveryCount;

    this.managementService = managementService;

    this.name = name;

    this.server = server;

    this.managementAddress = managementAddress;

    this.callback = callback;

    this.defaultAddress = defaultAddress;

    remotingConnection.addFailureListener(this);
    this.context = context;
    if (!xa) {
      tx = newTransaction();
    }
  }

  // ServerSession implementation
  // ----------------------------------------------------------------------------
  /** @return the sessionContext */
  public OperationContext getSessionContext() {
    return context;
  }

  public String getUsername() {
    return username;
  }

  public String getPassword() {
    return password;
  }

  public int getMinLargeMessageSize() {
    return minLargeMessageSize;
  }

  public String getName() {
    return name;
  }

  public Object getConnectionID() {
    return remotingConnection.getID();
  }

  public Set<ServerConsumer> getServerConsumers() {
    Set<ServerConsumer> consumersClone = new HashSet<ServerConsumer>(consumers.values());
    return Collections.unmodifiableSet(consumersClone);
  }

  public void removeConsumer(final long consumerID) throws Exception {
    if (consumers.remove(consumerID) == null) {
      throw new IllegalStateException("Cannot find consumer with id " + consumerID + " to remove");
    }
  }

  private void doClose(final boolean failed) throws Exception {
    synchronized (this) {
      if (closed) return;

      if (tx != null && tx.getXid() == null) {
        // We only rollback local txs on close, not XA tx branches

        try {
          rollback(failed, false);
        } catch (Exception e) {
          HornetQServerLogger.LOGGER.warn(e.getMessage(), e);
        }
      }

      server.removeSession(name);

      remotingConnection.removeFailureListener(this);

      callback.closed();

      closed = true;
    }

    // putting closing of consumers outside the sync block
    // https://issues.jboss.org/browse/HORNETQ-1141
    Set<ServerConsumer> consumersClone = new HashSet<ServerConsumer>(consumers.values());

    for (ServerConsumer consumer : consumersClone) {
      consumer.close(failed);
    }

    consumers.clear();

    if (currentLargeMessage != null) {
      try {
        currentLargeMessage.deleteFile();
      } catch (Throwable error) {
        HornetQServerLogger.LOGGER.errorDeletingLargeMessageFile(error);
      }
    }
  }

  public void createConsumer(
      final long consumerID,
      final SimpleString queueName,
      final SimpleString filterString,
      final boolean browseOnly)
      throws Exception {
    this.createConsumer(consumerID, queueName, filterString, browseOnly, true, null);
  }

  public void createConsumer(
      final long consumerID,
      final SimpleString queueName,
      final SimpleString filterString,
      final boolean browseOnly,
      final boolean supportLargeMessage,
      final Integer credits)
      throws Exception {
    Binding binding = postOffice.getBinding(queueName);

    if (binding == null || binding.getType() != BindingType.LOCAL_QUEUE) {
      throw HornetQMessageBundle.BUNDLE.noSuchQueue(queueName);
    }

    securityStore.check(binding.getAddress(), CheckType.CONSUME, this);

    Filter filter = FilterImpl.createFilter(filterString);

    ServerConsumer consumer =
        new ServerConsumerImpl(
            consumerID,
            this,
            (QueueBinding) binding,
            filter,
            started,
            browseOnly,
            storageManager,
            callback,
            preAcknowledge,
            strictUpdateDeliveryCount,
            managementService,
            supportLargeMessage,
            credits);
    consumers.put(consumer.getID(), consumer);

    if (!browseOnly) {
      TypedProperties props = new TypedProperties();

      props.putSimpleStringProperty(ManagementHelper.HDR_ADDRESS, binding.getAddress());

      props.putSimpleStringProperty(ManagementHelper.HDR_CLUSTER_NAME, binding.getClusterName());

      props.putSimpleStringProperty(ManagementHelper.HDR_ROUTING_NAME, binding.getRoutingName());

      props.putIntProperty(ManagementHelper.HDR_DISTANCE, binding.getDistance());

      Queue theQueue = (Queue) binding.getBindable();

      props.putIntProperty(ManagementHelper.HDR_CONSUMER_COUNT, theQueue.getConsumerCount());

      // HORNETQ-946
      props.putSimpleStringProperty(
          ManagementHelper.HDR_USER, SimpleString.toSimpleString(username));

      props.putSimpleStringProperty(
          ManagementHelper.HDR_REMOTE_ADDRESS,
          SimpleString.toSimpleString(this.remotingConnection.getRemoteAddress()));

      props.putSimpleStringProperty(
          ManagementHelper.HDR_SESSION_NAME, SimpleString.toSimpleString(name));

      if (filterString != null) {
        props.putSimpleStringProperty(ManagementHelper.HDR_FILTERSTRING, filterString);
      }

      Notification notification = new Notification(null, CONSUMER_CREATED, props);

      if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
        HornetQServerLogger.LOGGER.debug(
            "Session with user="******", connection="
                + this.remotingConnection
                + " created a consumer on queue "
                + queueName
                + ", filter = "
                + filterString);
      }

      managementService.sendNotification(notification);
    }
  }

  public void createQueue(
      final SimpleString address,
      final SimpleString name,
      final SimpleString filterString,
      final boolean temporary,
      final boolean durable)
      throws Exception {
    if (durable) {
      // make sure the user has privileges to create this queue
      securityStore.check(address, CheckType.CREATE_DURABLE_QUEUE, this);
    } else {
      securityStore.check(address, CheckType.CREATE_NON_DURABLE_QUEUE, this);
    }

    Queue queue = server.createQueue(address, name, filterString, durable, temporary);

    if (temporary) {
      // Temporary queue in core simply means the queue will be deleted if
      // the remoting connection
      // dies. It does not mean it will get deleted automatically when the
      // session is closed.
      // It is up to the user to delete the queue when finished with it

      TempQueueCleanerUpper cleaner = new TempQueueCleanerUpper(server, name);

      remotingConnection.addCloseListener(cleaner);
      remotingConnection.addFailureListener(cleaner);

      tempQueueCleannerUppers.put(name, cleaner);
    }

    if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
      HornetQServerLogger.LOGGER.debug(
          "Queue "
              + name
              + " created on address "
              + name
              + " with filter="
              + filterString
              + " temporary = "
              + temporary
              + " durable="
              + durable
              + " on session user="******", connection="
              + this.remotingConnection);
    }
  }

  @Override
  public void createSharedQueue(
      final SimpleString address,
      final SimpleString name,
      boolean durable,
      final SimpleString filterString)
      throws Exception {
    securityStore.check(address, CheckType.CREATE_NON_DURABLE_QUEUE, this);

    server.createSharedQueue(address, name, filterString, durable);
  }

  public RemotingConnection getRemotingConnection() {
    return remotingConnection;
  }

  private static class TempQueueCleanerUpper implements CloseListener, FailureListener {
    private final SimpleString bindingName;

    private final HornetQServer server;

    TempQueueCleanerUpper(final HornetQServer server, final SimpleString bindingName) {
      this.server = server;

      this.bindingName = bindingName;
    }

    private void run() {
      try {
        if (HornetQServerLogger.LOGGER.isDebugEnabled()) {
          HornetQServerLogger.LOGGER.debug("deleting temporary queue " + bindingName);
        }
        try {
          server.destroyQueue(bindingName, null, false);
        } catch (HornetQException e) {
          // that's fine.. it can happen due to queue already been deleted
          HornetQServerLogger.LOGGER.debug(e.getMessage(), e);
        }
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.errorRemovingTempQueue(e, bindingName);
      }
    }

    public void connectionFailed(HornetQException exception, boolean failedOver) {
      run();
    }

    public void connectionClosed() {
      run();
    }

    @Override
    public String toString() {
      return "Temporary Cleaner for queue " + bindingName;
    }
  }

  public void deleteQueue(final SimpleString queueToDelete) throws Exception {
    Binding binding = postOffice.getBinding(queueToDelete);

    if (binding == null || binding.getType() != BindingType.LOCAL_QUEUE) {
      throw new HornetQNonExistentQueueException();
    }

    server.destroyQueue(queueToDelete, this, true);

    TempQueueCleanerUpper cleaner = this.tempQueueCleannerUppers.remove(queueToDelete);

    if (cleaner != null) {
      remotingConnection.removeCloseListener(cleaner);

      remotingConnection.removeFailureListener(cleaner);
    }
  }

  public QueueQueryResult executeQueueQuery(final SimpleString name) throws Exception {
    if (name == null) {
      throw HornetQMessageBundle.BUNDLE.queueNameIsNull();
    }

    QueueQueryResult response;

    Binding binding = postOffice.getBinding(name);

    if (binding != null && binding.getType() == BindingType.LOCAL_QUEUE) {
      Queue queue = (Queue) binding.getBindable();

      Filter filter = queue.getFilter();

      SimpleString filterString = filter == null ? null : filter.getFilterString();

      response =
          new QueueQueryResult(
              name,
              binding.getAddress(),
              queue.isDurable(),
              queue.isTemporary(),
              filterString,
              queue.getConsumerCount(),
              queue.getMessageCount(QueueImpl.DELIVERY_TIMEOUT));
    }
    // make an exception for the management address (see HORNETQ-29)
    else if (name.equals(managementAddress)) {
      response = new QueueQueryResult(name, managementAddress, true, false, null, -1, -1);
    } else {
      response = new QueueQueryResult();
    }

    return response;
  }

  public BindingQueryResult executeBindingQuery(final SimpleString address) throws Exception {
    if (address == null) {
      throw HornetQMessageBundle.BUNDLE.addressIsNull();
    }

    List<SimpleString> names = new ArrayList<SimpleString>();

    // make an exception for the management address (see HORNETQ-29)
    if (address.equals(managementAddress)) {
      return new BindingQueryResult(true, names);
    }

    Bindings bindings = postOffice.getMatchingBindings(address);

    for (Binding binding : bindings.getBindings()) {
      if (binding.getType() == BindingType.LOCAL_QUEUE
          || binding.getType() == BindingType.REMOTE_QUEUE) {
        names.add(binding.getUniqueName());
      }
    }

    return new BindingQueryResult(!names.isEmpty(), names);
  }

  public void forceConsumerDelivery(final long consumerID, final long sequence) throws Exception {
    ServerConsumer consumer = consumers.get(consumerID);

    // this would be possible if the server consumer was closed by pings/pongs.. etc
    if (consumer != null) {
      consumer.forceDelivery(sequence);
    }
  }

  public void acknowledge(final long consumerID, final long messageID) throws Exception {
    ServerConsumer consumer = consumers.get(consumerID);

    if (consumer == null) {
      throw HornetQMessageBundle.BUNDLE.consumerDoesntExist(consumerID);
    }

    if (tx != null && tx.getState() == State.ROLLEDBACK) {
      // JBPAPP-8845 - if we let stuff to be acked on a rolled back TX, we will just
      // have these messages to be stuck on the limbo until the server is restarted
      // The tx has already timed out, so we need to ack and rollback immediately
      Transaction newTX = newTransaction();
      consumer.acknowledge(autoCommitAcks, newTX, messageID);
      newTX.rollback();
    } else {
      consumer.acknowledge(autoCommitAcks, tx, messageID);
    }
  }

  public void individualAcknowledge(final long consumerID, final long messageID) throws Exception {
    ServerConsumer consumer = consumers.get(consumerID);

    if (this.xa && tx == null) {
      throw new HornetQXAException(XAException.XAER_PROTO, "Invalid transaction state");
    }

    if (tx != null && tx.getState() == State.ROLLEDBACK) {
      // JBPAPP-8845 - if we let stuff to be acked on a rolled back TX, we will just
      // have these messages to be stuck on the limbo until the server is restarted
      // The tx has already timed out, so we need to ack and rollback immediately
      Transaction newTX = newTransaction();
      consumer.individualAcknowledge(autoCommitAcks, tx, messageID);
      newTX.rollback();
    } else {
      consumer.individualAcknowledge(autoCommitAcks, tx, messageID);
    }
  }

  public void individualCancel(final long consumerID, final long messageID, boolean failed)
      throws Exception {
    ServerConsumer consumer = consumers.get(consumerID);

    if (consumer != null) {
      consumer.individualCancel(messageID, failed);
    }
  }

  public void expire(final long consumerID, final long messageID) throws Exception {
    MessageReference ref = consumers.get(consumerID).removeReferenceByID(messageID);

    if (ref != null) {
      ref.getQueue().expire(ref);
    }
  }

  public synchronized void commit() throws Exception {
    if (isTrace) {
      HornetQServerLogger.LOGGER.trace("Calling commit");
    }
    try {
      tx.commit();
    } finally {
      tx = newTransaction();
    }
  }

  public void rollback(final boolean considerLastMessageAsDelivered) throws Exception {
    rollback(false, considerLastMessageAsDelivered);
  }

  /**
   * @param clientFailed If the client has failed, we can't decrease the delivery-counts, and the
   *     close may issue a rollback
   * @param considerLastMessageAsDelivered
   * @throws Exception
   */
  private synchronized void rollback(
      final boolean clientFailed, final boolean considerLastMessageAsDelivered) throws Exception {
    if (tx == null) {
      // Might be null if XA

      tx = newTransaction();
    }

    doRollback(clientFailed, considerLastMessageAsDelivered, tx);

    if (xa) {
      tx = null;
    } else {
      tx = newTransaction();
    }
  }

  /** @return */
  private TransactionImpl newTransaction() {
    return new TransactionImpl(storageManager, timeoutSeconds);
  }

  /**
   * @param xid
   * @return
   */
  private TransactionImpl newTransaction(final Xid xid) {
    return new TransactionImpl(xid, storageManager, timeoutSeconds);
  }

  public synchronized void xaCommit(final Xid xid, final boolean onePhase) throws Exception {

    if (tx != null && tx.getXid().equals(xid)) {
      final String msg =
          "Cannot commit, session is currently doing work in transaction " + tx.getXid();

      throw new HornetQXAException(XAException.XAER_PROTO, msg);
    } else {
      Transaction theTx = resourceManager.removeTransaction(xid);

      if (isTrace) {
        HornetQServerLogger.LOGGER.trace("XAcommit into " + theTx + ", xid=" + xid);
      }

      if (theTx == null) {
        // checked heuristic committed transactions
        if (resourceManager.getHeuristicCommittedTransactions().contains(xid)) {
          throw new HornetQXAException(
              XAException.XA_HEURCOM, "transaction has been heuristically committed: " + xid);
        }
        // checked heuristic rolled back transactions
        else if (resourceManager.getHeuristicRolledbackTransactions().contains(xid)) {
          throw new HornetQXAException(
              XAException.XA_HEURRB, "transaction has been heuristically rolled back: " + xid);
        } else {
          if (isTrace) {
            HornetQServerLogger.LOGGER.trace(
                "XAcommit into " + theTx + ", xid=" + xid + " cannot find it");
          }

          throw new HornetQXAException(
              XAException.XAER_NOTA, "Cannot find xid in resource manager: " + xid);
        }
      } else {
        if (theTx.getState() == Transaction.State.SUSPENDED) {
          // Put it back
          resourceManager.putTransaction(xid, theTx);

          throw new HornetQXAException(
              XAException.XAER_PROTO, "Cannot commit transaction, it is suspended " + xid);
        } else {
          theTx.commit(onePhase);
        }
      }
    }
  }

  public synchronized void xaEnd(final Xid xid) throws Exception {
    if (tx != null && tx.getXid().equals(xid)) {
      if (tx.getState() == Transaction.State.SUSPENDED) {
        final String msg = "Cannot end, transaction is suspended";

        throw new HornetQXAException(XAException.XAER_PROTO, msg);
      } else if (tx.getState() == Transaction.State.ROLLEDBACK) {
        final String msg = "Cannot end, transaction is rolled back";

        tx = null;

        throw new HornetQXAException(XAException.XAER_PROTO, msg);
      } else {
        tx = null;
      }
    } else {
      // It's also legal for the TM to call end for a Xid in the suspended
      // state
      // See JTA 1.1 spec 3.4.4 - state diagram
      // Although in practice TMs rarely do this.
      Transaction theTx = resourceManager.getTransaction(xid);

      if (theTx == null) {
        final String msg = "Cannot find suspended transaction to end " + xid;

        throw new HornetQXAException(XAException.XAER_NOTA, msg);
      } else {
        if (theTx.getState() != Transaction.State.SUSPENDED) {
          final String msg = "Transaction is not suspended " + xid;

          throw new HornetQXAException(XAException.XAER_PROTO, msg);
        } else {
          theTx.resume();
        }
      }
    }
  }

  public synchronized void xaForget(final Xid xid) throws Exception {
    long id = resourceManager.removeHeuristicCompletion(xid);

    if (id != -1) {
      try {
        storageManager.deleteHeuristicCompletion(id);
      } catch (Exception e) {
        e.printStackTrace();

        throw new HornetQXAException(XAException.XAER_RMERR);
      }
    } else {
      throw new HornetQXAException(XAException.XAER_NOTA);
    }
  }

  public synchronized void xaJoin(final Xid xid) throws Exception {
    Transaction theTx = resourceManager.getTransaction(xid);

    if (theTx == null) {
      final String msg = "Cannot find xid in resource manager: " + xid;

      throw new HornetQXAException(XAException.XAER_NOTA, msg);
    } else {
      if (theTx.getState() == Transaction.State.SUSPENDED) {
        throw new HornetQXAException(
            XAException.XAER_PROTO, "Cannot join tx, it is suspended " + xid);
      } else {
        tx = theTx;
      }
    }
  }

  public synchronized void xaResume(final Xid xid) throws Exception {
    if (tx != null) {
      final String msg =
          "Cannot resume, session is currently doing work in a transaction " + tx.getXid();

      throw new HornetQXAException(XAException.XAER_PROTO, msg);
    } else {
      Transaction theTx = resourceManager.getTransaction(xid);

      if (theTx == null) {
        final String msg = "Cannot find xid in resource manager: " + xid;

        throw new HornetQXAException(XAException.XAER_NOTA, msg);
      } else {
        if (theTx.getState() != Transaction.State.SUSPENDED) {
          throw new HornetQXAException(
              XAException.XAER_PROTO, "Cannot resume transaction, it is not suspended " + xid);
        } else {
          tx = theTx;

          tx.resume();
        }
      }
    }
  }

  public synchronized void xaRollback(final Xid xid) throws Exception {
    if (tx != null && tx.getXid().equals(xid)) {
      final String msg =
          "Cannot roll back, session is currently doing work in a transaction " + tx.getXid();

      throw new HornetQXAException(XAException.XAER_PROTO, msg);
    } else {
      Transaction theTx = resourceManager.removeTransaction(xid);
      if (isTrace) {
        HornetQServerLogger.LOGGER.trace("xarollback into " + theTx);
      }

      if (theTx == null) {
        // checked heuristic committed transactions
        if (resourceManager.getHeuristicCommittedTransactions().contains(xid)) {
          throw new HornetQXAException(
              XAException.XA_HEURCOM, "transaction has ben heuristically committed: " + xid);
        }
        // checked heuristic rolled back transactions
        else if (resourceManager.getHeuristicRolledbackTransactions().contains(xid)) {
          throw new HornetQXAException(
              XAException.XA_HEURRB, "transaction has ben heuristically rolled back: " + xid);
        } else {
          if (isTrace) {
            HornetQServerLogger.LOGGER.trace(
                "xarollback into " + theTx + ", xid=" + xid + " forcing a rollback regular");
          }

          try {
            // jbpapp-8845
            // This could have happened because the TX timed out,
            // at this point we would be better on rolling back this session as a way to prevent
            // consumers from holding their messages
            this.rollback(false);
          } catch (Exception e) {
            HornetQServerLogger.LOGGER.warn(e.getMessage(), e);
          }

          throw new HornetQXAException(
              XAException.XAER_NOTA, "Cannot find xid in resource manager: " + xid);
        }
      } else {
        if (theTx.getState() == Transaction.State.SUSPENDED) {
          if (isTrace) {
            HornetQServerLogger.LOGGER.trace(
                "xarollback into " + theTx + " sending tx back as it was suspended");
          }

          // Put it back
          resourceManager.putTransaction(xid, tx);

          throw new HornetQXAException(
              XAException.XAER_PROTO, "Cannot rollback transaction, it is suspended " + xid);
        } else {
          doRollback(false, false, theTx);
        }
      }
    }
  }

  public synchronized void xaStart(final Xid xid) throws Exception {
    if (tx != null) {
      HornetQServerLogger.LOGGER.xidReplacedOnXStart(tx.getXid().toString(), xid.toString());

      try {
        if (tx.getState() != Transaction.State.PREPARED) {
          // we don't want to rollback anything prepared here
          if (tx.getXid() != null) {
            resourceManager.removeTransaction(tx.getXid());
          }
          tx.rollback();
        }
      } catch (Exception e) {
        HornetQServerLogger.LOGGER.debug(
            "An exception happened while we tried to debug the previous tx, we can ignore this exception",
            e);
      }
    }

    tx = newTransaction(xid);

    if (isTrace) {
      HornetQServerLogger.LOGGER.trace("xastart into tx= " + tx);
    }

    boolean added = resourceManager.putTransaction(xid, tx);

    if (!added) {
      final String msg = "Cannot start, there is already a xid " + tx.getXid();

      throw new HornetQXAException(XAException.XAER_DUPID, msg);
    }
  }

  public synchronized void xaFailed(final Xid xid) throws Exception {
    if (tx != null) {
      final String msg =
          "Cannot start, session is already doing work in a transaction " + tx.getXid();

      throw new HornetQXAException(XAException.XAER_PROTO, msg);
    } else {

      tx = newTransaction(xid);
      tx.markAsRollbackOnly(
          new HornetQException("Can't commit as a Failover happened during the operation"));

      if (isTrace) {
        HornetQServerLogger.LOGGER.trace("xastart into tx= " + tx);
      }

      boolean added = resourceManager.putTransaction(xid, tx);

      if (!added) {
        final String msg = "Cannot start, there is already a xid " + tx.getXid();

        throw new HornetQXAException(XAException.XAER_DUPID, msg);
      }
    }
  }

  public synchronized void xaSuspend() throws Exception {

    if (isTrace) {
      HornetQServerLogger.LOGGER.trace("xasuspend on " + this.tx);
    }

    if (tx == null) {
      final String msg = "Cannot suspend, session is not doing work in a transaction ";

      throw new HornetQXAException(XAException.XAER_PROTO, msg);
    } else {
      if (tx.getState() == Transaction.State.SUSPENDED) {
        final String msg = "Cannot suspend, transaction is already suspended " + tx.getXid();

        throw new HornetQXAException(XAException.XAER_PROTO, msg);
      } else {
        tx.suspend();

        tx = null;
      }
    }
  }

  public synchronized void xaPrepare(final Xid xid) throws Exception {
    if (tx != null && tx.getXid().equals(xid)) {
      final String msg =
          "Cannot commit, session is currently doing work in a transaction " + tx.getXid();

      throw new HornetQXAException(XAException.XAER_PROTO, msg);
    } else {
      Transaction theTx = resourceManager.getTransaction(xid);

      if (isTrace) {
        HornetQServerLogger.LOGGER.trace("xaprepare into " + ", xid=" + xid + ", tx= " + tx);
      }

      if (theTx == null) {
        final String msg = "Cannot find xid in resource manager: " + xid;

        throw new HornetQXAException(XAException.XAER_NOTA, msg);
      } else {
        if (theTx.getState() == Transaction.State.SUSPENDED) {
          throw new HornetQXAException(
              XAException.XAER_PROTO, "Cannot prepare transaction, it is suspended " + xid);
        } else if (theTx.getState() == Transaction.State.PREPARED) {
          HornetQServerLogger.LOGGER.info("ignoring prepare on xid as already called :" + xid);
        } else {
          theTx.prepare();
        }
      }
    }
  }

  public List<Xid> xaGetInDoubtXids() {
    List<Xid> xids = new ArrayList<Xid>();

    xids.addAll(resourceManager.getPreparedTransactions());
    xids.addAll(resourceManager.getHeuristicCommittedTransactions());
    xids.addAll(resourceManager.getHeuristicRolledbackTransactions());

    return xids;
  }

  public int xaGetTimeout() {
    return resourceManager.getTimeoutSeconds();
  }

  public void xaSetTimeout(final int timeout) {
    timeoutSeconds = timeout;
    if (tx != null) {
      tx.setTimeout(timeout);
    }
  }

  public void start() {
    setStarted(true);
  }

  public void stop() {
    setStarted(false);
  }

  public void waitContextCompletion() {
    try {
      if (!context.waitCompletion(10000)) {
        HornetQServerLogger.LOGGER.errorCompletingContext(new Exception("warning"));
      }
    } catch (Exception e) {
      HornetQServerLogger.LOGGER.warn(e.getMessage(), e);
    }
  }

  public void close(final boolean failed) {
    if (closed) return;
    context.executeOnCompletion(
        new IOAsyncTask() {
          public void onError(int errorCode, String errorMessage) {}

          public void done() {
            try {
              doClose(failed);
            } catch (Exception e) {
              HornetQServerLogger.LOGGER.errorClosingSession(e);
            }
          }
        });
  }

  public void closeConsumer(final long consumerID) throws Exception {
    final ServerConsumer consumer = consumers.get(consumerID);

    if (consumer != null) {
      consumer.close(false);
    } else {
      HornetQServerLogger.LOGGER.cannotFindConsumer(consumerID);
    }
  }

  public void receiveConsumerCredits(final long consumerID, final int credits) throws Exception {
    ServerConsumer consumer = consumers.get(consumerID);

    if (consumer == null) {
      HornetQServerLogger.LOGGER.debug("There is no consumer with id " + consumerID);

      return;
    }

    consumer.receiveCredits(credits);
  }

  @Override
  public Transaction getCurrentTransaction() {
    return tx;
  }

  public void sendLarge(final MessageInternal message) throws Exception {
    // need to create the LargeMessage before continue
    long id = storageManager.generateUniqueID();

    LargeServerMessage largeMsg = storageManager.createLargeMessage(id, message);

    if (HornetQServerLogger.LOGGER.isTraceEnabled()) {
      HornetQServerLogger.LOGGER.trace("sendLarge::" + largeMsg);
    }

    if (currentLargeMessage != null) {
      HornetQServerLogger.LOGGER.replacingIncompleteLargeMessage(
          currentLargeMessage.getMessageID());
    }

    currentLargeMessage = largeMsg;
  }

  public void send(final ServerMessage message, final boolean direct) throws Exception {
    // large message may come from StompSession directly, in which
    // case the id header already generated.
    if (!message.isLargeMessage()) {
      long id = storageManager.generateUniqueID();

      message.setMessageID(id);
      message.encodeMessageIDToBuffer();
    }

    SimpleString address = message.getAddress();

    if (defaultAddress == null && address != null) {
      defaultAddress = address;
    }

    if (address == null) {
      if (message.isDurable()) {
        // We need to force a re-encode when the message gets persisted or when it gets reloaded
        // it will have no address
        message.setAddress(defaultAddress);
      } else {
        // We don't want to force a re-encode when the message gets sent to the consumer
        message.setAddressTransient(defaultAddress);
      }
    }

    if (isTrace) {
      HornetQServerLogger.LOGGER.trace(
          "send(message=" + message + ", direct=" + direct + ") being called");
    }

    if (message.getAddress() == null) {
      // This could happen with some tests that are ignoring messages
      throw HornetQMessageBundle.BUNDLE.noAddress();
    }

    if (message.getAddress().equals(managementAddress)) {
      // It's a management message

      handleManagementMessage(message, direct);
    } else {
      doSend(message, direct);
    }
  }

  public void sendContinuations(
      final int packetSize, final long messageBodySize, final byte[] body, final boolean continues)
      throws Exception {
    if (currentLargeMessage == null) {
      throw HornetQMessageBundle.BUNDLE.largeMessageNotInitialised();
    }

    // Immediately release the credits for the continuations- these don't contribute to the
    // in-memory size
    // of the message

    currentLargeMessage.addBytes(body);

    if (!continues) {
      currentLargeMessage.releaseResources();

      if (messageBodySize >= 0) {
        currentLargeMessage.putLongProperty(Message.HDR_LARGE_BODY_SIZE, messageBodySize);
      }

      doSend(currentLargeMessage, false);

      currentLargeMessage = null;
    }
  }

  public void requestProducerCredits(final SimpleString address, final int credits)
      throws Exception {
    PagingStore store = server.getPagingManager().getPageStore(address);

    if (!store.checkMemory(
        new Runnable() {
          public void run() {
            callback.sendProducerCreditsMessage(credits, address);
          }
        })) {
      callback.sendProducerCreditsFailMessage(credits, address);
    }
  }

  public void setTransferring(final boolean transferring) {
    Set<ServerConsumer> consumersClone = new HashSet<ServerConsumer>(consumers.values());

    for (ServerConsumer consumer : consumersClone) {
      consumer.setTransferring(transferring);
    }
  }

  public void addMetaData(String key, String data) {
    if (metaData == null) {
      metaData = new HashMap<String, String>();
    }
    metaData.put(key, data);
  }

  public boolean addUniqueMetaData(String key, String data) {
    ServerSession sessionWithMetaData = server.lookupSession(key, data);
    if (sessionWithMetaData != null && sessionWithMetaData != this) {
      // There is a duplication of this property
      return false;
    } else {
      addMetaData(key, data);
      return true;
    }
  }

  public String getMetaData(String key) {
    String data = null;
    if (metaData != null) {
      data = metaData.get(key);
    }
    return data;
  }

  public String[] getTargetAddresses() {
    Map<SimpleString, Pair<UUID, AtomicLong>> copy = cloneTargetAddresses();
    Iterator<SimpleString> iter = copy.keySet().iterator();
    int num = copy.keySet().size();
    String[] addresses = new String[num];
    int i = 0;
    while (iter.hasNext()) {
      addresses[i] = iter.next().toString();
      i++;
    }
    return addresses;
  }

  public String getLastSentMessageID(String address) {
    Pair<UUID, AtomicLong> value = targetAddressInfos.get(SimpleString.toSimpleString(address));
    if (value != null) {
      return value.getA().toString();
    } else {
      return null;
    }
  }

  public long getCreationTime() {
    return this.creationTime;
  }

  public StorageManager getStorageManager() {
    return this.storageManager;
  }

  @Override
  public void describeProducersInfo(JSONArray array) throws Exception {
    Map<SimpleString, Pair<UUID, AtomicLong>> targetCopy = cloneTargetAddresses();

    for (Map.Entry<SimpleString, Pair<UUID, AtomicLong>> entry : targetCopy.entrySet()) {
      JSONObject producerInfo = new JSONObject();
      producerInfo.put("connectionID", this.getConnectionID().toString());
      producerInfo.put("sessionID", this.getName());
      producerInfo.put("destination", entry.getKey().toString());
      producerInfo.put("lastUUIDSent", entry.getValue().getA());
      producerInfo.put("msgSent", entry.getValue().getB().longValue());
      array.put(producerInfo);
    }
  }

  @Override
  public String toString() {
    StringBuffer buffer = new StringBuffer();
    if (this.metaData != null) {
      for (Map.Entry<String, String> value : metaData.entrySet()) {
        if (buffer.length() != 0) {
          buffer.append(",");
        }
        Object tmpValue = value.getValue();
        if (tmpValue == null || tmpValue.toString().isEmpty()) {
          buffer.append(value.getKey() + "=*N/A*");
        } else {
          buffer.append(value.getKey() + "=" + tmpValue);
        }
      }
    }
    // This will actually appear on some management operations
    // so please don't clog this with debug objects
    // unless you provide a special way for management to translate sessions
    return "ServerSessionImpl(" + buffer.toString() + ")";
  }

  // FailureListener implementation
  // --------------------------------------------------------------------

  public void connectionFailed(final HornetQException me, boolean failedOver) {
    try {
      HornetQServerLogger.LOGGER.clientConnectionFailed(name);

      close(true);

      HornetQServerLogger.LOGGER.clientConnectionFailedClearingSession(name);
    } catch (Throwable t) {
      HornetQServerLogger.LOGGER.errorClosingConnection(this);
    }
  }

  // Public
  // ----------------------------------------------------------------------------

  public void clearLargeMessage() {
    currentLargeMessage = null;
  }

  // Private
  // ----------------------------------------------------------------------------

  private Map<SimpleString, Pair<UUID, AtomicLong>> cloneTargetAddresses() {
    return new HashMap<SimpleString, Pair<UUID, AtomicLong>>(targetAddressInfos);
  }

  private void setStarted(final boolean s) {
    Set<ServerConsumer> consumersClone = new HashSet<ServerConsumer>(consumers.values());

    for (ServerConsumer consumer : consumersClone) {
      consumer.setStarted(s);
    }

    started = s;
  }

  private void handleManagementMessage(final ServerMessage message, final boolean direct)
      throws Exception {
    try {
      securityStore.check(message.getAddress(), CheckType.MANAGE, this);
    } catch (HornetQException e) {
      if (!autoCommitSends) {
        tx.markAsRollbackOnly(e);
      }
      throw e;
    }

    ServerMessage reply = managementService.handleMessage(message);

    SimpleString replyTo = message.getSimpleStringProperty(ClientMessageImpl.REPLYTO_HEADER_NAME);

    if (replyTo != null) {
      reply.setAddress(replyTo);

      doSend(reply, direct);
    }
  }

  private void doRollback(
      final boolean clientFailed, final boolean lastMessageAsDelived, final Transaction theTx)
      throws Exception {
    boolean wasStarted = started;

    List<MessageReference> toCancel = new ArrayList<MessageReference>();

    for (ServerConsumer consumer : consumers.values()) {
      if (wasStarted) {
        consumer.setStarted(false);
      }

      toCancel.addAll(consumer.cancelRefs(clientFailed, lastMessageAsDelived, theTx));
    }

    for (MessageReference ref : toCancel) {
      ref.getQueue().cancel(theTx, ref);
    }
    // if we failed don't restart as an attempt to deliver messages may be made before we actually
    // close the consumer
    if (wasStarted && !clientFailed) {
      theTx.addOperation(
          new TransactionOperationAbstract() {

            @Override
            public void afterRollback(Transaction tx) {
              for (ServerConsumer consumer : consumers.values()) {
                consumer.setStarted(true);
              }
            }
          });
    }

    theTx.rollback();
  }

  private void doSend(final ServerMessage msg, final boolean direct) throws Exception {
    // check the user has write access to this address.
    try {
      securityStore.check(msg.getAddress(), CheckType.SEND, this);
    } catch (HornetQException e) {
      if (!autoCommitSends) {
        tx.markAsRollbackOnly(e);
      }
      throw e;
    }

    if (tx == null || autoCommitSends) {
    } else {
      routingContext.setTransaction(tx);
    }

    postOffice.route(msg, routingContext, direct);

    Pair<UUID, AtomicLong> value = targetAddressInfos.get(msg.getAddress());

    if (value == null) {
      targetAddressInfos.put(
          msg.getAddress(), new Pair<UUID, AtomicLong>(msg.getUserID(), new AtomicLong(1)));
    } else {
      value.setA(msg.getUserID());
      value.getB().incrementAndGet();
    }

    routingContext.clear();
  }

  @Override
  public List<MessageReference> getInTXMessagesForConsumer(long consumerId) {
    if (this.tx != null) {
      QueueImpl.RefsOperation oper =
          (QueueImpl.RefsOperation) tx.getProperty(TransactionPropertyIndexes.REFS_OPERATION);

      if (oper == null) {
        return Collections.emptyList();
      } else {
        return oper.getListOnConsumer(consumerId);
      }
    } else {
      return Collections.emptyList();
    }
  }
}