public boolean send(final StompConnection connection, final StompFrame frame) {
    if (ActiveMQServerLogger.LOGGER.isTraceEnabled()) {
      ActiveMQServerLogger.LOGGER.trace("sent " + frame);
    }

    invokeInterceptors(this.outgoingInterceptors, frame, connection);

    synchronized (connection) {
      if (connection.isDestroyed()) {
        ActiveMQStompProtocolLogger.LOGGER.connectionClosed(connection);
        return false;
      }

      try {
        connection.physicalSend(frame);
      } catch (Exception e) {
        ActiveMQStompProtocolLogger.LOGGER.errorSendingFrame(e, frame);
        return false;
      }
      return true;
    }
  }
public class InVMConnection implements Connection {
  private static final boolean isTrace = ActiveMQServerLogger.LOGGER.isTraceEnabled();

  private final BufferHandler handler;

  private final ConnectionLifeCycleListener listener;

  private final String id;

  private boolean closed;

  // Used on tests
  private static boolean flushEnabled = true;

  private final int serverID;

  private final Executor executor;

  private volatile boolean closing;

  private final ActiveMQPrincipal defaultActiveMQPrincipal;

  private RemotingConnection protocolConnection;

  public InVMConnection(
      final int serverID,
      final BufferHandler handler,
      final ConnectionLifeCycleListener listener,
      final Executor executor) {
    this(
        serverID,
        UUIDGenerator.getInstance().generateSimpleStringUUID().toString(),
        handler,
        listener,
        executor);
  }

  public InVMConnection(
      final int serverID,
      final String id,
      final BufferHandler handler,
      final ConnectionLifeCycleListener listener,
      final Executor executor) {
    this(serverID, id, handler, listener, executor, null);
  }

  public InVMConnection(
      final int serverID,
      final String id,
      final BufferHandler handler,
      final ConnectionLifeCycleListener listener,
      final Executor executor,
      final ActiveMQPrincipal defaultActiveMQPrincipal) {
    this.serverID = serverID;

    this.handler = handler;

    this.listener = listener;

    this.id = id;

    this.executor = executor;

    this.defaultActiveMQPrincipal = defaultActiveMQPrincipal;
  }

  public void forceClose() {
    // no op
  }

  public RemotingConnection getProtocolConnection() {
    return this.protocolConnection;
  }

  public void setProtocolConnection(RemotingConnection connection) {
    this.protocolConnection = connection;
  }

  public void close() {
    if (closing) {
      return;
    }

    closing = true;

    synchronized (this) {
      if (!closed) {
        listener.connectionDestroyed(id);

        closed = true;
      }
    }
  }

  public ActiveMQBuffer createTransportBuffer(final int size) {
    return ActiveMQBuffers.dynamicBuffer(size);
  }

  public Object getID() {
    return id;
  }

  public void checkFlushBatchBuffer() {}

  public void write(final ActiveMQBuffer buffer) {
    write(buffer, false, false, null);
  }

  public void write(final ActiveMQBuffer buffer, final boolean flush, final boolean batch) {
    write(buffer, flush, batch, null);
  }

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

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

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

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

        try {
          if (!latch.await(10, TimeUnit.SECONDS)) {
            ActiveMQServerLogger.LOGGER.timedOutFlushingInvmChannel();
          }
        } catch (InterruptedException e) {
          throw new ActiveMQInterruptedException(e);
        }
      }
    } catch (RejectedExecutionException e) {
      // Ignore - this can happen if server/client is shutdown and another request comes in
    }
  }

  public String getRemoteAddress() {
    return "invm:" + serverID;
  }

  public int getBatchingBufferSize() {
    return -1;
  }

  public void addReadyListener(ReadyListener listener) {}

  public void removeReadyListener(ReadyListener listener) {}

  @Override
  public boolean isUsingProtocolHandling() {
    return false;
  }

  public ActiveMQPrincipal getDefaultActiveMQPrincipal() {
    return defaultActiveMQPrincipal;
  }

  public static void setFlushEnabled(boolean enable) {
    flushEnabled = enable;
  }

  public Executor getExecutor() {
    return executor;
  }

  @Override
  public TransportConfiguration getConnectorConfig() {
    Map<String, Object> params = new HashMap<String, Object>();

    params.put(
        org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants.SERVER_ID_PROP_NAME,
        serverID);

    return new TransportConfiguration(InVMConnectorFactory.class.getName(), params);
  }

  @Override
  public String toString() {
    return "InVMConnection [serverID=" + serverID + ", id=" + id + "]";
  }
}
예제 #3
0
/** @see PagingStore */
public class PagingStoreImpl implements PagingStore {

  private final SimpleString address;

  private final StorageManager storageManager;

  private final DecimalFormat format = new DecimalFormat("000000000");

  private final AtomicInteger currentPageSize = new AtomicInteger(0);

  private final SimpleString storeName;

  // The FileFactory is created lazily as soon as the first write is attempted
  private volatile SequentialFileFactory fileFactory;

  private final PagingStoreFactory storeFactory;

  // Used to schedule sync threads
  private final PageSyncTimer syncTimer;

  private long maxSize;

  private long pageSize;

  private volatile AddressFullMessagePolicy addressFullMessagePolicy;

  private boolean printedDropMessagesWarning;

  private final PagingManager pagingManager;

  private final Executor executor;

  // Bytes consumed by the queue on the memory
  private final AtomicLong sizeInBytes = new AtomicLong();

  private int numberOfPages;

  private int firstPageId;

  private volatile int currentPageId;

  private volatile Page currentPage;

  private volatile boolean paging = false;

  private final PageCursorProvider cursorProvider;

  private final ReadWriteLock lock = new ReentrantReadWriteLock();

  private volatile boolean running = false;

  private final boolean syncNonTransactional;

  private volatile AtomicBoolean blocking = new AtomicBoolean(false);

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

  public PagingStoreImpl(
      final SimpleString address,
      final ScheduledExecutorService scheduledExecutor,
      final long syncTimeout,
      final PagingManager pagingManager,
      final StorageManager storageManager,
      final SequentialFileFactory fileFactory,
      final PagingStoreFactory storeFactory,
      final SimpleString storeName,
      final AddressSettings addressSettings,
      final Executor executor,
      final boolean syncNonTransactional) {
    if (pagingManager == null) {
      throw new IllegalStateException("Paging Manager can't be null");
    }

    this.address = address;

    this.storageManager = storageManager;

    this.storeName = storeName;

    applySetting(addressSettings);

    if (addressFullMessagePolicy == AddressFullMessagePolicy.PAGE
        && maxSize != -1
        && pageSize >= maxSize) {
      throw new IllegalStateException(
          "pageSize for address "
              + address
              + " >= maxSize. Normally pageSize should"
              + " be significantly smaller than maxSize, ms: "
              + maxSize
              + " ps "
              + pageSize);
    }

    this.executor = executor;

    this.pagingManager = pagingManager;

    this.fileFactory = fileFactory;

    this.storeFactory = storeFactory;

    this.syncNonTransactional = syncNonTransactional;

    if (scheduledExecutor != null && syncTimeout > 0) {
      this.syncTimer = new PageSyncTimer(this, scheduledExecutor, syncTimeout);
    } else {
      this.syncTimer = null;
    }

    this.cursorProvider =
        new PageCursorProviderImpl(
            this, this.storageManager, executor, addressSettings.getPageCacheMaxSize());
  }

  /** @param addressSettings */
  @Override
  public void applySetting(final AddressSettings addressSettings) {
    maxSize = addressSettings.getMaxSizeBytes();

    pageSize = addressSettings.getPageSizeBytes();

    addressFullMessagePolicy = addressSettings.getAddressFullMessagePolicy();

    if (cursorProvider != null) {
      cursorProvider.setCacheMaxSize(addressSettings.getPageCacheMaxSize());
    }
  }

  @Override
  public String toString() {
    return "PagingStoreImpl(" + this.address + ")";
  }

