/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/jms/queue/AbstractMemoryQueue.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jms.queue;

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.caucho.env.thread.ThreadPool;
import com.caucho.util.Alarm;

/**
 * Provides abstract implementation for a memory queue.
 * 
 */
@SuppressWarnings("serial")
public abstract class AbstractMemoryQueue<E,QE extends QueueEntry<E>>
  extends AbstractQueue<E>
{
  private static final Logger log
    = Logger.getLogger(AbstractMemoryQueue.class.getName());
  
  private int _queueSizeMax = Integer.MAX_VALUE / 2;
  
  private final Object _queueLock = new Object();

  private ArrayList<EntryCallback<E>> _callbackList
    = new ArrayList<EntryCallback<E>>();

  private ArrayList<EntryCallback<E>> _listenList
    = new ArrayList<EntryCallback<E>>();

  private QE []_head = (QE []) new QueueEntry[10];
  private QE []_tail = (QE []) new QueueEntry[10];

  private ThreadPool _threadPool = ThreadPool.getThreadPool();
  
  private final AtomicLong _readSequenceGenerator = new AtomicLong();
  
  private final AtomicInteger _queueSize = new AtomicInteger();
  
  private final AtomicBoolean _isQueueThrottle = new AtomicBoolean();
  
  // stats
  private AtomicInteger _receiverCount = new AtomicInteger();
  private AtomicInteger _listenerCount = new AtomicInteger();
  
  //
  // configuration
  //
  
  public void setQueueSizeMax(int max)
  {
    if (max <= 0 || Integer.MAX_VALUE / 2 < max)
      _queueSizeMax = Integer.MAX_VALUE / 2;
    else
      _queueSizeMax = max;
  }
  
  public int getQueueSizeMax()
  {
    return _queueSizeMax;
  }

  //
  // Abstract/stub methods to be implemented by the Queue
  //
  /**
   * Sends a message to the queue
   */
  @Override
  public void send(String msgId,
                   E payload,
                   int priority,
                   long expireTime,
                   String publisherId)
    throws MessageException
  {
    QE entry = writeEntry(msgId, payload, priority, expireTime);
      
    addQueueEntry(entry, expireTime);
  }

  //
  // send implementation
  //

  abstract protected QE writeEntry(String msg,
                                   E payload,
                                   int priority,
                                   long expires);

  protected void addQueueEntry(QE entry, long expires)
  {
    addEntry(entry, expires);

    dispatchMessage();
  }

  //
  // receive implementation
  //

  /**
   * Primary message receiving, registers a callback for any new
   * message.
   */
  @Override
  public QE receiveEntry(long expireTime, boolean isAutoAck) 
     throws MessageException
  {
    return receiveEntry(expireTime, isAutoAck, null);
  }
  
  @Override
  public QE receiveEntry(long expireTime, 
                         boolean isAutoAck, 
                         QueueEntrySelector selector) 
    throws MessageException
  {
    if (Alarm.isTest())
      expireTime += Alarm.getCurrentTimeActual() - Alarm.getCurrentTime();

    _receiverCount.incrementAndGet();
    
    try {
      QE entry = null;

      synchronized (_queueLock) {
        if (_callbackList.size() == 0) {
          entry = readEntry(selector);
        }
      }

      if (entry != null) {
        readPayload(entry);
  
        if (isAutoAck)
          acknowledge(entry.getMsgId());
          
        return entry;
      }
  
      if (expireTime <= Alarm.getCurrentTimeActual()) {
        return null;
      }
  
      ReceiveEntryCallback callback = new ReceiveEntryCallback(isAutoAck);

      return (QE) callback.waitForEntry(expireTime);      
    } finally {
      _receiverCount.decrementAndGet();  
    }
  }
  
  public void receive(long expireTime,
                      boolean isAutoAck, 
                      QueueEntrySelector selector,
                      MessageCallback callback)
    throws MessageException
  {
    ListenEntryCallback entryCallback
    = new ListenEntryCallback(callback, isAutoAck);
    
    listen(entryCallback);
  }

  @Override
  public void addMessageCallback(MessageCallback<E> callback,
                                 boolean isAutoAck)
  {
    _listenerCount.incrementAndGet();
    
    ListenEntryCallback entryCallback
    = new ListenEntryCallback(callback, isAutoAck);
    
    synchronized (_listenList) {
      _listenList.add(entryCallback);
    }

    listen(entryCallback);
  }

  @Override
  public void removeMessageCallback(MessageCallback<E> callback)
  {
    ListenEntryCallback listenerCallback = null;
    
    synchronized (_listenList) {
      for (int i = _listenList.size() - 1; i >= 0; i--) {
        EntryCallback cb = _listenList.get(i);
        
        if (cb.getMessageCallback() == callback) {
          listenerCallback = (ListenEntryCallback) cb;
          
          _callbackList.remove(cb);
            
          break;
        }
      }
    }
    
    if (listenerCallback != null) {
      listenerCallback.close();
    }

    synchronized (_queueLock) {
      if (listenerCallback != null)
        _callbackList.remove(listenerCallback);
    }

    if (listenerCallback != null) {
      listenerCallback.close();
      _listenerCount.decrementAndGet();
    }
  }

  //
  // abstract receive stubs
  //

  protected void acknowledge(QE entry)
  {
  }

  protected void readPayload(QE entry)
  {
  }
    
  /**
   * Acknowledges the receipt of a message
   */
  @Override
  public void acknowledge(String msgId)
  {
    QE entry = removeEntry(msgId);

    if (entry != null)
      acknowledge(entry);
  }

  protected boolean listen(EntryCallback<E> callback)
    throws MessageException
  {
    QE entry = null;
    
    synchronized (_queueLock) {
      if (_callbackList.size() > 0 || (entry = readEntry()) == null) {
        _callbackList.add(callback);
        return false;
      }
    }

    readPayload(entry);

    if (callback.entryReceived(entry)) {
      acknowledge(entry.getMsgId());
    }

    return true;
  }

  protected void dispatchMessage()
  {
    while (true) {
      QE entry = null;
      EntryCallback<E> callback = null;
      
      synchronized (_queueLock) {
        if (_callbackList.size() == 0 || (entry = readEntry()) == null) {
          return;
        }

        callback = _callbackList.remove(0);
      }

      readPayload(entry);

      if (callback.entryReceived(entry)) {
        acknowledge(entry.getMsgId());
      }
    }
  }
  
  //
  // Queue statistics JMX
  //

  /**
   * Returns the queue size
   */
  @Override
  public int getQueueSize()
  {
    int count = 0;

    for (int i = 0; i < _head.length; i++) {
      for (QueueEntry<E> entry = _head[i];
           entry != null;
           entry = entry._next) {
        count++;
      }
    }

    return count;
  }
  
  /**
   * Returns true if a message is available.
   */
  @Override
  public boolean hasMessage()
  {
    return getQueueSize() > 0;
  }
  
  @Override  
  public int getConsumerCount()
  {
    return _listenerCount.get();
  }

  @Override  
  public int getReceiverCount()
  {
    return _receiverCount.get();
  }
  
  //
  // queue management
  //

  /**
   * Add an entry to the queue
   */
  private QE addEntry(QE entry, long expires)
  {
    int priority = entry.getPriority();
    
    synchronized (_queueLock) {
      if (_tail[priority] != null)
        _tail[priority]._next = entry;
      else
        _head[priority] = entry;

      _tail[priority] = entry;
    }
    
    int size = _queueSize.incrementAndGet();
    
    if (_queueSizeMax < size) {
      long timeout = 100;
      
      waitForQueueThrottle(timeout);
    }

    return entry;
  }
  
  private void waitForQueueThrottle(long timeout)
  {
    _isQueueThrottle.set(true);
    
    synchronized (_isQueueThrottle) {
      try {
        if (_isQueueThrottle.get()) {
          // long timeout = expires - Alarm.getCurrentTimeActual();
          
          if (timeout > 1000)
            timeout = 1000;
          
          if (timeout > 0) {
            _isQueueThrottle.wait(timeout);
          }
        }
      } catch (Exception e) {
        log.log(Level.FINER, e.toString(), e);
      }
    }
  }
  
  private void wakeQueueThrottle()
  {
   int size = _queueSize.get();
    
    if (size <= _queueSizeMax) {
      if (_isQueueThrottle.compareAndSet(true, false)) {
        synchronized (_isQueueThrottle) {
          _isQueueThrottle.notifyAll();
        }
      }
    }
  }
  
  /**
   * Returns the next entry from the queue
   */
  protected QE readEntry()
  {
    QE entry = readEntry(null);
    
    return entry;
  }
  /**
   * Returns the next entry from the queue
   */
  protected QE readEntry(QueueEntrySelector selector)
  {
    for (int i = _head.length - 1; i >= 0; i--) {
      for (QE entry = _head[i];
           entry != null;
           entry = (QE) entry._next) {

        if (! entry.isLease()) {
          continue;
        }

        if (entry.isRead()) {
          continue;
        }
        
        readPayload((QE) entry);
        if ((selector != null) && (! selector.isMatch(entry))) {
          continue;
        }
          
        entry.setReadSequence(_readSequenceGenerator.incrementAndGet());

        return entry;
      }
    }

    return null;
  }

  /**
   * 
   * @param selector
   * @return          Entries present in the Queue.
   */
  public ArrayList<QE> getBrowserList()
  {
    ArrayList<QE> entries = new ArrayList<QE>();
    for (int i = _head.length - 1; i >= 0; i--) {
      for (QE entry = _head[i];
           entry != null;
           entry = (QE) entry._next) {

        if (! entry.isLease()) {
          continue;
        }

        if (entry.isRead()) {
          continue;
        }
        
        readPayload(entry);

        entries.add(entry);
      }
    }
    
    return entries.size() > 0 ? entries : null;
  }
  
  /**
   * Removes message.
   */
  protected QE removeEntry(String msgId)
  {
    synchronized (_queueLock) {
      for (int i = _head.length - 1; i >= 0; i--) {
        QE prev = null;
        QE entry = _head[i];

        while (entry != null) {
          QE next = (QE) entry._next;
          
          if (msgId.equals(entry.getMsgId())) {
            if (prev != null)
              prev._next = entry._next;
            else
              _head[i] = (QE) entry._next;

            if (_tail[i] == entry)
              _tail[i] = prev;
            
            _queueSize.decrementAndGet();
            
            if (_isQueueThrottle.get()) {
              wakeQueueThrottle();
            }
            
            return entry;
          }

          prev = entry;
          entry = next;
        }
      }
    }

    return null;
  }
    
  
  /**
   * Rolls back the receipt of a message
   */
  @Override
  public void rollback(String msgId)
  {
    synchronized (_queueLock) {
      for (int i = _head.length - 1; i >= 0; i--) {
        for (QueueEntry<E> entry = _head[i];
             entry != null;
             entry = entry._next) {
          if (msgId.equals(entry.getMsgId())) {
            if (entry.isRead()) {
              entry.setReadSequence(0);

              /*
              MessageImpl msg = (MessageImpl) getPayload(entry);
        
              if (msg != null)
                msg.setJMSRedelivered(true);
              */
            }
            
            return;
          }
        }
      }
    }
  }
  
  public ArrayList<String> getMessageIds()
  {
    ArrayList<String> browserList = new ArrayList<String>();

    synchronized (_queueLock) {
      for (int i = 0; i < _head.length; i++) {
        for (QueueEntry<E> entry = _head[i];
             entry != null;
             entry = entry._next) {
          browserList.add(entry.getMsgId());
        }
      }
    }

    return browserList;    
  }

  /**
   * Synchronous timeout receive
   */
  class ReceiveEntryCallback implements EntryCallback<E> {
    private boolean _isAutoAck;
    
    private Thread _thread;
    private volatile QueueEntry<E> _entry;

    ReceiveEntryCallback(boolean isAutoAck)
    {
      _isAutoAck = isAutoAck;
      _thread = Thread.currentThread();
    }
    
    public MessageCallback<E> getMessageCallback()
    {
      return null;
    }
    
    public boolean entryReceived(QueueEntry<E> entry)
    {
      _entry = entry;

      LockSupport.unpark(_thread);

      return _isAutoAck;
    }

    public QueueEntry<E> waitForEntry(long expireTime)
    {
      listen(this);
      
      while (_entry == null
             && (Alarm.getCurrentTimeActual() < expireTime)) {
        LockSupport.parkUntil(expireTime);
      }

      if (_entry == null) {
        synchronized (_queueLock) {
          _callbackList.remove(this);
        }
      }
      
      return _entry;
    }
  }

  /**
   * Async listen receive
   */
  class ListenEntryCallback implements EntryCallback<E>, Runnable {
    private MessageCallback<E> _callback;
    private ClassLoader _classLoader;

    private boolean _isClosed;
    
    private volatile QueueEntry<E> _entry;

    ListenEntryCallback(MessageCallback<E> callback, boolean isAutoAck)
    {
      _callback = callback;
      _classLoader = Thread.currentThread().getContextClassLoader();
    }
    
    @Override
    public MessageCallback<E> getMessageCallback()
    {
      return _callback;
    }
    
    @Override
    public boolean entryReceived(QueueEntry<E> entry)
    {
      _entry = entry;

      _threadPool.schedule(this);

      return false;
    }

    @Override
    public void run()
    {
      Thread thread = Thread.currentThread();
      ClassLoader oldLoader = thread.getContextClassLoader();
      boolean isValid = false;
      long readSequence = _entry.getReadSequence();
      
      try {
        thread.setContextClassLoader(_classLoader);

        _callback.messageReceived(_entry.getMsgId(), _entry.getPayload());
        isValid = true;
      } catch (Exception e) {
        log.log(Level.WARNING, e.toString(), e);
        isValid = true;
      } catch (Throwable t) {
        log.log(Level.SEVERE, t.toString(), t);
      } finally {
        thread.setContextClassLoader(oldLoader);
        
        if (readSequence == _entry.getReadSequence()){
          acknowledge(_entry.getMsgId());
        }
      }

      if (! _isClosed && isValid) {
        listen(this);
      }
    }

    public void close()
    {
      _isClosed = true;
    }
  }
}