Package org.hornetq.core.paging.cursor.impl

Source Code of org.hornetq.core.paging.cursor.impl.PageSubscriptionImpl

/*
* Copyright 2010 Red Hat, Inc.
* Red Hat licenses this file to you under the Apache License, version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*    http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.  See the License for the specific language governing
* permissions and limitations under the License.
*/

package org.hornetq.core.paging.cursor.impl;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.hornetq.core.filter.Filter;
import org.hornetq.core.journal.IOAsyncTask;
import org.hornetq.core.paging.PageTransactionInfo;
import org.hornetq.core.paging.PagedMessage;
import org.hornetq.core.paging.PagingStore;
import org.hornetq.core.paging.cursor.PageCache;
import org.hornetq.core.paging.cursor.PageCursorProvider;
import org.hornetq.core.paging.cursor.PagePosition;
import org.hornetq.core.paging.cursor.PageSubscription;
import org.hornetq.core.paging.cursor.PageSubscriptionCounter;
import org.hornetq.core.paging.cursor.PagedReference;
import org.hornetq.core.paging.impl.Page;
import org.hornetq.core.persistence.StorageManager;
import org.hornetq.core.server.HornetQServerLogger;
import org.hornetq.core.server.MessageReference;
import org.hornetq.core.server.Queue;
import org.hornetq.core.server.ServerMessage;
import org.hornetq.core.transaction.Transaction;
import org.hornetq.core.transaction.TransactionOperationAbstract;
import org.hornetq.core.transaction.TransactionPropertyIndexes;
import org.hornetq.core.transaction.impl.TransactionImpl;
import org.hornetq.utils.ConcurrentHashSet;
import org.hornetq.utils.FutureLatch;
import org.hornetq.utils.LinkedListIterator;

/**
* A PageCursorImpl
*
* @author <a href="mailto:clebert.suconic@jboss.com">Clebert Suconic</a>
*/
final class PageSubscriptionImpl implements PageSubscription
{
   private final boolean isTrace = HornetQServerLogger.LOGGER.isTraceEnabled();

   private boolean empty = true;

   // Number of scheduled cleanups, to avoid too many schedules
   private final AtomicInteger scheduledCleanupCount = new AtomicInteger(0);

   private volatile boolean autoCleanup = true;

   private final StorageManager store;

   private final long cursorId;

   private Queue queue;

   private final boolean persistent;

   private final Filter filter;

   private final PagingStore pageStore;

   private final PageCursorProvider cursorProvider;

   private volatile PagePosition lastAckedPosition;

   private List<PagePosition> recoveredACK;

   private final SortedMap<Long, PageCursorInfo> consumedPages = new TreeMap<Long, PageCursorInfo>();

   private final PageSubscriptionCounter counter;

   private final Executor executor;

   private final AtomicLong deliveredCount = new AtomicLong(0);

   // We only store the position for redeliveries. They will be read from the SoftCache again during delivery.
   private final java.util.Queue<PagePosition> redeliveries = new LinkedList<PagePosition>();

   PageSubscriptionImpl(final PageCursorProvider cursorProvider,
                        final PagingStore pageStore,
                        final StorageManager store,
                        final Executor executor,
                        final Filter filter,
                        final long cursorId,
                        final boolean persistent)
   {
      this.pageStore = pageStore;
      this.store = store;
      this.cursorProvider = cursorProvider;
      this.cursorId = cursorId;
      this.executor = executor;
      this.filter = filter;
      this.persistent = persistent;
      this.counter = new PageSubscriptionCounterImpl(store, this, executor, persistent, cursorId);
   }

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

   public PagingStore getPagingStore()
   {
      return pageStore;
   }

   public Queue getQueue()
   {
      return queue;
   }

   public boolean isPaging()
   {
      return pageStore.isPaging();
   }

   public void setQueue(Queue queue)
   {
      this.queue = queue;
   }

   public void disableAutoCleanup()
   {
      autoCleanup = false;
   }

   public void enableAutoCleanup()
   {
      autoCleanup = true;
   }

   public PageCursorProvider getProvider()
   {
      return cursorProvider;
   }