  @Override
  public boolean lock(long timeout) {
    if (timeout == -1) {
      lock.writeLock().lock();
      return true;
    }
    try {
      return lock.writeLock().tryLock(timeout, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
      return false;
    }
  }

  @Override
  public void unlock() {
    lock.writeLock().unlock();
  }

  @Override
  public PageCursorProvider getCursorProvider() {
    return cursorProvider;
  }

  @Override
  public long getFirstPage() {
    return firstPageId;
  }

  @Override
  public SimpleString getAddress() {
    return address;
  }

  @Override
  public long getAddressSize() {
    return sizeInBytes.get();
  }

  @Override
  public long getMaxSize() {
    return maxSize;
  }

  @Override
  public AddressFullMessagePolicy getAddressFullMessagePolicy() {
    return addressFullMessagePolicy;
  }

  @Override
  public long getPageSizeBytes() {
    return pageSize;
  }

  @Override
  public File getFolder() {
    SequentialFileFactory factoryUsed = this.fileFactory;
    if (factoryUsed != null) {
      return factoryUsed.getDirectory();
    } else {
      return null;
    }
  }

  @Override
  public boolean isPaging() {
    lock.readLock().lock();

    try {
      if (addressFullMessagePolicy == AddressFullMessagePolicy.BLOCK) {
        return false;
      }
      if (addressFullMessagePolicy == AddressFullMessagePolicy.FAIL) {
        return isFull();
      }
      if (addressFullMessagePolicy == AddressFullMessagePolicy.DROP) {
        return isFull();
      }
      return paging;
    } finally {
      lock.readLock().unlock();
    }
  }

  @Override
  public int getNumberOfPages() {
    return numberOfPages;
  }

  @Override
  public int getCurrentWritingPage() {
    return currentPageId;
  }

  @Override
  public SimpleString getStoreName() {
    return storeName;
  }

  @Override
  public void sync() throws Exception {
    if (syncTimer != null) {
      syncTimer.addSync(storageManager.getContext());
    } else {
      ioSync();
    }
  }

  @Override
  public void ioSync() throws Exception {
    lock.readLock().lock();

    try {
      if (currentPage != null) {
        currentPage.sync();
      }
    } finally {
      lock.readLock().unlock();
    }
  }

  @Override
  public void processReload() throws Exception {
    cursorProvider.processReload();
  }

  @Override
  public PagingManager getPagingManager() {
    return pagingManager;
  }

  @Override
  public boolean isStarted() {
    return running;
  }

  @Override
  public synchronized void stop() throws Exception {
    if (running) {
      cursorProvider.stop();

      running = false;

      flushExecutors();

      if (currentPage != null) {
        currentPage.close();
        currentPage = null;
      }
    }
  }

  @Override
  public void flushExecutors() {
    cursorProvider.flushExecutors();

    FutureLatch future = new FutureLatch();

    executor.execute(future);

    if (!future.await(60000)) {
      ActiveMQServerLogger.LOGGER.pageStoreTimeout(address);
    }
  }

  @Override
  public void start() throws Exception {
    lock.writeLock().lock();

    try {

      if (running) {
        // don't throw an exception.
        // You could have two threads adding PagingStore to a
        // ConcurrentHashMap,
        // and having both threads calling init. One of the calls should just
        // need to be ignored
        return;
      } else {
        running = true;
        firstPageId = Integer.MAX_VALUE;

        // There are no files yet on this Storage. We will just return it empty
        if (fileFactory != null) {

          currentPageId = 0;
          if (currentPage != null) {
            currentPage.close();
          }
          currentPage = null;

          List<String> files = fileFactory.listFiles("page");

          numberOfPages = files.size();

          for (String fileName : files) {
            final int fileId = PagingStoreImpl.getPageIdFromFileName(fileName);

            if (fileId > currentPageId) {
              currentPageId = fileId;
            }

            if (fileId < firstPageId) {
              firstPageId = fileId;
            }
          }

          if (currentPageId != 0) {
            currentPage = createPage(currentPageId);
            currentPage.open();

            List<PagedMessage> messages = currentPage.read(storageManager);

            LivePageCache pageCache = new LivePageCacheImpl(currentPage);

            for (PagedMessage msg : messages) {
              pageCache.addLiveMessage(msg);
              if (msg.getMessage().isLargeMessage()) {
                // We have to do this since addLIveMessage will increment an extra one
                ((LargeServerMessage) msg.getMessage()).decrementDelayDeletionCount();
              }
            }

            currentPage.setLiveCache(pageCache);

            currentPageSize.set(currentPage.getSize());

            cursorProvider.addPageCache(pageCache);
          }

          // We will not mark it for paging if there's only a single empty file
          if (currentPage != null && !(numberOfPages == 1 && currentPage.getSize() == 0)) {
            startPaging();
          }
        }
      }

    } finally {
      lock.writeLock().unlock();
    }
  }

  @Override
  public void stopPaging() {
    lock.writeLock().lock();
    try {
      paging = false;
      this.cursorProvider.onPageModeCleared();
    } finally {
      lock.writeLock().unlock();
    }
  }

  @Override
  public boolean startPaging() {
    if (!running) {
      return false;
    }

    lock.readLock().lock();
    try {
      // I'm not calling isPaging() here because
      // isPaging will perform extra steps.
      // at this context it doesn't really matter what policy we are using
      // since this method is only called when paging.
      // Besides that isPaging() will perform lock.readLock() again which is not needed here
      // for that reason the attribute is used directly here.
      if (paging) {
        return false;
      }
    } finally {
      lock.readLock().unlock();
    }

    // if the first check failed, we do it again under a global currentPageLock
    // (writeLock) this time
    lock.writeLock().lock();

    try {
      // Same notes from previous if (paging) on this method will apply here
      if (paging) {
        return false;
      }

      if (currentPage == null) {
        try {
          openNewPage();
        } catch (Exception e) {
          // If not possible to starting page due to an IO error, we will just consider it non
          // paging.
          // This shouldn't happen anyway
          ActiveMQServerLogger.LOGGER.pageStoreStartIOError(e);
          return false;
        }
      }

      paging = true;

      return true;
    } finally {
      lock.writeLock().unlock();
    }
  }

  @Override
  public Page getCurrentPage() {
    return currentPage;
  }

  @Override
  public boolean checkPageFileExists(final int pageNumber) {
    String fileName = createFileName(pageNumber);
    SequentialFile file = fileFactory.createSequentialFile(fileName);
    return file.exists();
  }

  @Override
  public Page createPage(final int pageNumber) throws Exception {
    String fileName = createFileName(pageNumber);

    if (fileFactory == null) {
      fileFactory = storeFactory.newFileFactory(getStoreName());
    }

    SequentialFile file = fileFactory.createSequentialFile(fileName);

    Page page = new Page(storeName, storageManager, fileFactory, file, pageNumber);

    // To create the file
    file.open();

    file.position(0);

    file.close();

    return page;
  }

  @Override
  public void forceAnotherPage() throws Exception {
    openNewPage();
  }

  /**
   * Returns a Page out of the Page System without reading it.
   *
   * <p>The method calling this method will remove the page and will start reading it outside of any
   * locks. This method could also replace the current file by a new file, and that process is done
   * through acquiring a writeLock on currentPageLock.
   *
   * <p>Observation: This method is used internally as part of the regular depage process, but
   * externally is used only on tests, and that's why this method is part of the Testable Interface
   */
  @Override
  public Page depage() throws Exception {
    lock.writeLock().lock(); // Make sure no checks are done on currentPage while we are depaging
    try {
      if (!running) {
        return null;
      }

      if (numberOfPages == 0) {
        return null;
      } else {
        numberOfPages--;

        final Page returnPage;

        // We are out of old pages, all that is left now is the current page.
        // On that case we need to replace it by a new empty page, and return the current page
        // immediately
        if (currentPageId == firstPageId) {
          firstPageId = Integer.MAX_VALUE;

          if (currentPage == null) {
            // sanity check... it shouldn't happen!
            throw new IllegalStateException("CurrentPage is null");
          }

          returnPage = currentPage;
          returnPage.close();
          currentPage = null;

          // The current page is empty... which means we reached the end of the pages
          if (returnPage.getNumberOfMessages() == 0) {
            stopPaging();
            returnPage.open();
            returnPage.delete(null);

            // This will trigger this address to exit the page mode,
            // and this will make ActiveMQ Artemis start using the journal again
            return null;
          } else {
            // We need to create a new page, as we can't lock the address until we finish depaging.
            openNewPage();
          }

          return returnPage;
        } else {
          returnPage = createPage(firstPageId++);
        }

        return returnPage;
      }
    } finally {
      lock.writeLock().unlock();
    }
  }

  private final Queue<OurRunnable> onMemoryFreedRunnables = new ConcurrentLinkedQueue<>();

  private class MemoryFreedRunnablesExecutor implements Runnable {

    @Override
    public void run() {
      Runnable runnable;

      while ((runnable = onMemoryFreedRunnables.poll()) != null) {
        runnable.run();
      }
    }
  }

  private final Runnable memoryFreedRunnablesExecutor = new MemoryFreedRunnablesExecutor();

  private static final class OurRunnable implements Runnable {

    private boolean ran;

    private final Runnable runnable;

    private OurRunnable(final Runnable runnable) {
      this.runnable = runnable;
    }

    @Override
    public synchronized void run() {
      if (!ran) {
        runnable.run();

        ran = true;
      }
    }
  }

  @Override
  public boolean checkMemory(final Runnable runWhenAvailable) {
    if (addressFullMessagePolicy == AddressFullMessagePolicy.BLOCK && maxSize != -1) {
      if (sizeInBytes.get() > maxSize) {
        OurRunnable ourRunnable = new OurRunnable(runWhenAvailable);

        onMemoryFreedRunnables.add(ourRunnable);

        // We check again to avoid a race condition where the size can come down just after the
        // element
        // has been added, but the check to execute was done before the element was added
        // NOTE! We do not fix this race by locking the whole thing, doing this check provides
        // MUCH better performance in a highly concurrent environment
        if (sizeInBytes.get() <= maxSize) {
          // run it now
          ourRunnable.run();
        } else if (!blocking.get()) {
          ActiveMQServerLogger.LOGGER.blockingMessageProduction(
              address, sizeInBytes.get(), maxSize);
          blocking.set(true);
        }

        return true;
      }
    } else if (addressFullMessagePolicy == AddressFullMessagePolicy.FAIL && maxSize != -1) {
      if (sizeInBytes.get() > maxSize) {
        return false;
      }
    }

    runWhenAvailable.run();

    return true;
  }

  @Override
  public void addSize(final int size) {
    if (addressFullMessagePolicy == AddressFullMessagePolicy.BLOCK) {
      if (maxSize != -1) {
        long newSize = sizeInBytes.addAndGet(size);

        if (newSize <= maxSize) {
          if (!onMemoryFreedRunnables.isEmpty()) {
            executor.execute(memoryFreedRunnablesExecutor);
            if (blocking.get()) {
              ActiveMQServerLogger.LOGGER.unblockingMessageProduction(
                  address, sizeInBytes.get(), maxSize);
              blocking.set(false);
            }
          }
        }
      }

      return;
    } else if (addressFullMessagePolicy == AddressFullMessagePolicy.PAGE) {
      final long addressSize = sizeInBytes.addAndGet(size);

      if (size > 0) {
        if (maxSize > 0 && addressSize > maxSize) {
          if (startPaging()) {
            ActiveMQServerLogger.LOGGER.pageStoreStart(storeName, addressSize, maxSize);
          }
        }
      }

      return;
    } else if (addressFullMessagePolicy == AddressFullMessagePolicy.DROP
        || addressFullMessagePolicy == AddressFullMessagePolicy.FAIL) {
      sizeInBytes.addAndGet(size);
    }
  }

  @Override
  public boolean page(
      ServerMessage message,
      final Transaction tx,
      RouteContextList listCtx,
      final ReadLock managerLock)
      throws Exception {

    if (!running) {
      throw new IllegalStateException("PagingStore(" + getStoreName() + ") not initialized");
    }

    boolean full = isFull();

    if (addressFullMessagePolicy == AddressFullMessagePolicy.DROP
        || addressFullMessagePolicy == AddressFullMessagePolicy.FAIL) {
      if (full) {
        if (!printedDropMessagesWarning) {
          printedDropMessagesWarning = true;

          ActiveMQServerLogger.LOGGER.pageStoreDropMessages(storeName, sizeInBytes.get(), maxSize);
        }

        if (message.isLargeMessage()) {
          ((LargeServerMessage) message).deleteFile();
        }

        if (addressFullMessagePolicy == AddressFullMessagePolicy.FAIL) {
          throw ActiveMQMessageBundle.BUNDLE.addressIsFull(address.toString());
        }

        // Address is full, we just pretend we are paging, and drop the data
        return true;
      } else {
        return false;
      }
    } else if (addressFullMessagePolicy == AddressFullMessagePolicy.BLOCK) {
      return false;
    }

    // We need to ensure a read lock, as depage could change the paging state
    lock.readLock().lock();

    try {
      // First check done concurrently, to avoid synchronization and increase throughput
      if (!paging) {
        return false;
      }
    } finally {
      lock.readLock().unlock();
    }

    managerLock.lock();
    try {
      lock.writeLock().lock();

      try {
        if (!paging) {
          return false;
        }

        if (!message.isDurable()) {
          // The address should never be transient when paging (even for non-persistent messages
          // when paging)
          // This will force everything to be persisted
          message.forceAddress(address);
        }

        final long transactionID = tx == null ? -1 : tx.getID();
        PagedMessage pagedMessage =
            new PagedMessageImpl(message, routeQueues(tx, listCtx), transactionID);

        if (message.isLargeMessage()) {
          ((LargeServerMessage) message).setPaged();
        }

        int bytesToWrite = pagedMessage.getEncodeSize() + Page.SIZE_RECORD;

        if (currentPageSize.addAndGet(bytesToWrite) > pageSize
            && currentPage.getNumberOfMessages() > 0) {
          // Make sure nothing is currently validating or using currentPage
          openNewPage();
          currentPageSize.addAndGet(bytesToWrite);
        }

        if (tx != null) {
          installPageTransaction(tx, listCtx);
        }

        // the apply counter will make sure we write a record on journal
        // especially on the case for non transactional sends and paging
        // doing this will give us a possibility of recovering the page counters
        applyPageCounters(tx, getCurrentPage(), listCtx);

        currentPage.write(pagedMessage);

        if (tx == null && syncNonTransactional && message.isDurable()) {
          sync();
        }

        if (isTrace) {
          ActiveMQServerLogger.LOGGER.trace(
              "Paging message "
                  + pagedMessage
                  + " on pageStore "
                  + this.getStoreName()
                  + " pageId="
                  + currentPage.getPageId());
        }

        return true;
      } finally {
        lock.writeLock().unlock();
      }
    } finally {
      managerLock.unlock();
    }
  }

  /** This method will disable cleanup of pages. No page will be deleted after this call. */
  @Override
  public void disableCleanup() {
    getCursorProvider().disableCleanup();
  }

  /**
   * This method will re-enable cleanup of pages. Notice that it will also start cleanup threads.
   */
  @Override
  public void enableCleanup() {
    getCursorProvider().resumeCleanup();
  }

  private long[] routeQueues(Transaction tx, RouteContextList ctx) throws Exception {
    List<org.apache.activemq.artemis.core.server.Queue> durableQueues = ctx.getDurableQueues();
    List<org.apache.activemq.artemis.core.server.Queue> nonDurableQueues =
        ctx.getNonDurableQueues();
    long[] ids = new long[durableQueues.size() + nonDurableQueues.size()];
    int i = 0;

    for (org.apache.activemq.artemis.core.server.Queue q : durableQueues) {
      q.getPageSubscription().notEmpty();
      ids[i++] = q.getID();
    }

    for (org.apache.activemq.artemis.core.server.Queue q : nonDurableQueues) {
      q.getPageSubscription().getCounter().increment(tx, 1);
      q.getPageSubscription().notEmpty();
      ids[i++] = q.getID();
    }
    return ids;
  }

  /**
   * This is done to prevent non tx to get out of sync in case of failures
   *
   * @param tx
   * @param page
   * @param ctx
   * @throws Exception
   */
  private void applyPageCounters(Transaction tx, Page page, RouteContextList ctx) throws Exception {
    List<org.apache.activemq.artemis.core.server.Queue> durableQueues = ctx.getDurableQueues();
    List<org.apache.activemq.artemis.core.server.Queue> nonDurableQueues =
        ctx.getNonDurableQueues();
    for (org.apache.activemq.artemis.core.server.Queue q : durableQueues) {
      if (tx == null) {
        // non transactional writes need an intermediate place
        // to avoid the counter getting out of sync
        q.getPageSubscription().getCounter().pendingCounter(page, 1);
      } else {
        // null tx is treated through pending counters
        q.getPageSubscription().getCounter().increment(tx, 1);
      }
    }

    for (org.apache.activemq.artemis.core.server.Queue q : nonDurableQueues) {
      q.getPageSubscription().getCounter().increment(tx, 1);
    }
  }

  private void installPageTransaction(final Transaction tx, final RouteContextList listCtx)
      throws Exception {
    FinishPageMessageOperation pgOper =
        (FinishPageMessageOperation) tx.getProperty(TransactionPropertyIndexes.PAGE_TRANSACTION);
    if (pgOper == null) {
      PageTransactionInfo pgTX = new PageTransactionInfoImpl(tx.getID());
      pagingManager.addTransaction(pgTX);
      pgOper = new FinishPageMessageOperation(pgTX, storageManager, pagingManager);
      tx.putProperty(TransactionPropertyIndexes.PAGE_TRANSACTION, pgOper);
      tx.addOperation(pgOper);
    }

    pgOper.addStore(this);
    pgOper.pageTransaction.increment(
        listCtx.getNumberOfDurableQueues(), listCtx.getNumberOfNonDurableQueues());

    return;
  }

  private static class FinishPageMessageOperation implements TransactionOperation {

    private final PageTransactionInfo pageTransaction;
    private final StorageManager storageManager;
    private final PagingManager pagingManager;
    private final Set<PagingStore> usedStores = new HashSet<>();

    private boolean stored = false;

    public void addStore(PagingStore store) {
      this.usedStores.add(store);
    }

    public FinishPageMessageOperation(
        final PageTransactionInfo pageTransaction,
        final StorageManager storageManager,
        final PagingManager pagingManager) {
      this.pageTransaction = pageTransaction;
      this.storageManager = storageManager;
      this.pagingManager = pagingManager;
    }

    @Override
    public void afterCommit(final Transaction tx) {
      // If part of the transaction goes to the queue, and part goes to paging, we can't let depage
      // start for the
      // transaction until all the messages were added to the queue
      // or else we could deliver the messages out of order

      if (pageTransaction != null) {
        pageTransaction.commit();
      }
    }

    @Override
    public void afterPrepare(final Transaction tx) {}

    @Override
    public void afterRollback(final Transaction tx) {
      if (pageTransaction != null) {
        pageTransaction.rollback();
      }
    }

    @Override
    public void beforeCommit(final Transaction tx) throws Exception {
      syncStore();
      storePageTX(tx);
    }

    /** @throws Exception */
    private void syncStore() throws Exception {
      for (PagingStore store : usedStores) {
        store.sync();
      }
    }

    @Override
    public void beforePrepare(final Transaction tx) throws Exception {
      syncStore();
      storePageTX(tx);
    }

    private void storePageTX(final Transaction tx) throws Exception {
      if (!stored) {
        tx.setContainsPersistent();
        pageTransaction.store(storageManager, pagingManager, tx);
        stored = true;
      }
    }

    @Override
    public void beforeRollback(final Transaction tx) throws Exception {}

    @Override
    public List<MessageReference> getRelatedMessageReferences() {
      return Collections.emptyList();
    }

    @Override
    public List<MessageReference> getListOnConsumer(long consumerID) {
      return Collections.emptyList();
    }
  }

  private void openNewPage() throws Exception {
    lock.writeLock().lock();

    try {
      numberOfPages++;

      int tmpCurrentPageId = currentPageId + 1;

      if (currentPage != null) {
        currentPage.close();
      }

      currentPage = createPage(tmpCurrentPageId);

      LivePageCache pageCache = new LivePageCacheImpl(currentPage);

      currentPage.setLiveCache(pageCache);

      cursorProvider.addPageCache(pageCache);

      currentPageSize.set(0);

      currentPage.open();

      currentPageId = tmpCurrentPageId;

      if (currentPageId < firstPageId) {
        firstPageId = currentPageId;
      }
    } finally {
      lock.writeLock().unlock();
    }
  }

  /**
   * @param pageID
   * @return
   */
  private String createFileName(final int pageID) {
    /** {@link DecimalFormat} is not thread safe. */
    synchronized (format) {
      return format.format(pageID) + ".page";
    }
  }

  private static int getPageIdFromFileName(final String fileName) {
    return Integer.parseInt(fileName.substring(0, fileName.indexOf('.')));
  }

  // To be used on isDropMessagesWhenFull
  public boolean isFull() {
    return maxSize > 0 && getAddressSize() > maxSize;
  }

  @Override
  public Collection<Integer> getCurrentIds() throws Exception {
    List<Integer> ids = new ArrayList<>();
    if (fileFactory != null) {
      for (String fileName : fileFactory.listFiles("page")) {
        ids.add(getPageIdFromFileName(fileName));
      }
    }
    return ids;
  }

  @Override
  public void sendPages(ReplicationManager replicator, Collection<Integer> pageIds)
      throws Exception {
    lock.writeLock().lock();
    try {
      for (Integer id : pageIds) {
        SequentialFile sFile = fileFactory.createSequentialFile(createFileName(id));
        if (!sFile.exists()) {
          continue;
        }
        replicator.syncPages(sFile, id, getAddress());
      }
    } finally {
      lock.writeLock().unlock();
    }
  }

  // Inner classes -------------------------------------------------
}
/**
 * This implementation delegates to the JAAS security interfaces.
 *
 * <p>The {@link Subject} returned by the login context is expecting to have a set of {@link
 * RolePrincipal} for each role of the user.
 */
public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 {

  private final boolean trace = ActiveMQServerLogger.LOGGER.isTraceEnabled();

  private String configurationName;

  @Override
  public boolean validateUser(String user, String password) {
    throw new UnsupportedOperationException(
        "Invoke validateUser(String, String, X509Certificate[]) instead");
  }

  @Override
  public boolean validateUser(
      final String user, final String password, X509Certificate[] certificates) {
    try {
      getAuthenticatedSubject(user, password, certificates);
      return true;
    } catch (LoginException e) {
      ActiveMQServerLogger.LOGGER.debug("Couldn't validate user", e);
      return false;
    }
  }

  @Override
  public boolean validateUserAndRole(
      String user, String password, Set<Role> roles, CheckType checkType) {
    throw new UnsupportedOperationException(
        "Invoke validateUserAndRole(String, String, Set<Role>, CheckType, String, RemotingConnection) instead");
  }

  @Override
  public boolean validateUserAndRole(
      final String user,
      final String password,
      final Set<Role> roles,
      final CheckType checkType,
      final String address,
      final RemotingConnection connection) {
    X509Certificate[] certificates = null;
    if (connection.getTransportConnection() instanceof NettyConnection) {
      certificates =
          CertificateUtil.getCertsFromChannel(
              ((NettyConnection) connection.getTransportConnection()).getChannel());
    }
    Subject localSubject;
    try {
      localSubject = getAuthenticatedSubject(user, password, certificates);
    } catch (LoginException e) {
      ActiveMQServerLogger.LOGGER.debug("Couldn't validate user", e);
      return false;
    }

    boolean authorized = false;

    if (localSubject != null) {
      Set<RolePrincipal> rolesWithPermission = getPrincipalsInRole(checkType, roles);

      // Check the caller's roles
      Set<RolePrincipal> rolesForSubject = localSubject.getPrincipals(RolePrincipal.class);
      if (rolesForSubject.size() > 0 && rolesWithPermission.size() > 0) {
        Iterator<RolePrincipal> rolesForSubjectIter = rolesForSubject.iterator();
        while (!authorized && rolesForSubjectIter.hasNext()) {
          Iterator<RolePrincipal> rolesWithPermissionIter = rolesWithPermission.iterator();
          while (!authorized && rolesWithPermissionIter.hasNext()) {
            Principal role = rolesWithPermissionIter.next();
            authorized = rolesForSubjectIter.next().equals(role);
          }
        }
      }

      if (trace) {
        ActiveMQServerLogger.LOGGER.trace(
            "user " + (authorized ? " is " : " is NOT ") + "authorized");
      }
    }

    return authorized;
  }

  private Subject getAuthenticatedSubject(
      final String user, final String password, final X509Certificate[] certificates)
      throws LoginException {
    LoginContext lc =
        new LoginContext(configurationName, new JaasCallbackHandler(user, password, certificates));
    lc.login();
    return lc.getSubject();
  }

  private Set<RolePrincipal> getPrincipalsInRole(final CheckType checkType, final Set<Role> roles) {
    Set<RolePrincipal> principals = new HashSet<>();
    for (Role role : roles) {
      if (checkType.hasRole(role)) {
        principals.add(new RolePrincipal(role.getName()));
      }
    }
    return principals;
  }

  public void setConfigurationName(final String configurationName) {
    this.configurationName = configurationName;
  }
}
/**
 * Handles all the synchronization necessary for replication on the backup side (that is the
 * backup's side of the "remote backup" use case).
 */
public final class ReplicationEndpoint implements ChannelHandler, ActiveMQComponent {

