/*
* JBoss, Home of Professional Open Source Copyright 2005-2008, Red Hat
* Middleware LLC, and individual contributors by the @authors tag. See the
* copyright.txt in the distribution for a full listing of individual
* contributors.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software 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. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.jboss.messaging.core.server.impl;
import org.jboss.messaging.core.filter.Filter;
import org.jboss.messaging.core.logging.Logger;
import org.jboss.messaging.core.persistence.StorageManager;
import org.jboss.messaging.core.postoffice.PostOffice;
import org.jboss.messaging.core.remoting.Channel;
import org.jboss.messaging.core.remoting.DelayedResult;
import org.jboss.messaging.core.remoting.impl.wireformat.SessionReceiveMessage;
import org.jboss.messaging.core.remoting.impl.wireformat.SessionReplicateDeliveryMessage;
import org.jboss.messaging.core.server.HandleStatus;
import org.jboss.messaging.core.server.MessageReference;
import org.jboss.messaging.core.server.Queue;
import org.jboss.messaging.core.server.ServerConsumer;
import org.jboss.messaging.core.server.ServerMessage;
import org.jboss.messaging.core.server.ServerSession;
import org.jboss.messaging.core.settings.HierarchicalRepository;
import org.jboss.messaging.core.settings.impl.QueueSettings;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Concrete implementation of a ClientConsumer.
*
* @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
* @author <a href="mailto:jmesnil@redhat.com">Jeff Mesnil</a>
*
* @version <tt>$Revision: 3783 $</tt> $Id: ServerConsumerImpl.java 3783 2008-02-25 12:15:14Z timfox $
*/
public class ServerConsumerImpl implements ServerConsumer
{
// Constants
// ------------------------------------------------------------------------------------
private static final Logger log = Logger.getLogger(ServerConsumerImpl.class);
// Static
// ---------------------------------------------------------------------------------------
// Attributes
// -----------------------------------------------------------------------------------
private final boolean trace = log.isTraceEnabled();
private final long id;
private final Queue messageQueue;
private final Filter filter;
private final ServerSession session;
private final Object startStopLock = new Object();
private final AtomicInteger availableCredits;
private boolean started;
/**
* if we are a browse only consumer we don't need to worry about acknowledgemenets or being started/stopeed by the session.
*/
private boolean browseOnly;
private final StorageManager storageManager;
private final HierarchicalRepository<QueueSettings> queueSettingsRepository;
private final PostOffice postOffice;
private final java.util.Queue<MessageReference> deliveringRefs = new ConcurrentLinkedQueue<MessageReference>();
private final Channel channel;
// Constructors
// ---------------------------------------------------------------------------------
public ServerConsumerImpl(final long id,
final ServerSession session,
final Queue messageQueue,
final Filter filter,
final boolean enableFlowControl,
final int maxRate,
final boolean started,
final boolean browseOnly,
final StorageManager storageManager,
final HierarchicalRepository<QueueSettings> queueSettingsRepository,
final PostOffice postOffice,
final Channel channel)
{
this.id = id;
this.messageQueue = messageQueue;
this.filter = filter;
this.session = session;
this.started = browseOnly || started;
this.browseOnly = browseOnly;
if (enableFlowControl)
{
availableCredits = new AtomicInteger(0);
}
else
{
availableCredits = null;
}
this.storageManager = storageManager;
this.queueSettingsRepository = queueSettingsRepository;
this.postOffice = postOffice;
this.channel = channel;
messageQueue.addConsumer(this);
}
// ServerConsumer implementation
// ----------------------------------------------------------------------
public long getID()
{
return id;
}
public HandleStatus handle(final MessageReference ref) throws Exception
{
if (availableCredits != null && availableCredits.get() <= 0)
{
return HandleStatus.BUSY;
}
final ServerMessage message = ref.getMessage();
if (message.isExpired())
{
// TODO need to replicate expires
ref.expire(storageManager, postOffice, queueSettingsRepository);
return HandleStatus.HANDLED;
}
synchronized (startStopLock)
{
// If the consumer is stopped then we don't accept the message, it
// should go back into the
// queue for delivery later.
if (!started)
{
return HandleStatus.BUSY;
}
if (filter != null && !filter.match(message))
{
return HandleStatus.NO_MATCH;
}
if (availableCredits != null)
{
availableCredits.addAndGet(-message.getEncodeSize());
}
final SessionReceiveMessage packet = new SessionReceiveMessage(id, message, ref.getDeliveryCount() + 1);
DelayedResult result = channel.replicatePacket(new SessionReplicateDeliveryMessage(id, message.getMessageID()));
if(!browseOnly)
{
deliveringRefs.add(ref);
}
if (result == null)
{
// Not replicated - just send now
channel.send(packet);
}
else
{
// Send when replicate delivery response comes back
result.setResultRunner(new Runnable()
{
public void run()
{
channel.send(packet);
}
});
}
return HandleStatus.HANDLED;
}
}
public void close() throws Exception
{
browseOnly = false;
setStarted(false);
messageQueue.removeConsumer(this);
session.removeConsumer(this);
LinkedList<MessageReference> refs = cancelRefs();
Iterator<MessageReference> iter = refs.iterator();
while (iter.hasNext())
{
MessageReference ref = iter.next();
if (!ref.cancel(storageManager, postOffice, queueSettingsRepository))
{
iter.remove();
}
}
if (!refs.isEmpty())
{
messageQueue.addListFirst(refs);
}
}
public LinkedList<MessageReference> cancelRefs() throws Exception
{
LinkedList<MessageReference> refs = new LinkedList<MessageReference>();
if (!deliveringRefs.isEmpty())
{
for (MessageReference ref : deliveringRefs)
{
refs.add(ref);
}
deliveringRefs.clear();
}
return refs;
}
public void setStarted(final boolean started)
{
synchronized (startStopLock)
{
this.started = browseOnly || started;
}
// Outside the lock
if (started)
{
promptDelivery();
}
}
public void receiveCredits(final int credits) throws Exception
{
if (availableCredits != null)
{
int previous = availableCredits.getAndAdd(credits);
if (previous <= 0 && previous + credits > 0)
{
promptDelivery();
}
}
}
public Queue getQueue()
{
return messageQueue;
}
public MessageReference getReference(final long messageID) throws Exception
{
// Acknowledge acknowledges all refs delivered by the consumer up to and including the one explicitly
// acknowledged
MessageReference ref;
do
{
ref = deliveringRefs.poll();
if (ref == null)
{
throw new IllegalStateException("Could not find reference with id " + messageID +
" backup " +
messageQueue.isBackup());
}
}
while (ref.getMessage().getMessageID() != messageID);
return ref;
}
public void deliverReplicated(final long messageID) throws Exception
{
// It may not be the first in the queue - since there may be multiple producers
// sending to the queue
MessageReference ref = messageQueue.removeReferenceWithID(messageID);
HandleStatus handled = this.handle(ref);
if (handled != HandleStatus.HANDLED)
{
throw new IllegalStateException("Reference was not handled " + ref + " " + handled);
}
}
public void failedOver()
{
if (messageQueue.consumerFailedOver())
{
if (started)
{
promptDelivery();
}
}
}
// Public
// -----------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------------------
private void promptDelivery()
{
session.promptDelivery(messageQueue);
}
// Inner classes
// ------------------------------------------------------------------------
}