   public void notEmpty()
   {
      synchronized (consumedPages)
      {
         this.empty = false;
      }

   }

   public void bookmark(PagePosition position) throws Exception
   {
      PageCursorInfo cursorInfo = getPageInfo(position);

      if (position.getMessageNr() > 0)
      {
         cursorInfo.confirmed.addAndGet(position.getMessageNr());
      }

      confirmPosition(position);
   }

   public long getMessageCount()
   {
      if (empty)
      {
         return 0;
      }
      else
      {
         return counter.getValue() - deliveredCount.get();
      }
   }

   public PageSubscriptionCounter getCounter()
   {
      return counter;
   }

   /**
    * A page marked as complete will be ignored until it's cleared.
    * <p>
    * Usually paging is a stream of messages but in certain scenarios (such as a pending prepared
    * TX) we may have big holes on the page streaming, and we will need to ignore such pages on the
    * cursor/subscription.
    */
   public void reloadPageCompletion(PagePosition position)
   {
      PageCursorInfo info = new PageCursorInfo(position.getPageNr(), position.getMessageNr(), null);
      info.setCompleteInfo(position);
      synchronized (consumedPages)
      {
         consumedPages.put(Long.valueOf(position.getPageNr()), info);
      }
   }

   public void scheduleCleanupCheck()
   {
      if (autoCleanup)
      {
         if (scheduledCleanupCount.get() > 2)
         {
            return;
         }

         scheduledCleanupCount.incrementAndGet();
         executor.execute(new Runnable()
         {

            public void run()
            {
               try
               {
                  cleanupEntries(false);
               }
               catch (Exception e)
               {
                  HornetQServerLogger.LOGGER.problemCleaningCursorPages(e);
               }
               finally
               {
                  scheduledCleanupCount.decrementAndGet();
               }
            }
         });
      }
   }

   public void onPageModeCleared(Transaction tx) throws Exception
   {
      if (counter != null)
      {
         // this could be null on testcases
         counter.delete(tx);
      }
      this.empty = true;
   }

   /**
    * It will cleanup all the records for completed pages
    * */
   public void cleanupEntries(final boolean completeDelete) throws Exception
   {
      if (completeDelete)
      {
         counter.delete();
      }
      Transaction tx = new TransactionImpl(store);

      boolean persist = false;

      final ArrayList<PageCursorInfo> completedPages = new ArrayList<PageCursorInfo>();

      // First get the completed pages using a lock
      synchronized (consumedPages)
      {
         // lastAckedPosition = null means no acks were done yet, so we are not ready to cleanup
         if (lastAckedPosition == null)
         {
            return;
         }

         for (Entry<Long, PageCursorInfo> entry : consumedPages.entrySet())
         {
            PageCursorInfo info = entry.getValue();

            if (info.isDone() && !info.isPendingDelete())
            {
               Page currentPage = pageStore.getCurrentPage();

               if (currentPage != null && entry.getKey() == pageStore.getCurrentPage().getPageId() &&
                  currentPage.isLive())
               {
                  HornetQServerLogger.LOGGER.trace("We can't clear page " + entry.getKey() +
                     " now since it's the current page");
               }
               else
               {
                  info.setPendingDelete();
                  completedPages.add(entry.getValue());
               }
            }
         }
      }

      for (PageCursorInfo infoPG : completedPages)
      {
         // HORNETQ-1017: There are a few cases where a pending transaction might set a big hole on the page system
         //               where we need to ignore these pages in case of a restart.
         //               for that reason when we delete complete ACKs we store a single record per page file that will
         //               be removed once the page file is deleted
         //               notice also that this record is added as part of the same transaction where the information is deleted.
         //               In case of a TX Failure (a crash on the server) this will be recovered on the next cleanup once the
         //               server is restarted.
         // first will mark the page as complete
         if (isPersistent())
         {
            PagePosition completePage = new PagePositionImpl(infoPG.getPageId(), infoPG.getNumberOfMessages());
            infoPG.setCompleteInfo(completePage);
            store.storePageCompleteTransactional(tx.getID(), this.getId(), completePage);
            if (!persist)
            {
               persist = true;
               tx.setContainsPersistent();
            }
         }

         // it will delete the page ack records
         for (PagePosition pos : infoPG.acks)
         {
            if (pos.getRecordID() >= 0)
            {
               store.deleteCursorAcknowledgeTransactional(tx.getID(), pos.getRecordID());
               if (!persist)
               {
                  // only need to set it once
                  tx.setContainsPersistent();
                  persist = true;
               }
            }
         }

         infoPG.acks.clear();
      }

      tx.addOperation(new TransactionOperationAbstract()
      {

         @Override
         public void afterCommit(final Transaction tx1)
         {
            executor.execute(new Runnable()
            {

               public void run()
               {
                  if (!completeDelete)
                  {
                     cursorProvider.scheduleCleanup();
                  }
               }
            });
         }
      });

      tx.commit();

   }