  private static final boolean trace = ActiveMQServerLogger.LOGGER.isTraceEnabled();

  private final IOCriticalErrorListener criticalErrorListener;
  private final ActiveMQServerImpl server;
  private final boolean wantedFailBack;
  private final SharedNothingBackupActivation activation;
  private final boolean noSync = false;
  private Channel channel;

  private Journal[] journals;
  private final JournalLoadInformation[] journalLoadInformation = new JournalLoadInformation[2];

  /** Files reserved in each journal for synchronization of existing data from the 'live' server. */
  private final Map<JournalContent, Map<Long, JournalSyncFile>> filesReservedForSync =
      new HashMap<>();

  /**
   * Used to hold the real Journals before the backup is synchronized. This field should be {@code
   * null} on an up-to-date server.
   */
  private Map<JournalContent, Journal> journalsHolder = new HashMap<>();

  private StorageManager storageManager;

  private PagingManager pageManager;

  private final ConcurrentMap<SimpleString, ConcurrentMap<Integer, Page>> pageIndex =
      new ConcurrentHashMap<>();
  private final ConcurrentMap<Long, ReplicatedLargeMessage> largeMessages =
      new ConcurrentHashMap<>();

  // Used on tests, to simulate failures on delete pages
  private boolean deletePages = true;
  private volatile boolean started;

