/*
* This file is part of FFMQ.
*
* FFMQ 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 of the License, or
* (at your option) any later version.
*
* FFMQ 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 FFMQ; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.timewalker.ffmq3.local.destination;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Topic;
import net.timewalker.ffmq3.FFMQException;
import net.timewalker.ffmq3.common.message.AbstractMessage;
import net.timewalker.ffmq3.common.message.MessageSelector;
import net.timewalker.ffmq3.local.destination.subscription.LocalTopicSubscription;
import net.timewalker.ffmq3.local.session.LocalMessageConsumer;
import net.timewalker.ffmq3.local.session.LocalSession;
import net.timewalker.ffmq3.management.destination.definition.TopicDefinition;
import net.timewalker.ffmq3.storage.message.MessageSerializationLevel;
import net.timewalker.ffmq3.utils.ErrorTools;
import net.timewalker.ffmq3.utils.concurrent.CopyOnWriteList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>Implementation for a local JMS {@link Topic}</p>
*/
public final class LocalTopic extends AbstractLocalDestination implements Topic, LocalTopicMBean
{
private static final Log log = LogFactory.getLog(LocalTopic.class);
// Definition
private TopicDefinition topicDef;
// Subscribers map
private CopyOnWriteList subscriptions = new CopyOnWriteList();
private Map subscriptionMap = new Hashtable();
// Stats
private volatile long sentToTopicCount = 0;
private volatile long dispatchedFromTopicCount = 0;
/**
* Constructor
*/
public LocalTopic( TopicDefinition topicDef )
{
super(topicDef);
this.topicDef = topicDef;
}
/**
* Get the queue definition
*/
public TopicDefinition getDefinition()
{
return topicDef;
}
/*
* (non-Javadoc)
* @see javax.jms.Topic#getTopicName()
*/
public String getTopicName()
{
return getName();
}
/* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.AbstractLocalDestination#registerConsumer(net.timewalker.ffmq3.local.session.LocalMessageConsumer)
*/
public void registerConsumer(LocalMessageConsumer consumer)
{
super.registerConsumer(consumer);
synchronized (subscriptionMap)
{
LocalTopicSubscription subscription = new LocalTopicSubscription(consumer);
subscriptions.add(subscription);
subscriptionMap.put(consumer.getSubscriberId(),subscription);
}
}
/* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.AbstractLocalDestination#unregisterConsumer(net.timewalker.ffmq3.local.session.LocalMessageConsumer)
*/
public void unregisterConsumer(LocalMessageConsumer consumer)
{
super.unregisterConsumer(consumer);
if (!consumer.isDurable())
{
log.debug("Removing non-durable subscription "+consumer.getSubscriberId());
synchronized (subscriptionMap)
{
LocalTopicSubscription subscription = (LocalTopicSubscription)subscriptionMap.remove(consumer.getSubscriberId());
if (subscription != null)
subscriptions.remove(subscription);
}
}
}
/**
* Unsubscribe all durable consumers for a given client ID and subscription name
*/
public void unsubscribe( String clientID , String subscriptionName ) throws JMSException
{
String subscriberID = clientID+"-"+subscriptionName;
LocalTopicSubscription subscription = (LocalTopicSubscription)subscriptionMap.get(subscriberID);
if (subscription == null)
return;
if (isConsumerRegistered(subscriberID))
throw new FFMQException("Subscription "+subscriptionName+" is still in use","SUBSCRIPTION_STILL_IN_USE");
synchronized (subscriptionMap)
{
subscriptionMap.remove(subscriberID);
subscriptions.remove(subscription);
}
}
/**
* Put a message in the topic.
* Message copies are immediately delivered to all consumers.
*/
public void put( AbstractMessage srcMessage, LocalSession session , Set committables ) throws JMSException
{
checkNotClosed();
// Check delivery mode
if (!topicDef.supportDeliveryMode(srcMessage.getJMSDeliveryMode()))
throw new FFMQException("Topic does not support this delivery mode : "+
(srcMessage.getJMSDeliveryMode() == DeliveryMode.NON_PERSISTENT ?
"DeliveryMode.NON_PERSISTENT" : "DeliveryMode.PERSISTENT"),
"INVALID_DELIVERY_MODE");
String connectionID = session.getConnection().getId();
CopyOnWriteList subscriptionsSnapshot;
synchronized (subscriptionMap)
{
sentToTopicCount++;
if (subscriptions.isEmpty())
return;
subscriptionsSnapshot = subscriptions.fastCopy();
}
for (int i = 0; i < subscriptionsSnapshot.size(); i++)
{
LocalTopicSubscription subscription = (LocalTopicSubscription)subscriptionsSnapshot.get(i);
// No-local filtering
if (subscription.getNoLocal() && subscription.getConnectionID().equals(connectionID))
continue;
try
{
// Message selector filtering
MessageSelector selector = subscription.getMessageSelector();
if (selector != null)
{
srcMessage.ensureDeserializationLevel(MessageSerializationLevel.ALL_HEADERS);
if (!selector.matches(srcMessage))
continue;
}
if (subscription.getLocalQueue().put(srcMessage))
{
// Only require a commit if the subscription is declared as durable
if (committables != null && subscription.isDurable())
committables.add(subscription.getLocalQueue());
}
dispatchedFromTopicCount++;
}
catch (JMSException e)
{
ErrorTools.log(e, log);
}
}
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.LocalDestinationMBean#getSize()
*/
public int getSize()
{
int size = 0;
synchronized (subscriptionMap)
{
for (int i = 0; i < subscriptions.size(); i++)
{
LocalTopicSubscription subscription = (LocalTopicSubscription)subscriptions.get(i);
size += subscription.getLocalQueue().getSize();
}
}
return size;
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.LocalDestinationMBean#resetStats()
*/
public void resetStats()
{
sentToTopicCount = 0;
dispatchedFromTopicCount = 0;
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.LocalTopicMBean#getSentToTopicCount()
*/
public long getSentToTopicCount()
{
return sentToTopicCount;
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.LocalTopicMBean#getDispatchedFromTopicCount()
*/
public long getDispatchedFromTopicCount()
{
return dispatchedFromTopicCount;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("Topic{");
sb.append(getName());
sb.append("}[size=");
sb.append(getSize());
sb.append(",consumers=");
sb.append(localConsumers.size());
sb.append(",in=");
sb.append(sentToTopicCount);
sb.append(",out=");
sb.append(dispatchedFromTopicCount);
sb.append("]");
return sb.toString();
}
public String getConsumersSummary()
{
StringBuffer sb = new StringBuffer();
synchronized (subscriptionMap)
{
for (int i = 0; i < subscriptions.size(); i++)
{
LocalTopicSubscription subscription = (LocalTopicSubscription)subscriptions.get(i);
if (i>0)
sb.append("\n");
sb.append(subscription);
}
}
return sb.toString();
}
/*
* (non-Javadoc)
* @see net.timewalker.ffmq3.local.destination.AbstractLocalDestination#close()
*/
public final void close() throws JMSException
{
synchronized (closeLock)
{
if (closed)
return;
closed = true;
}
}
}