   @Override
   public String toString()
   {
      return "PageSubscriptionImpl [cursorId=" + cursorId + ", queue=" + queue + ", filter = " + filter + "]";
   }

   private PagedReference getReference(PagePosition pos)
   {
      return cursorProvider.newReference(pos, cursorProvider.getMessage(pos), this);
   }

   @Override
   public LinkedListIterator<PagedReference> iterator()
   {
      return new CursorIterator();
   }

   private PagedReference internalGetNext(final PagePosition pos)
   {
      PagePosition retPos = pos.nextMessage();

      PageCache cache = cursorProvider.getPageCache(pos.getPageNr());

      if (cache != null && !cache.isLive() && retPos.getMessageNr() >= cache.getNumberOfMessages())
      {
         // The next message is beyond what's available at the current page, so we need to move to the next page
         cache = null;
      }

      // it will scan for the next available page
      while (cache == null && retPos.getPageNr() <= pageStore.getCurrentWritingPage())
      {
         retPos = moveNextPage(retPos);

         cache = cursorProvider.getPageCache(retPos.getPageNr());
      }

      if (cache == null)
      {
         return null;
      }
      else
      {
         PagedMessage serverMessage = cache.getMessage(retPos.getMessageNr());

         if (serverMessage != null)
         {
            return cursorProvider.newReference(retPos, serverMessage, this);
         }
         else
         {
            return null;
         }
      }
   }

   private PagePosition moveNextPage(final PagePosition pos)
   {
      PagePosition retPos = pos;
      while (true)
      {
         retPos = retPos.nextPage();
         synchronized (consumedPages)
         {
            PageCursorInfo pageInfo = consumedPages.get(retPos.getPageNr());
            // any deleted or complete page will be ignored on the moveNextPage, we will just keep going
            if (pageInfo == null || (!pageInfo.isPendingDelete() && pageInfo.getCompleteInfo() == null))
            {
               return retPos;
            }
         }
      }
   }

   private boolean routed(PagedMessage message)
   {
      long id = getId();

      for (long qid : message.getQueueIDs())
      {
         if (qid == id)
         {
            return true;
         }
      }
      return false;
   }

   /**
    *
    */
   private synchronized PagePosition getStartPosition()
   {
      return new PagePositionImpl(pageStore.getFirstPage(), -1);
   }

   public void confirmPosition(final Transaction tx, final PagePosition position) throws Exception
   {
      // if the cursor is persistent
      if (persistent)
      {
         store.storeCursorAcknowledgeTransactional(tx.getID(), cursorId, position);
      }
      installTXCallback(tx, position);

   }

   public void ackTx(final Transaction tx, final PagedReference reference) throws Exception
   {
      confirmPosition(tx, reference.getPosition());

      counter.increment(tx, -1);

      PageTransactionInfo txInfo = getPageTransaction(reference);
      if (txInfo != null)
      {
         txInfo.storeUpdate(store, pageStore.getPagingManager(), tx);
      }
   }

   @Override
   public void ack(final PagedReference reference) throws Exception
   {
      // Need to do the ACK and counter atomically (inside a TX) or the counter could get out of sync
      Transaction tx = new TransactionImpl(this.store);
      ackTx(tx, reference);
      tx.commit();
   }