  private SharedNothingBackupQuorum backupQuorum;

  private Executor executor;

  // Constructors --------------------------------------------------
  public ReplicationEndpoint(
      final ActiveMQServerImpl server,
      IOCriticalErrorListener criticalErrorListener,
      boolean wantedFailBack,
      SharedNothingBackupActivation activation) {
    this.server = server;
    this.criticalErrorListener = criticalErrorListener;
    this.wantedFailBack = wantedFailBack;
    this.activation = activation;
  }

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

  public synchronized void registerJournal(final byte id, final Journal journal) {
    if (journals == null || id >= journals.length) {
      Journal[] oldJournals = journals;
      journals = new Journal[id + 1];

      if (oldJournals != null) {
        for (int i = 0; i < oldJournals.length; i++) {
          journals[i] = oldJournals[i];
        }
      }
    }

    journals[id] = journal;
  }

  @Override
  public void handlePacket(final Packet packet) {
    PacketImpl response = new ReplicationResponseMessage();
    final byte type = packet.getType();

    try {
      if (!started) {
        return;
      }

      if (type == PacketImpl.REPLICATION_APPEND) {
        handleAppendAddRecord((ReplicationAddMessage) packet);
      } else if (type == PacketImpl.REPLICATION_APPEND_TX) {
        handleAppendAddTXRecord((ReplicationAddTXMessage) packet);
      } else if (type == PacketImpl.REPLICATION_DELETE) {
        handleAppendDelete((ReplicationDeleteMessage) packet);
      } else if (type == PacketImpl.REPLICATION_DELETE_TX) {
        handleAppendDeleteTX((ReplicationDeleteTXMessage) packet);
      } else if (type == PacketImpl.REPLICATION_PREPARE) {
        handlePrepare((ReplicationPrepareMessage) packet);
      } else if (type == PacketImpl.REPLICATION_COMMIT_ROLLBACK) {
        handleCommitRollback((ReplicationCommitMessage) packet);
      } else if (type == PacketImpl.REPLICATION_PAGE_WRITE) {
        handlePageWrite((ReplicationPageWriteMessage) packet);
      } else if (type == PacketImpl.REPLICATION_PAGE_EVENT) {
        handlePageEvent((ReplicationPageEventMessage) packet);
      } else if (type == PacketImpl.REPLICATION_LARGE_MESSAGE_BEGIN) {
        handleLargeMessageBegin((ReplicationLargeMessageBeginMessage) packet);
      } else if (type == PacketImpl.REPLICATION_LARGE_MESSAGE_WRITE) {
        handleLargeMessageWrite((ReplicationLargeMessageWriteMessage) packet);
      } else if (type == PacketImpl.REPLICATION_LARGE_MESSAGE_END) {
        handleLargeMessageEnd((ReplicationLargeMessageEndMessage) packet);
      } else if (type == PacketImpl.REPLICATION_START_FINISH_SYNC) {
        response = handleStartReplicationSynchronization((ReplicationStartSyncMessage) packet);
      } else if (type == PacketImpl.REPLICATION_SYNC_FILE) {
        handleReplicationSynchronization((ReplicationSyncFileMessage) packet);
      } else if (type == PacketImpl.REPLICATION_SCHEDULED_FAILOVER) {
        handleLiveStopping((ReplicationLiveIsStoppingMessage) packet);
      } else if (type == PacketImpl.BACKUP_REGISTRATION_FAILED) {
        handleFatalError((BackupReplicationStartFailedMessage) packet);
      } else {
        ActiveMQServerLogger.LOGGER.invalidPacketForReplication(packet);
      }
    } catch (ActiveMQException e) {
      ActiveMQServerLogger.LOGGER.errorHandlingReplicationPacket(e, packet);
      response = new ActiveMQExceptionMessage(e);
    } catch (Exception e) {
      ActiveMQServerLogger.LOGGER.errorHandlingReplicationPacket(e, packet);
      response =
          new ActiveMQExceptionMessage(ActiveMQMessageBundle.BUNDLE.replicationUnhandledError(e));
    }
    channel.send(response);
  }

