/**
* Copyright 2004 Protique Ltd
*
* Licensed 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.codehaus.activemq.service.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.DuplicateDurableSubscriptionException;
import org.codehaus.activemq.broker.BrokerClient;
import org.codehaus.activemq.filter.AndFilter;
import org.codehaus.activemq.filter.DestinationMap;
import org.codehaus.activemq.filter.Filter;
import org.codehaus.activemq.filter.FilterFactory;
import org.codehaus.activemq.filter.FilterFactoryImpl;
import org.codehaus.activemq.filter.NoLocalFilter;
import org.codehaus.activemq.message.ActiveMQDestination;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ActiveMQTopic;
import org.codehaus.activemq.message.ConsumerInfo;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.service.DeadLetterPolicy;
import org.codehaus.activemq.service.Dispatcher;
import org.codehaus.activemq.service.MessageContainer;
import org.codehaus.activemq.service.RedeliveryPolicy;
import org.codehaus.activemq.service.Subscription;
import org.codehaus.activemq.service.SubscriptionContainer;
import org.codehaus.activemq.service.TopicMessageContainer;
import org.codehaus.activemq.service.TransactionManager;
import org.codehaus.activemq.service.TransactionTask;
import org.codehaus.activemq.store.PersistenceAdapter;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
/**
* A default Broker used for Topic messages for durable consumers
*
* @version $Revision: 1.14 $
*/
public class DurableTopicMessageContainerManager extends MessageContainerManagerSupport {
private static final Log log = LogFactory.getLog(DurableTopicMessageContainerManager.class);
private PersistenceAdapter persistenceAdapter;
protected SubscriptionContainer subscriptionContainer;
protected FilterFactory filterFactory;
protected Map activeSubscriptions = new ConcurrentHashMap();
private DestinationMap destinationMap = new DestinationMap();
private ConcurrentHashMap durableSubscriptions = new ConcurrentHashMap();
public DurableTopicMessageContainerManager(PersistenceAdapter persistenceAdapter, RedeliveryPolicy redeliveryPolicy,DeadLetterPolicy deadLetterPolicy) {
this(persistenceAdapter, new DurableTopicSubscriptionContainerImpl(redeliveryPolicy,deadLetterPolicy), new FilterFactoryImpl(),
new DispatcherImpl());
}
public DurableTopicMessageContainerManager(PersistenceAdapter persistenceAdapter,
SubscriptionContainer subscriptionContainer, FilterFactory filterFactory, Dispatcher dispatcher) {
super(dispatcher);
this.persistenceAdapter = persistenceAdapter;
this.subscriptionContainer = subscriptionContainer;
this.filterFactory = filterFactory;
try {
loadAllMessageContainers();
}
catch (JMSException e) {
log.error("Failed to load initial Topic Containers",e);
}
}
public void addMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
if (info.isDurableTopic()) {
if (log.isDebugEnabled()) {
log.debug("Adding consumer: " + info);
}
doAddMessageConsumer(client, info);
}
}
public void removeMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
// we should not remove a durable topic subscription from the subscriptionContainer
// unless via the deleteSubscription() method
// subscriptionContainer.removeSubscription(info.getConsumerId());
// subscription.clear();
Subscription sub = (Subscription) activeSubscriptions.remove(info.getConsumerId());
if (sub != null) {
sub.setActive(false);
dispatcher.removeActiveSubscription(client, sub);
}
}
/**
* Delete a durable subscriber
*
* @param clientId
* @param subscriberName
* @throws javax.jms.JMSException if the subscriber doesn't exist or is still active
*/
public void deleteSubscription(String clientId, String subscriberName) throws JMSException {
String consumerKey = ConsumerInfo.generateConsumerKey(clientId, subscriberName);
Subscription sub = (Subscription) durableSubscriptions.remove(consumerKey);
if( sub!=null ) {
//only delete if not active
if (sub.isActive()) {
throw new JMSException("The Consummer " + subscriberName + " is still active");
}
else {
subscriptionContainer.removeSubscription(sub.getConsumerId());
sub.clear();
Set containers = destinationMap.get(sub.getDestination());
for (Iterator iter = containers.iterator();iter.hasNext();) {
TopicMessageContainer container = (TopicMessageContainer) iter.next();
if (container instanceof DurableTopicMessageContainer) {
((DurableTopicMessageContainer) container).deleteSubscription(sub.getPersistentKey());
}
}
}
} else {
throw new InvalidDestinationException("The Consumer " + subscriberName + " does not exist for client: "
+ clientId);
}
}
/**
* @param client
* @param message
* @throws javax.jms.JMSException
*/
public void sendMessage(final BrokerClient client, final ActiveMQMessage message) throws JMSException {
ActiveMQDestination dest = (ActiveMQDestination) message.getJMSDestination();
if (dest != null && dest.isTopic() && message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT) {
Set containers = destinationMap.get(dest);
if (containers != null && !containers.isEmpty()) {
// note that we still need to persist the message even if there are no matching
// subscribers as they may come along later
// plus we don't pre-load subscription information
final MessageContainer container = getContainer(message.getJMSDestination().toString());
container.addMessage(message);
TransactionManager.getContexTransaction().addPostCommitTask(new TransactionTask() {
public void execute() throws Throwable {
doSendMessage(client, message, container);
}
});
}
}
}
/**
* @param client
* @param message
* @throws JMSException
*/
private void doSendMessage(BrokerClient client, ActiveMQMessage message, MessageContainer container) throws JMSException {
Set matchingSubscriptions = subscriptionContainer.getSubscriptions(message.getJMSActiveMQDestination());
if (!matchingSubscriptions.isEmpty()) {
for (Iterator i = matchingSubscriptions.iterator();i.hasNext();) {
Subscription sub = (Subscription) i.next();
if (sub.isTarget(message)) {
sub.addMessage(container, message);
}
}
updateSendStats(client, message);
}
}
/**
* Returns an unmodifiable map, indexed by String name, of all the {@link javax.jms.Destination}
* objects used by non-broker consumers directly connected to this container
*
* @return
*/
public Map getLocalDestinations() {
Map localDestinations = new HashMap();
for (Iterator iter = subscriptionContainer.subscriptionIterator(); iter.hasNext();) {
Subscription sub = (Subscription) iter.next();
if (sub.isLocalSubscription()) {
final ActiveMQDestination dest = sub.getDestination();
localDestinations.put(dest.getPhysicalName(), dest);
}
}
return Collections.unmodifiableMap(localDestinations);
}
/**
* Acknowledge a message as being read and consumed byh the Consumer
*
* @param client
* @param ack
* @throws javax.jms.JMSException
*/
public void acknowledgeMessage(BrokerClient client, final MessageAck ack) throws JMSException {
if ( !ack.getDestination().isTopic() )
return;
Subscription sub = (Subscription) activeSubscriptions.get(ack.getConsumerId());
if (sub == null) {
return;
}
sub.messageConsumed(ack);
}
/**
* poll or messages
*
* @throws javax.jms.JMSException
*/
public void poll() throws JMSException {
//do nothing
}
// Implementation methods
//-------------------------------------------------------------------------
protected MessageContainer createContainer(String destinationName) throws JMSException {
TopicMessageContainer topicMessageContainer = persistenceAdapter.createTopicMessageContainer(destinationName);
destinationMap.put(new ActiveMQTopic(destinationName), topicMessageContainer);
return topicMessageContainer;
}
protected Destination createDestination(String destinationName) {
return new ActiveMQTopic(destinationName);
}
protected void doAddMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
boolean shouldRecover = false;
if (info.getConsumerName() != null && info.getClientId() != null) {
for (Iterator iter = activeSubscriptions.values().iterator();iter.hasNext();) {
Subscription subscription = (Subscription) iter.next();
if (subscription.isSameDurableSubscription(info)) {
throw new DuplicateDurableSubscriptionException(info);
}
}
}
Subscription subscription = (Subscription) durableSubscriptions.get(info.getConsumerKey());
//subscriptionContainer.getSubscription(info.getConsumerId());
if (subscription != null) {
//check the subscription hasn't changed
if (!subscription.getDestination().equals(info.getDestination())
|| !subscription.getSelector().equals(info.getSelector())) {
subscriptionContainer.removeSubscription(info.getConsumerId());
subscription.clear();
subscription = subscriptionContainer.makeSubscription(dispatcher, client, info, createFilter(info));
durableSubscriptions.put(info.getConsumerKey(), subscription);
}
}
else {
subscription = subscriptionContainer.makeSubscription(dispatcher, client,info, createFilter(info));
shouldRecover = true;
durableSubscriptions.put(info.getConsumerKey(), subscription);
}
subscription.setActiveConsumer(client,info);
activeSubscriptions.put(info.getConsumerId(), subscription);
dispatcher.addActiveSubscription(client, subscription);
// load the container
getContainer(subscription.getDestination().getPhysicalName());
Set containers = destinationMap.get(subscription.getDestination());
for (Iterator iter = containers.iterator();iter.hasNext();) {
TopicMessageContainer container = (TopicMessageContainer) iter.next();
if (container instanceof DurableTopicMessageContainer) {
((DurableTopicMessageContainer) container).storeSubscription(info, subscription);
}
}
if (shouldRecover) {
recoverSubscriptions(subscription);
}
// lets not make the subscription active until later
// as we can't start dispatching until we've sent back the receipt
// TODO we might wish to register a post-receipt action here
// to perform the wakeup
subscription.setActive(true);
//dispatcher.wakeup(subscription);
}
/**
* This method is called when a new durable subscription is started and so we need to go through each matching
* message container and dispatch any matching messages that may be outstanding
*
* @param subscription
*/
protected void recoverSubscriptions(Subscription subscription) throws JMSException {
// we should load all of the message containers from disk if we're a wildcard
getContainer(subscription.getDestination().getPhysicalName());
Set containers = destinationMap.get(subscription.getDestination());
for (Iterator iter = containers.iterator();iter.hasNext();) {
TopicMessageContainer container = (TopicMessageContainer) iter.next();
container.recoverSubscription(subscription);
}
}
/**
* Called when recovering a wildcard subscription where we need to load all the durable message containers (for
* which we have any outstanding messages to deliver) into RAM
*/
protected void loadAllMessageContainers() throws JMSException {
Map destinations = persistenceAdapter.getInitialDestinations();
if (destinations != null) {
for (Iterator iter = destinations.entrySet().iterator();iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
String name = (String) entry.getKey();
Destination destination = (Destination) entry.getValue();
loadContainer(name, destination);
}
}
}
/**
* Create filter for a Consumer
*
* @param info
* @return the Fitler
* @throws javax.jms.JMSException
*/
protected Filter createFilter(ConsumerInfo info) throws JMSException {
Filter filter = filterFactory.createFilter(info.getDestination(), info.getSelector());
if (info.isNoLocal()) {
filter = new AndFilter(filter, new NoLocalFilter(info.getClientId()));
}
return filter;
}
public void createMessageContainer(ActiveMQDestination dest) throws JMSException {
// This container only does topics.
if(!dest.isTopic())
return;
super.createMessageContainer(dest);
}
public synchronized void destroyMessageContainer(ActiveMQDestination dest) throws JMSException {
// This container only does topics.
if(!dest.isTopic())
return;
super.destroyMessageContainer(dest);
destinationMap.removeAll(dest);
}
}