   public void confirmPosition(final PagePosition position) throws Exception
   {
      // if we are dealing with a persistent cursor
      if (persistent)
      {
         store.storeCursorAcknowledge(cursorId, position);
      }

      store.afterCompleteOperations(new IOAsyncTask()
      {
         volatile String error = "";

         @Override
         public void onError(final int errorCode, final String errorMessage)
         {
            error = " errorCode=" + errorCode + ", msg=" + errorMessage;
            HornetQServerLogger.LOGGER.pageSubscriptionError(this, error);
         }

         @Override
         public void done()
         {
            processACK(position);
         }

         @Override
         public String toString()
         {
            return IOAsyncTask.class.getSimpleName() + "(" + PageSubscriptionImpl.class.getSimpleName() + ") " + error;
         }
      });
   }

   @Override
   public long getFirstPage()
   {
      synchronized (consumedPages)
      {
         if (empty && consumedPages.isEmpty())
         {
            return -1;
         }
         long lastPageSeen = 0;
         for (Map.Entry<Long, PageCursorInfo> info : consumedPages.entrySet())
         {
            lastPageSeen = info.getKey();
            if (!info.getValue().isDone() && !info.getValue().isPendingDelete())
            {
               return info.getKey();
            }
         }
         return lastPageSeen;
      }

   }

   public void addPendingDelivery(final PagePosition position)
   {
      getPageInfo(position).incrementPendingTX();
   }

   @Override
   public void redeliver(final PagePosition position)
   {
      synchronized (redeliveries)
      {
         redeliveries.add(position);
      }

      synchronized (consumedPages)
      {
         PageCursorInfo pageInfo = consumedPages.get(position.getPageNr());
         if (pageInfo != null)
         {
            pageInfo.decrementPendingTX();
         }
         else
         {
            // this shouldn't really happen.
         }
      }
   }

   @Override
   public PagedMessage queryMessage(PagePosition pos)
   {
      try
      {
         return cursorProvider.getMessage(pos);
      }
      catch (Exception e)
      {
         throw new RuntimeException(e.getMessage(), e);
      }
   }

   /**
    * Theres no need to synchronize this method as it's only called from journal load on startup
    */
   public void reloadACK(final PagePosition position)
   {
      if (recoveredACK == null)
      {
         recoveredACK = new LinkedList<PagePosition>();
      }

      recoveredACK.add(position);
   }

   @Override
   public void reloadPreparedACK(final Transaction tx, final PagePosition position)
   {
      deliveredCount.incrementAndGet();
      installTXCallback(tx, position);
   }

   @Override
   public void positionIgnored(final PagePosition position)
   {
      processACK(position);
   }

   public void lateDeliveryRollback(PagePosition position)
   {
      PageCursorInfo cursorInfo = processACK(position);
      cursorInfo.decrementPendingTX();
   }

   @Override
   public boolean isComplete(long page)
   {
      synchronized (consumedPages)
      {
         if (empty && consumedPages.isEmpty())
         {
            return true;
         }

         PageCursorInfo info = consumedPages.get(page);

         if (info == null && empty)
         {
            return true;
         }
         else
         {
            return info != null && info.isDone();
         }
      }
   }

   /**
    * All the data associated with the cursor should go away here
    */
   public void destroy() throws Exception
   {
      final long tx = store.generateUniqueID();
      try
      {

         boolean isPersistent = false;

         synchronized (consumedPages)
         {
            for (PageCursorInfo cursor : consumedPages.values())
            {
               for (PagePosition info : cursor.acks)
               {
                  if (info.getRecordID() >= 0)
                  {
                     isPersistent = true;
                     store.deleteCursorAcknowledgeTransactional(tx, info.getRecordID());
                  }
               }
               PagePosition completeInfo = cursor.getCompleteInfo();
               if (completeInfo != null && completeInfo.getRecordID() >= 0)
               {
                  store.deletePageComplete(completeInfo.getRecordID());
                  cursor.setCompleteInfo(null);
               }
            }
         }

         if (isPersistent)
         {
            store.commit(tx);
         }

         cursorProvider.close(this);
      }
      catch (Exception e)
      {
         try
         {
            store.rollback(tx);
         }
         catch (Exception ignored)
         {
            // exception of the exception.. nothing that can be done here
         }
      }
   }