  /** @param packet */
  private void handleFatalError(BackupReplicationStartFailedMessage packet) {
    ActiveMQServerLogger.LOGGER.errorStartingReplication(packet.getRegistrationProblem());
    server.stopTheServer(false);
  }

  /**
   * @param packet
   * @throws ActiveMQException
   */
  private void handleLiveStopping(ReplicationLiveIsStoppingMessage packet)
      throws ActiveMQException {
    activation.remoteFailOver(packet.isFinalMessage());
  }

  @Override
  public boolean isStarted() {
    return started;
  }

  @Override
  public synchronized void start() throws Exception {
    Configuration config = server.getConfiguration();
    try {
      storageManager = server.getStorageManager();
      storageManager.start();

      server.getManagementService().setStorageManager(storageManager);

      journalsHolder.put(JournalContent.BINDINGS, storageManager.getBindingsJournal());
      journalsHolder.put(JournalContent.MESSAGES, storageManager.getMessageJournal());

      for (JournalContent jc : EnumSet.allOf(JournalContent.class)) {
        filesReservedForSync.put(jc, new HashMap<Long, JournalSyncFile>());
        // We only need to load internal structures on the backup...
        journalLoadInformation[jc.typeByte] =
            journalsHolder.get(jc).loadSyncOnly(JournalState.SYNCING);
      }

      pageManager =
          new PagingManagerImpl(
              new PagingStoreFactoryNIO(
                  storageManager,
                  config.getPagingLocation(),
                  config.getJournalBufferSize_NIO(),
                  server.getScheduledPool(),
                  server.getExecutorFactory(),
                  config.isJournalSyncNonTransactional(),
                  criticalErrorListener),
              server.getAddressSettingsRepository());

      pageManager.start();

      started = true;
    } catch (Exception e) {
      if (server.isStarted()) throw e;
    }
  }

  @Override
  public synchronized void stop() throws Exception {
    if (!started) {
      return;
    }

    // Channel may be null if there isn't a connection to a live server
    if (channel != null) {
      channel.close();
    }

    for (ReplicatedLargeMessage largeMessage : largeMessages.values()) {
      largeMessage.releaseResources();
    }
    largeMessages.clear();

    for (Entry<JournalContent, Map<Long, JournalSyncFile>> entry :
        filesReservedForSync.entrySet()) {
      for (JournalSyncFile filesReserved : entry.getValue().values()) {
        filesReserved.close();
      }
    }

    filesReservedForSync.clear();
    if (journals != null) {
      for (Journal j : journals) {
        if (j instanceof FileWrapperJournal) j.stop();
      }
    }

    for (ConcurrentMap<Integer, Page> map : pageIndex.values()) {
      for (Page page : map.values()) {
        try {
          page.sync();
          page.close();
        } catch (Exception e) {
          ActiveMQServerLogger.LOGGER.errorClosingPageOnReplication(e);
        }
      }
    }
    pageManager.stop();

    pageIndex.clear();
    final CountDownLatch latch = new CountDownLatch(1);
    executor.execute(
        new Runnable() {

          @Override
          public void run() {
            latch.countDown();
          }
        });
    latch.await(30, TimeUnit.SECONDS);

    // Storage needs to be the last to stop
    storageManager.stop();

    started = false;
  }

  public Channel getChannel() {
    return channel;
  }

  public void setChannel(final Channel channel) {
    this.channel = channel;
  }

  public void compareJournalInformation(final JournalLoadInformation[] journalInformation)
      throws ActiveMQException {
    if (!activation.isRemoteBackupUpToDate()) {
      throw ActiveMQMessageBundle.BUNDLE.journalsNotInSync();
    }

    if (journalLoadInformation == null
        || journalLoadInformation.length != journalInformation.length) {
      throw ActiveMQMessageBundle.BUNDLE.replicationTooManyJournals();
    }

    for (int i = 0; i < journalInformation.length; i++) {
      if (!journalInformation[i].equals(journalLoadInformation[i])) {
        ActiveMQServerLogger.LOGGER.journalcomparisonMismatch(
            journalParametersToString(journalInformation));
        throw ActiveMQMessageBundle.BUNDLE.replicationTooManyJournals();
      }
    }
  }

  /** Used on tests only. To simulate missing page deletes */
  public void setDeletePages(final boolean deletePages) {
    this.deletePages = deletePages;
  }

  /** @param journalInformation */
  private String journalParametersToString(final JournalLoadInformation[] journalInformation) {
    return "**********************************************************\n"
        + "parameters:\n"
        + "BindingsImpl = "
        + journalInformation[0]
        + "\n"
        + "Messaging = "
        + journalInformation[1]
        + "\n"
        + "**********************************************************"
        + "\n"
        + "Expected:"
        + "\n"
        + "BindingsImpl = "
        + journalLoadInformation[0]
        + "\n"
        + "Messaging = "
        + journalLoadInformation[1]
        + "\n"
        + "**********************************************************";
  }

  private void finishSynchronization(String liveID) throws Exception {
    for (JournalContent jc : EnumSet.allOf(JournalContent.class)) {
      Journal journal = journalsHolder.remove(jc);
      journal.synchronizationLock();
      try {
        // files should be already in place.
        filesReservedForSync.remove(jc);
        registerJournal(jc.typeByte, journal);
        journal.stop();
        journal.start();
        journal.loadSyncOnly(JournalState.SYNCING_UP_TO_DATE);
      } finally {
        journal.synchronizationUnlock();
      }
    }
    ByteBuffer buffer = ByteBuffer.allocate(4 * 1024);
    for (Entry<Long, ReplicatedLargeMessage> entry : largeMessages.entrySet()) {
      ReplicatedLargeMessage lm = entry.getValue();
      if (lm instanceof LargeServerMessageInSync) {
        LargeServerMessageInSync lmSync = (LargeServerMessageInSync) lm;
        lmSync.joinSyncedData(buffer);
      }
    }

    journalsHolder = null;
    backupQuorum.liveIDSet(liveID);
    activation.setRemoteBackupUpToDate();
    ActiveMQServerLogger.LOGGER.backupServerSynched(server);
    return;
  }

  /**
   * Receives 'raw' journal/page/large-message data from live server for synchronization of logs.
   *
   * @param msg
   * @throws Exception
   */
  private synchronized void handleReplicationSynchronization(ReplicationSyncFileMessage msg)
      throws Exception {
    Long id = Long.valueOf(msg.getId());
    byte[] data = msg.getData();
    SequentialFile channel1;
    switch (msg.getFileType()) {
      case LARGE_MESSAGE:
        {
          ReplicatedLargeMessage largeMessage = lookupLargeMessage(id, false);
          if (!(largeMessage instanceof LargeServerMessageInSync)) {
            ActiveMQServerLogger.LOGGER.largeMessageIncompatible();
            return;
          }
          LargeServerMessageInSync largeMessageInSync = (LargeServerMessageInSync) largeMessage;
          channel1 = largeMessageInSync.getSyncFile();
          break;
        }
      case PAGE:
        {
          Page page = getPage(msg.getPageStore(), (int) msg.getId());
          channel1 = page.getFile();
          break;
        }
      case JOURNAL:
        {
          JournalSyncFile journalSyncFile =
              filesReservedForSync.get(msg.getJournalContent()).get(id);
          FileChannel channel2 = journalSyncFile.getChannel();
          if (data == null) {
            channel2.close();
            return;
          }
          channel2.write(ByteBuffer.wrap(data));
          return;
        }
      default:
        throw ActiveMQMessageBundle.BUNDLE.replicationUnhandledFileType(msg.getFileType());
    }

    if (data == null) {
      channel1.close();
      return;
    }

    if (!channel1.isOpen()) {
      channel1.open();
    }
    channel1.writeDirect(ByteBuffer.wrap(data), true);
  }

  /**
   * Reserves files (with the given fileID) in the specified journal, and places a {@link
   * FileWrapperJournal} in place to store messages while synchronization is going on.
   *
   * @param packet
   * @throws Exception
   * @return if the incoming packet indicates the synchronization is finished then return an
   *     acknowledgement otherwise return an empty response
   */
  private ReplicationResponseMessageV2 handleStartReplicationSynchronization(
      final ReplicationStartSyncMessage packet) throws Exception {
    ReplicationResponseMessageV2 replicationResponseMessage = new ReplicationResponseMessageV2();
    if (activation.isRemoteBackupUpToDate()) {
      throw ActiveMQMessageBundle.BUNDLE.replicationBackupUpToDate();
    }

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

      if (packet.isSynchronizationFinished()) {
        finishSynchronization(packet.getNodeID());
        replicationResponseMessage.setSynchronizationIsFinishedAcknowledgement(true);
        return replicationResponseMessage;
      }

      switch (packet.getDataType()) {
        case LargeMessages:
          for (long msgID : packet.getFileIds()) {
            createLargeMessage(msgID, true);
          }
          break;
        case JournalBindings:
        case JournalMessages:
          if (wantedFailBack && !packet.isServerToFailBack()) {
            ActiveMQServerLogger.LOGGER.autoFailBackDenied();
          }

          final JournalContent journalContent =
              SyncDataType.getJournalContentType(packet.getDataType());
          final Journal journal = journalsHolder.get(journalContent);

          if (packet.getNodeID() != null) {
            // At the start of replication, we still do not know which is the nodeID that the live
            // uses.
            // This is the point where the backup gets this information.
            backupQuorum.liveIDSet(packet.getNodeID());
          }
          Map<Long, JournalSyncFile> mapToFill = filesReservedForSync.get(journalContent);

          for (Entry<Long, JournalFile> entry :
              journal.createFilesForBackupSync(packet.getFileIds()).entrySet()) {
            mapToFill.put(entry.getKey(), new JournalSyncFile(entry.getValue()));
          }
          FileWrapperJournal syncJournal = new FileWrapperJournal(journal);
          registerJournal(journalContent.typeByte, syncJournal);
          break;
        default:
          throw ActiveMQMessageBundle.BUNDLE.replicationUnhandledDataType();
      }
    }

    return replicationResponseMessage;
  }

  private void handleLargeMessageEnd(final ReplicationLargeMessageEndMessage packet) {
    final ReplicatedLargeMessage message = lookupLargeMessage(packet.getMessageId(), true);
    if (message != null) {
      executor.execute(
          new Runnable() {
            @Override
            public void run() {
              try {
                message.deleteFile();
              } catch (Exception e) {
                ActiveMQServerLogger.LOGGER.errorDeletingLargeMessage(e, packet.getMessageId());
              }
            }
          });
    }
  }

  /** @param packet */
  private void handleLargeMessageWrite(final ReplicationLargeMessageWriteMessage packet)
      throws Exception {
    ReplicatedLargeMessage message = lookupLargeMessage(packet.getMessageId(), false);
    if (message != null) {
      message.addBytes(packet.getBody());
    }
  }