   @Override
   public long getId()
   {
      return cursorId;
   }

   public boolean isPersistent()
   {
      return persistent;
   }

   public void processReload() throws Exception
   {
      if (recoveredACK != null)
      {
         if (isTrace)
         {
            HornetQServerLogger.LOGGER.trace("********** processing reload!!!!!!!");
         }
         Collections.sort(recoveredACK);

         long txDeleteCursorOnReload = -1;

         for (PagePosition pos : recoveredACK)
         {
            lastAckedPosition = pos;
            PageCursorInfo pageInfo = getPageInfo(pos);

            if (pageInfo == null)
            {
               HornetQServerLogger.LOGGER.pageNotFound(pos);
               if (txDeleteCursorOnReload == -1)
               {
                  txDeleteCursorOnReload = store.generateUniqueID();
               }
               store.deleteCursorAcknowledgeTransactional(txDeleteCursorOnReload, pos.getRecordID());
            }
            else
            {
               pageInfo.loadACK(pos);
            }
         }

         if (txDeleteCursorOnReload >= 0)
         {
            store.commit(txDeleteCursorOnReload);
         }

         recoveredACK.clear();
         recoveredACK = null;
      }
   }

   public void flushExecutors()
   {
      FutureLatch future = new FutureLatch();
      executor.execute(future);
      while (!future.await(1000))
      {
         HornetQServerLogger.LOGGER.timedOutFlushingExecutorsPagingCursor(this);
      }
   }

   public void stop()
   {
      flushExecutors();
   }

   public void printDebug()
   {
      printDebug(toString());
   }

   public void printDebug(final String msg)
   {
      System.out.println("Debug information on PageCurorImpl- " + msg);
      for (PageCursorInfo info : consumedPages.values())
      {
         System.out.println(info);
      }
   }


   public void onDeletePage(Page deletedPage) throws Exception
   {
      PageCursorInfo info;
      synchronized (consumedPages)
      {
         info = consumedPages.remove(Long.valueOf(deletedPage.getPageId()));
      }
      if (info != null)
      {
         PagePosition completeInfo = info.getCompleteInfo();
         if (completeInfo != null)
         {
            try
            {
               store.deletePageComplete(completeInfo.getRecordID());
            }
            catch (Exception e)
            {
               HornetQServerLogger.LOGGER.warn("Error while deleting page-complete-record", e);
            }
            info.setCompleteInfo(null);
         }
         for (PagePosition deleteInfo : info.acks)
         {
            if (deleteInfo.getRecordID() >= 0)
            {
               try
               {
                  store.deleteCursorAcknowledge(deleteInfo.getRecordID());
               }
               catch (Exception e)
               {
                  HornetQServerLogger.LOGGER.warn("Error while deleting page-complete-record", e);
               }
            }
         }
         info.acks.clear();
      }
   }

   public Executor getExecutor()
   {
      return executor;
   }


   public void reloadPageInfo(long pageNr)
   {
      getPageInfo(pageNr, true);
   }

   private PageCursorInfo getPageInfo(final PagePosition pos)
   {
      return getPageInfo(pos.getPageNr(), true);
   }

   private PageCursorInfo getPageInfo(final long pageNr, boolean create)
   {
      synchronized (consumedPages)
      {
         PageCursorInfo pageInfo = consumedPages.get(pageNr);

         if (create && pageInfo == null)
         {
            PageCache cache = cursorProvider.getPageCache(pageNr);
            if (cache == null)
            {
               return null;
            }
            pageInfo = new PageCursorInfo(pageNr, cache.getNumberOfMessages(), cache);
            consumedPages.put(pageNr, pageInfo);
         }
         return pageInfo;
      }

   }

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

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

   private boolean match(final ServerMessage message)
   {
      if (filter == null)
      {
         return true;
      }
      else
      {
         return filter.match(message);
      }
   }

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

   // To be called only after the ACK has been processed and guaranteed to be on storage
   // The only exception is on non storage events such as not matching messages
   private PageCursorInfo processACK(final PagePosition pos)
   {
      if (lastAckedPosition == null || pos.compareTo(lastAckedPosition) > 0)
      {
         if (isTrace)
         {
            HornetQServerLogger.LOGGER.trace("a new position is being processed as ACK");
         }
         if (lastAckedPosition != null && lastAckedPosition.getPageNr() != pos.getPageNr())
         {
            if (isTrace)
            {
               HornetQServerLogger.LOGGER.trace("Scheduling cleanup on pageSubscription for address = " + pageStore.getAddress() + " queue = " + this.getQueue().getName());
            }

            // there's a different page being acked, we will do the check right away
            if (autoCleanup)
            {
               scheduleCleanupCheck();
            }
         }
         lastAckedPosition = pos;
      }
      PageCursorInfo info = getPageInfo(pos);

      info.addACK(pos);

      return info;
   }

   /**
    * @param tx
    * @param position
    */
   private void installTXCallback(final Transaction tx, final PagePosition position)
   {
      if (position.getRecordID() >= 0)
      {
         // It needs to persist, otherwise the cursor will return to the fist page position
         tx.setContainsPersistent();
      }

      getPageInfo(position).remove(position);

      PageCursorTX cursorTX = (PageCursorTX)tx.getProperty(TransactionPropertyIndexes.PAGE_CURSOR_POSITIONS);

      if (cursorTX == null)
      {
         cursorTX = new PageCursorTX();
         tx.putProperty(TransactionPropertyIndexes.PAGE_CURSOR_POSITIONS, cursorTX);
         tx.addOperation(cursorTX);
      }

      cursorTX.addPositionConfirmation(this, position);

   }

   private PageTransactionInfo getPageTransaction(final PagedReference reference)
   {
      if (reference.getPagedMessage().getTransactionID() >= 0)
      {
         return pageStore.getPagingManager().getTransaction(reference.getPagedMessage().getTransactionID());
      }
      else
      {
         return null;
      }
   }

   /**
    * A callback from the PageCursorInfo. It will be called when all the messages on a page have been acked
    *
    * @param info
    */
   private void onPageDone(final PageCursorInfo info)
   {
      if (autoCleanup)
      {
         scheduleCleanupCheck();
      }
   }

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

   /**
    * This will hold information about the pending ACKs towards a page.
    * <p>
    * This instance will be released as soon as the entire page is consumed, releasing the memory at
    * that point The ref counts are increased also when a message is ignored for any reason.
    */
   private final class PageCursorInfo
   {
      // Number of messages existent on this page
      private final int numberOfMessages;

      private final long pageId;

      // Confirmed ACKs on this page
      private final Set<PagePosition> acks = Collections.synchronizedSet(new LinkedHashSet<PagePosition>());

      private WeakReference<PageCache> cache;

      private final Set<PagePosition> removedReferences = new ConcurrentHashSet<PagePosition>();

      // The page was live at the time of the creation
      private final boolean wasLive;

      // There's a pending TX to add elements on this page
      private final AtomicInteger pendingTX = new AtomicInteger(0);

      // There's a pending delete on the async IO pipe
      // We're holding this object to avoid delete the pages before the IO is complete,
      // however we can't delete these records again
      private boolean pendingDelete;

      /**
       * This is to be set when all the messages are complete on a given page, and we cleanup the records that are marked on it
       */
      private PagePosition completePage;

      // We need a separate counter as the cursor may be ignoring certain values because of incomplete transactions or
      // expressions
      private final AtomicInteger confirmed = new AtomicInteger(0);

      @Override
      public String toString()
      {
         return "PageCursorInfo::PageID=" + pageId +
            " numberOfMessage = " +
            numberOfMessages +
            ", confirmed = " +
            confirmed +
            ", isDone=" +
            this.isDone();
      }

      public PageCursorInfo(final long pageId, final int numberOfMessages, final PageCache cache)
      {
         this.pageId = pageId;
         this.numberOfMessages = numberOfMessages;
         if (cache != null)
         {
            wasLive = cache.isLive();
            this.cache = new WeakReference<PageCache>(cache);
         }
         else
         {
            wasLive = false;
         }
      }

      /**
       * @param completePage
       */
      public void setCompleteInfo(final PagePosition completePage)
      {
         this.completePage = completePage;
      }

      public PagePosition getCompleteInfo()
      {
         return completePage;
      }