  private ReplicatedLargeMessage lookupLargeMessage(final long messageId, final boolean delete) {
    ReplicatedLargeMessage message;

    if (delete) {
      message = largeMessages.remove(messageId);
    } else {
      message = largeMessages.get(messageId);
      if (message == null) {
        // No warnings if it's a delete, as duplicate deletes may be sent repeatedly.
        ActiveMQServerLogger.LOGGER.largeMessageNotAvailable(messageId);
      }
    }

    return message;
  }

  /** @param packet */
  private void handleLargeMessageBegin(final ReplicationLargeMessageBeginMessage packet) {
    final long id = packet.getMessageId();
    createLargeMessage(id, false);
    ActiveMQServerLogger.LOGGER.trace("Receiving Large Message " + id + " on backup");
  }

  private void createLargeMessage(final long id, boolean liveToBackupSync) {
    ReplicatedLargeMessage msg;
    if (liveToBackupSync) {
      msg = new LargeServerMessageInSync(storageManager);
    } else {
      msg = storageManager.createLargeMessage();
    }

    msg.setDurable(true);
    msg.setMessageID(id);
    largeMessages.put(id, msg);
  }

  /** @param packet */
  private void handleCommitRollback(final ReplicationCommitMessage packet) throws Exception {
    Journal journalToUse = getJournal(packet.getJournalID());
    if (packet.isRollback()) {
      journalToUse.appendRollbackRecord(packet.getTxId(), noSync);
    } else {
      journalToUse.appendCommitRecord(packet.getTxId(), noSync);
    }
  }

  /** @param packet */
  private void handlePrepare(final ReplicationPrepareMessage packet) throws Exception {
    Journal journalToUse = getJournal(packet.getJournalID());
    journalToUse.appendPrepareRecord(packet.getTxId(), packet.getRecordData(), noSync);
  }

  /** @param packet */
  private void handleAppendDeleteTX(final ReplicationDeleteTXMessage packet) throws Exception {
    Journal journalToUse = getJournal(packet.getJournalID());

    journalToUse.appendDeleteRecordTransactional(
        packet.getTxId(), packet.getId(), packet.getRecordData());
  }

  /** @param packet */
  private void handleAppendDelete(final ReplicationDeleteMessage packet) throws Exception {
    Journal journalToUse = getJournal(packet.getJournalID());
    journalToUse.appendDeleteRecord(packet.getId(), noSync);
  }

  /** @param packet */
  private void handleAppendAddTXRecord(final ReplicationAddTXMessage packet) throws Exception {
    Journal journalToUse = getJournal(packet.getJournalID());

    if (packet.getOperation() == ADD_OPERATION_TYPE.UPDATE) {
      journalToUse.appendUpdateRecordTransactional(
          packet.getTxId(), packet.getId(), packet.getRecordType(), packet.getRecordData());
    } else {
      journalToUse.appendAddRecordTransactional(
          packet.getTxId(), packet.getId(), packet.getRecordType(), packet.getRecordData());
    }
  }

  /**
   * @param packet
   * @throws Exception
   */
  private void handleAppendAddRecord(final ReplicationAddMessage packet) throws Exception {
    Journal journalToUse = getJournal(packet.getJournalID());
    if (packet.getRecord() == ADD_OPERATION_TYPE.UPDATE) {
      if (ReplicationEndpoint.trace) {
        ActiveMQServerLogger.LOGGER.trace("Endpoint appendUpdate id = " + packet.getId());
      }
      journalToUse.appendUpdateRecord(
          packet.getId(), packet.getJournalRecordType(), packet.getRecordData(), noSync);
    } else {
      if (ReplicationEndpoint.trace) {
        ActiveMQServerLogger.LOGGER.trace("Endpoint append id = " + packet.getId());
      }
      journalToUse.appendAddRecord(
          packet.getId(), packet.getJournalRecordType(), packet.getRecordData(), noSync);
    }
  }

  /** @param packet */
  private void handlePageEvent(final ReplicationPageEventMessage packet) throws Exception {
    ConcurrentMap<Integer, Page> pages = getPageMap(packet.getStoreName());

    Page page = pages.remove(packet.getPageNumber());

    if (page == null) {
      page = getPage(packet.getStoreName(), packet.getPageNumber());
    }

    if (page != null) {
      if (packet.isDelete()) {
        if (deletePages) {
          page.delete(null);
        }
      } else {
        page.close();
      }
    }
  }

  /** @param packet */
  private void handlePageWrite(final ReplicationPageWriteMessage packet) throws Exception {
    PagedMessage pgdMessage = packet.getPagedMessage();
    pgdMessage.initMessage(storageManager);
    ServerMessage msg = pgdMessage.getMessage();
    Page page = getPage(msg.getAddress(), packet.getPageNumber());
    page.write(pgdMessage);
  }

  private ConcurrentMap<Integer, Page> getPageMap(final SimpleString storeName) {
    ConcurrentMap<Integer, Page> resultIndex = pageIndex.get(storeName);

    if (resultIndex == null) {
      resultIndex = new ConcurrentHashMap<>();
      ConcurrentMap<Integer, Page> mapResult = pageIndex.putIfAbsent(storeName, resultIndex);
      if (mapResult != null) {
        resultIndex = mapResult;
      }
    }

    return resultIndex;
  }

  private Page getPage(final SimpleString storeName, final int pageId) throws Exception {
    ConcurrentMap<Integer, Page> map = getPageMap(storeName);

    Page page = map.get(pageId);

    if (page == null) {
      page = newPage(pageId, storeName, map);
    }

    return page;
  }

  /**
   * @param pageId
   * @param map
   * @return
   */
  private synchronized Page newPage(
      final int pageId, final SimpleString storeName, final ConcurrentMap<Integer, Page> map)
      throws Exception {
    Page page = map.get(pageId);

    if (page == null) {
      page = pageManager.getPageStore(storeName).createPage(pageId);
      page.open();
      map.put(pageId, page);
    }

    return page;
  }

  /**
   * @param journalID
   * @return
   */
  private Journal getJournal(final byte journalID) {
    return journals[journalID];
  }

  public static final class JournalSyncFile {

    private FileChannel channel;
    private final File file;
    private FileOutputStream fos;

    public JournalSyncFile(JournalFile jFile) throws Exception {
      SequentialFile seqFile = jFile.getFile();
      file = seqFile.getJavaFile();
      seqFile.close();
    }

    synchronized FileChannel getChannel() throws Exception {
      if (channel == null) {
        fos = new FileOutputStream(file);
        channel = fos.getChannel();
      }
      return channel;
    }

    synchronized void close() throws IOException {
      if (fos != null) fos.close();
      if (channel != null) channel.close();
    }

    @Override
    public String toString() {
      return "JournalSyncFile(file=" + file.getAbsolutePath() + ")";
    }
  }

  /**
   * Sets the quorumManager used by the server in the replicationEndpoint. It is used to inform the
   * backup server of the live's nodeID.
   *
   * @param backupQuorum
   */
  public synchronized void setBackupQuorum(SharedNothingBackupQuorum backupQuorum) {
    this.backupQuorum = backupQuorum;
  }

  /** @param executor2 */
  public void setExecutor(Executor executor2) {
    this.executor = executor2;
  }
}
public class CoreProtocolManager implements ProtocolManager<Interceptor> {
  private static final boolean isTrace = ActiveMQServerLogger.LOGGER.isTraceEnabled();

  private final ActiveMQServer server;

  private final List<Interceptor> incomingInterceptors;

  private final List<Interceptor> outgoingInterceptors;

  private final CoreProtocolManagerFactory protocolManagerFactory;

  public CoreProtocolManager(
      final CoreProtocolManagerFactory factory,
      final ActiveMQServer server,
      final List<Interceptor> incomingInterceptors,
      List<Interceptor> outgoingInterceptors) {
    this.protocolManagerFactory = factory;

    this.server = server;

    this.incomingInterceptors = incomingInterceptors;

    this.outgoingInterceptors = outgoingInterceptors;
  }

  @Override
  public ProtocolManagerFactory<Interceptor> getFactory() {
    return protocolManagerFactory;
  }

  @Override
  public void updateInterceptors(List<BaseInterceptor> incoming, List<BaseInterceptor> outgoing) {
    this.incomingInterceptors.clear();
    this.incomingInterceptors.addAll(getFactory().filterInterceptors(incoming));

    this.outgoingInterceptors.clear();
    this.outgoingInterceptors.addAll(getFactory().filterInterceptors(outgoing));
  }

  /**
   * no need to implement this now
   *
   * @return
   */
  @Override
  public MessageConverter getConverter() {
    return null;
  }