      public boolean isDone()
      {
         return completePage != null || (getNumberOfMessages() == confirmed.get() && pendingTX.get() == 0);
      }

      public boolean isPendingDelete()
      {
         return pendingDelete || completePage != null;
      }

      public void setPendingDelete()
      {
         pendingDelete = true;
      }

      /**
       * @return the pageId
       */
      public long getPageId()
      {
         return pageId;
      }

      public void incrementPendingTX()
      {
         pendingTX.incrementAndGet();
      }

      public void decrementPendingTX()
      {
         pendingTX.decrementAndGet();
         checkDone();
      }

      public boolean isRemoved(final PagePosition pos)
      {
         return removedReferences.contains(pos);
      }

      public void remove(final PagePosition position)
      {
         removedReferences.add(position);
      }

      public void addACK(final PagePosition posACK)
      {

         if (isTrace)
         {
            HornetQServerLogger.LOGGER.trace("numberOfMessages =  " + getNumberOfMessages() +
               " confirmed =  " +
               (confirmed.get() + 1) +
               " pendingTX = " + pendingTX +
               ", page = " +
               pageId + " posACK = " + posACK);
         }

         boolean added = internalAddACK(posACK);

         // Negative could mean a bookmark on the first element for the page (example -1)
         if (added && posACK.getMessageNr() >= 0)
         {
            confirmed.incrementAndGet();
            checkDone();
         }
      }

      // To be called during reload
      public void loadACK(final PagePosition posACK)
      {
         if (internalAddACK(posACK) && posACK.getMessageNr() >= 0)
         {
            confirmed.incrementAndGet();
         }
      }

      private boolean internalAddACK(final PagePosition posACK)
      {
         removedReferences.add(posACK);
         return acks.add(posACK);
      }

      /**
       *
       */
      protected void checkDone()
      {
         if (isDone())
         {
            onPageDone(this);
         }
      }

      private int getNumberOfMessages()
      {
         if (wasLive)
         {
            // if the page was live at any point, we need to
            // get the number of messages from the page-cache
            PageCache localcache = this.cache.get();
            if (localcache == null)
            {
               localcache = cursorProvider.getPageCache(pageId);
               this.cache = new WeakReference<PageCache>(localcache);
            }

            return localcache.getNumberOfMessages();
         }
         else
         {
            return numberOfMessages;
         }
      }

   }

   private static final class PageCursorTX extends TransactionOperationAbstract
   {
      private final Map<PageSubscriptionImpl, List<PagePosition>> pendingPositions =
         new HashMap<PageSubscriptionImpl, List<PagePosition>>();

      private void addPositionConfirmation(final PageSubscriptionImpl cursor, final PagePosition position)
      {
         List<PagePosition> list = pendingPositions.get(cursor);

         if (list == null)
         {
            list = new LinkedList<PagePosition>();
            pendingPositions.put(cursor, list);
         }

         list.add(position);
      }

      @Override
      public void afterCommit(final Transaction tx)
      {
         for (Entry<PageSubscriptionImpl, List<PagePosition>> entry : pendingPositions.entrySet())
         {
            PageSubscriptionImpl cursor = entry.getKey();

            List<PagePosition> positions = entry.getValue();

            for (PagePosition confirmed : positions)
            {
               cursor.processACK(confirmed);
               cursor.deliveredCount.decrementAndGet();
            }

         }
      }

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

   }

   private class CursorIterator implements LinkedListIterator<PagedReference>
   {
      private PagePosition position = null;

      private PagePosition lastOperation = null;

      private volatile boolean isredelivery = false;

      private volatile PagedReference lastRedelivery = null;

      /**
       * next element taken on hasNext test.
       * it has to be delivered on next next operation
       */
      private volatile PagedReference cachedNext;

      public CursorIterator()
      {
      }

      public void repeat()
      {
         if (isredelivery)
         {
            synchronized (redeliveries)
            {
               cachedNext = lastRedelivery;
            }
         }
         else
         {
            if (lastOperation == null)
            {
               position = null;
            }
            else
            {
               position = lastOperation;
            }
         }
      }