  public ConnectionEntry createConnectionEntry(
      final Acceptor acceptorUsed, final Connection connection) {
    final Configuration config = server.getConfiguration();

    Executor connectionExecutor = server.getExecutorFactory().getExecutor();

    final CoreRemotingConnection rc =
        new RemotingConnectionImpl(
            ServerPacketDecoder.INSTANCE,
            connection,
            incomingInterceptors,
            outgoingInterceptors,
            config.isAsyncConnectionExecutionEnabled() ? connectionExecutor : null,
            server.getNodeID());

    Channel channel1 = rc.getChannel(CHANNEL_ID.SESSION.id, -1);

    ChannelHandler handler = new ActiveMQPacketHandler(this, server, channel1, rc);

    channel1.setHandler(handler);

    long ttl = ActiveMQClient.DEFAULT_CONNECTION_TTL;

    if (config.getConnectionTTLOverride() != -1) {
      ttl = config.getConnectionTTLOverride();
    }

    final ConnectionEntry entry =
        new ConnectionEntry(rc, connectionExecutor, System.currentTimeMillis(), ttl);

    final Channel channel0 = rc.getChannel(ChannelImpl.CHANNEL_ID.PING.id, -1);

    channel0.setHandler(new LocalChannelHandler(config, entry, channel0, acceptorUsed, rc));

    server
        .getClusterManager()
        .addClusterChannelHandler(
            rc.getChannel(CHANNEL_ID.CLUSTER.id, -1), acceptorUsed, rc, server.getActivation());

    return entry;
  }

  private final Map<String, ServerSessionPacketHandler> sessionHandlers =
      new ConcurrentHashMap<String, ServerSessionPacketHandler>();

  ServerSessionPacketHandler getSessionHandler(final String sessionName) {
    return sessionHandlers.get(sessionName);
  }

  void addSessionHandler(final String name, final ServerSessionPacketHandler handler) {
    sessionHandlers.put(name, handler);
  }

  public void removeHandler(final String name) {
    sessionHandlers.remove(name);
  }

  public void handleBuffer(RemotingConnection connection, ActiveMQBuffer buffer) {}

  @Override
  public void addChannelHandlers(ChannelPipeline pipeline) {
    pipeline.addLast("activemq-decoder", new ActiveMQFrameDecoder2());
  }

  @Override
  public boolean isProtocol(byte[] array) {
    String frameStart = new String(array, StandardCharsets.US_ASCII);
    return frameStart.startsWith("ACTIVEMQ");
  }

  @Override
  public void handshake(NettyServerConnection connection, ActiveMQBuffer buffer) {
    // if we are not an old client then handshake
    if (buffer.getByte(0) == 'A'
        && buffer.getByte(1) == 'R'
        && buffer.getByte(2) == 'T'
        && buffer.getByte(3) == 'E'
        && buffer.getByte(4) == 'M'
        && buffer.getByte(5) == 'I'
        && buffer.getByte(6) == 'S') {
      // todo add some handshaking
      buffer.readBytes(7);
    }
  }

  @Override
  public String toString() {
    return "CoreProtocolManager(server=" + server + ")";
  }

  private class LocalChannelHandler implements ChannelHandler {
    private final Configuration config;
    private final ConnectionEntry entry;
    private final Channel channel0;
    private final Acceptor acceptorUsed;
    private final CoreRemotingConnection rc;

    public LocalChannelHandler(
        final Configuration config,
        final ConnectionEntry entry,
        final Channel channel0,
        final Acceptor acceptorUsed,
        final CoreRemotingConnection rc) {
      this.config = config;
      this.entry = entry;
      this.channel0 = channel0;
      this.acceptorUsed = acceptorUsed;
      this.rc = rc;
    }

    public void handlePacket(final Packet packet) {
      if (packet.getType() == PacketImpl.PING) {
        Ping ping = (Ping) packet;

        if (config.getConnectionTTLOverride() == -1) {
          // Allow clients to specify connection ttl
          entry.ttl = ping.getConnectionTTL();
        }

        // Just send a ping back
        channel0.send(packet);
      } else if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY
          || packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) {
        SubscribeClusterTopologyUpdatesMessage msg =
            (SubscribeClusterTopologyUpdatesMessage) packet;

        if (packet.getType() == PacketImpl.SUBSCRIBE_TOPOLOGY_V2) {
          channel0
              .getConnection()
              .setClientVersion(
                  ((SubscribeClusterTopologyUpdatesMessageV2) msg).getClientVersion());
        }

        final ClusterTopologyListener listener =
            new ClusterTopologyListener() {
              @Override
              public void nodeUP(final TopologyMember topologyMember, final boolean last) {
                try {
                  final Pair<TransportConfiguration, TransportConfiguration> connectorPair =
                      BackwardsCompatibilityUtils.getTCPair(
                          channel0.getConnection().getClientVersion(), topologyMember);

                  final String nodeID = topologyMember.getNodeId();
                  // Using an executor as most of the notifications on the Topology
                  // may come from a channel itself
                  // What could cause deadlocks
                  entry.connectionExecutor.execute(
                      new Runnable() {
                        public void run() {
                          if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V3)) {
                            channel0.send(
                                new ClusterTopologyChangeMessage_V3(
                                    topologyMember.getUniqueEventID(),
                                    nodeID,
                                    topologyMember.getBackupGroupName(),
                                    topologyMember.getScaleDownGroupName(),
                                    connectorPair,
                                    last));
                          } else if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
                            channel0.send(
                                new ClusterTopologyChangeMessage_V2(
                                    topologyMember.getUniqueEventID(),
                                    nodeID,
                                    topologyMember.getBackupGroupName(),
                                    connectorPair,
                                    last));
                          } else {
                            channel0.send(
                                new ClusterTopologyChangeMessage(nodeID, connectorPair, last));
                          }
                        }
                      });
                } catch (RejectedExecutionException ignored) {
                  // this could happen during a shutdown and we don't care, if we lost a nodeDown
                  // during a shutdown
                  // what can we do anyways?
                }
              }

              @Override
              public void nodeDown(final long uniqueEventID, final String nodeID) {
                // Using an executor as most of the notifications on the Topology
                // may come from a channel itself
                // What could cause deadlocks
                try {
                  entry.connectionExecutor.execute(
                      new Runnable() {
                        public void run() {
                          if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
                            channel0.send(
                                new ClusterTopologyChangeMessage_V2(uniqueEventID, nodeID));
                          } else {
                            channel0.send(new ClusterTopologyChangeMessage(nodeID));
                          }
                        }
                      });
                } catch (RejectedExecutionException ignored) {
                  // this could happen during a shutdown and we don't care, if we lost a nodeDown
                  // during a shutdown
                  // what can we do anyways?
                }
              }

              @Override
              public String toString() {
                return "Remote Proxy on channel "
                    + Integer.toHexString(System.identityHashCode(this));
              }
            };

        if (acceptorUsed.getClusterConnection() != null) {
          acceptorUsed.getClusterConnection().addClusterTopologyListener(listener);

          rc.addCloseListener(
              new CloseListener() {
                public void connectionClosed() {
                  acceptorUsed.getClusterConnection().removeClusterTopologyListener(listener);
                }
              });
        } else {
          // if not clustered, we send a single notification to the client containing the node-id
          // where the server is connected to
          // This is done this way so Recovery discovery could also use the node-id for
          // non-clustered setups
          entry.connectionExecutor.execute(
              new Runnable() {
                public void run() {
                  String nodeId = server.getNodeID().toString();
                  Pair<TransportConfiguration, TransportConfiguration> emptyConfig =
                      new Pair<TransportConfiguration, TransportConfiguration>(null, null);
                  if (channel0.supports(PacketImpl.CLUSTER_TOPOLOGY_V2)) {
                    channel0.send(
                        new ClusterTopologyChangeMessage_V2(
                            System.currentTimeMillis(), nodeId, null, emptyConfig, true));
                  } else {
                    channel0.send(new ClusterTopologyChangeMessage(nodeId, emptyConfig, true));
                  }
                }
              });
        }
      }
    }

    private Pair<TransportConfiguration, TransportConfiguration> getPair(
        TransportConfiguration conn, boolean isBackup) {
      if (isBackup) {
        return new Pair<TransportConfiguration, TransportConfiguration>(null, conn);
      }
      return new Pair<TransportConfiguration, TransportConfiguration>(conn, null);
    }
  }
}