      @Override
      public synchronized PagedReference next()
      {

         if (cachedNext != null)
         {
            PagedReference retPos = cachedNext;
            cachedNext = null;
            return retPos;
         }

         try
         {
            if (position == null)
            {
               position = getStartPosition();
            }

            return moveNext();
         }
         catch (RuntimeException e)
         {
            e.printStackTrace();
            throw e;
         }
      }

      private PagedReference moveNext()
      {
         synchronized (PageSubscriptionImpl.this)
         {
            boolean match = false;

            PagedReference message = null;

            PagePosition lastPosition = position;
            PagePosition tmpPosition = position;

            do
            {
               synchronized (redeliveries)
               {
                  PagePosition redelivery = redeliveries.poll();

                  if (redelivery != null)
                  {
                     // There's a redelivery pending, we will get it out of that pool instead
                     isredelivery = true;
                     PagedReference redeliveredMsg = getReference(redelivery);
                     lastRedelivery = redeliveredMsg;

                     return redeliveredMsg;
                  }
                  else
                  {
                     lastRedelivery = null;
                     isredelivery = false;
                  }

                  message = internalGetNext(tmpPosition);
               }

               if (message == null)
               {
                  break;
               }

               tmpPosition = message.getPosition();

               boolean valid = true;
               boolean ignored = false;

               // Validate the scenarios where the message should be considered not valid even to be considered

               // 1st... is it routed?

               valid = routed(message.getPagedMessage());
               if (!valid)
               {
                  ignored = true;
               }

               PageCursorInfo info = getPageInfo(message.getPosition().getPageNr(), false);

               if (info != null && (info.isRemoved(message.getPosition()) || info.getCompleteInfo() != null))
               {
                  continue;
               }

               // 2nd ... if TX, is it committed?
               if (valid && message.getPagedMessage().getTransactionID() >= 0)
               {
                  PageTransactionInfo tx = pageStore.getPagingManager().getTransaction(message.getPagedMessage()
                     .getTransactionID());
                  if (tx == null)
                  {
                     HornetQServerLogger.LOGGER.pageSubscriptionCouldntLoad(message.getPagedMessage().getTransactionID(),
                        message.getPosition(), pageStore.getAddress(), queue.getName());
                     valid = false;
                     ignored = true;
                  }
                  else
                  {
                     if (tx.deliverAfterCommit(PageSubscriptionImpl.this, message.getPosition()))
                     {
                        valid = false;
                        ignored = false;
                     }
                  }
               }

               // 3rd... was it previously removed?
               if (valid)
               {
                  // We don't create a PageCursorInfo unless we are doing a write operation (ack or removing)
                  // Say you have a Browser that will only read the files... there's no need to control PageCursors is
                  // nothing
                  // is being changed. That's why the false is passed as a parameter here

                  if (info != null && info.isRemoved(message.getPosition()))
                  {
                     valid = false;
                  }
               }

               if (!ignored)
               {
                  position = message.getPosition();
               }

               if (valid)
               {
                  match = match(message.getMessage());

                  if (!match)
                  {
                     processACK(message.getPosition());
                  }
               }
               else if (ignored)
               {
                  positionIgnored(message.getPosition());
               }
            }
            while (message != null && !match);

            if (message != null)
            {
               lastOperation = lastPosition;
            }

            return message;
         }
      }

      /**
       * QueueImpl::deliver could be calling hasNext while QueueImpl.depage could be using next and hasNext as well.
       * It would be a rare race condition but I would prefer avoiding that scenario
       */
      public synchronized boolean hasNext()
      {
         // if an unbehaved program called hasNext twice before next, we only cache it once.
         if (cachedNext != null)
         {
            return true;
         }

         if (!pageStore.isPaging())
         {
            return false;
         }

         cachedNext = next();

         return cachedNext != null;
      }

      @Override
      public void remove()
      {
         deliveredCount.incrementAndGet();
         PageCursorInfo info = PageSubscriptionImpl.this.getPageInfo(position);
         if (info != null)
         {
            info.remove(position);
         }
      }

      @Override
      public void close()
      {
      }
   }
}
TOP

Related Classes of org.hornetq.core.paging.cursor.impl.PageSubscriptionImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.