/**
*
* 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.boundedvm;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.jms.Destination;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.ConsumerInfo;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.message.util.MemoryBoundedQueue;
import org.codehaus.activemq.message.util.MemoryBoundedQueueManager;
import org.codehaus.activemq.service.MessageContainer;
import org.codehaus.activemq.service.MessageContainerManager;
import org.codehaus.activemq.service.impl.DispatcherImpl;
import org.codehaus.activemq.service.impl.MessageContainerManagerSupport;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
/**
* A MessageContainerManager for transient queues
*
* @version $Revision: 1.4 $
*/
/**
* A manager of MessageContainer instances
*/
public class TransientQueueBoundedMessageManager implements MessageContainerManager, Runnable {
private static final int GARBAGE_COLLECTION_CAPACITY_LIMIT = 20;
private static final Log log = LogFactory.getLog(TransientQueueBoundedMessageManager.class);
private MemoryBoundedQueueManager queueManager;
private ConcurrentHashMap containers;
private ConcurrentHashMap subscriptions;
private FilterFactory filterFactory;
private SynchronizedBoolean started;
private SynchronizedBoolean doingGarbageCollection;
private Map destinations;
private DestinationMap destinationMap;
private Thread garbageCollectionThread;
/**
* Constructor for TransientQueueBoundedMessageManager
*
* @param mgr
*/
public TransientQueueBoundedMessageManager(MemoryBoundedQueueManager mgr) {
this.queueManager = mgr;
this.containers = new ConcurrentHashMap();
this.destinationMap = new DestinationMap();
this.destinations = new ConcurrentHashMap();
this.subscriptions = new ConcurrentHashMap();
this.filterFactory = new FilterFactoryImpl();
this.started = new SynchronizedBoolean(false);
this.doingGarbageCollection = new SynchronizedBoolean(false);
}
/**
* start the manager
*
* @throws JMSException
*/
public void start() throws JMSException {
if (started.commit(false, true)) {
for (Iterator i = containers.values().iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
container.start();
}
garbageCollectionThread = new Thread(this);
garbageCollectionThread.setName("TQMCMGarbageCollector");
garbageCollectionThread.start();
}
}
/**
* stop the manager
*
* @throws JMSException
*/
public void stop() throws JMSException {
if (started.commit(true, false)) {
for (Iterator i = containers.values().iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
container.stop();
}
if (garbageCollectionThread != null) {
garbageCollectionThread.interrupt();
}
}
}
/**
* collect expired messages
*/
public void run() {
while (started.get()) {
doGarbageCollection();
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
}
}
}
/**
* Add a consumer if appropiate
*
* @param client
* @param info
* @throws JMSException
*/
public synchronized void addMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
ActiveMQDestination destination = info.getDestination();
if (destination.isQueue()) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) containers
.get(destination);
if (container == null) {
MemoryBoundedQueue queue = queueManager.getMemoryBoundedQueue(client.toString());
container = new TransientQueueBoundedMessageContainer(queueManager, destination);
addContainer(container);
if (started.get()) {
container.start();
}
}
TransientQueueSubscription ts = container.addConsumer(createFilter(info), info, client);
if (ts != null) {
subscriptions.put(info.getConsumerId(), ts);
}
String name = destination.getPhysicalName();
if (!destinations.containsKey(name)) {
destinations.put(name, destination);
}
}
}
/**
* @param client
* @param info
* @throws JMSException
*/
public synchronized void removeMessageConsumer(BrokerClient client, ConsumerInfo info) throws JMSException {
ActiveMQDestination destination = info.getDestination();
if (destination.isQueue()) {
for (Iterator i = containers.values().iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
if (container != null) {
container.removeConsumer(info);
}
}
subscriptions.remove(info.getConsumerId());
}
}
/**
* Delete a durable subscriber
*
* @param clientId
* @param subscriberName
* @throws JMSException if the subscriber doesn't exist or is still active
*/
public void deleteSubscription(String clientId, String subscriberName) throws JMSException {
}
/**
* @param client
* @param message
* @throws JMSException
*/
public void sendMessage(BrokerClient client, ActiveMQMessage message) throws JMSException {
if (message != null && message.getJMSActiveMQDestination().isQueue() && (message.isTemporary())) {
if (queueManager.getCurrentCapacity() <= GARBAGE_COLLECTION_CAPACITY_LIMIT) {
doGarbageCollection();
}
Set set = destinationMap.get(message.getJMSActiveMQDestination());
for (Iterator i = set.iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
container.enqueue(message);
}
}
}
/**
* @param client
* @param ack
* @throws JMSException
*/
public void acknowledgeMessage(BrokerClient client, MessageAck ack) throws JMSException {
TransientQueueSubscription ts = (TransientQueueSubscription) subscriptions.get(ack.getConsumerId());
if (ts != null) {
ActiveMQMessage message = ts.acknowledgeMessage(ack.getMessageID());
if (message != null && !ack.isMessageRead()){
message.setJMSRedelivered(true);
Set set = destinationMap.get(message.getJMSActiveMQDestination());
for (Iterator i = set.iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
container.enqueueFirst(message);
break;
}
}
}
}
/**
* @param client
* @param transactionId
* @param ack
* @throws JMSException
*/
public void acknowledgeTransactedMessage(BrokerClient client, String transactionId, MessageAck ack)
throws JMSException {
}
/**
* @param client
* @param ack
* @throws JMSException
*/
public void redeliverMessage(BrokerClient client, MessageAck ack) throws JMSException {
TransientQueueSubscription ts = (TransientQueueSubscription) subscriptions.get(ack.getConsumerId());
if (ts != null) {
ActiveMQMessage message = ts.acknowledgeMessage(ack.getMessageID());
if (message != null){
message.setJMSRedelivered(true);
Set set = destinationMap.get(message.getJMSActiveMQDestination());
for (Iterator i = set.iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
container.enqueueFirst(message);
break;
}
}
}
}
/**
* @throws JMSException
*/
public void poll() throws JMSException {
}
/**
* A hook when the transaction is about to be commited; so apply all outstanding commands to the Journal if using a
* Journal (transaction log)
*
* @param client
* @param transactionId
* @throws JMSException
*/
public void commitTransaction(BrokerClient client, String transactionId) throws JMSException {
}
/**
* A hook when the transaction is about to be rolled back; so discard all outstanding commands that are pending to
* be written to the Journal
*
* @param client
* @param transactionId
*/
public void rollbackTransaction(BrokerClient client, String transactionId) {
}
/**
* @param physicalName
* @return @throws JMSException
*/
public MessageContainer getContainer(String physicalName) throws JMSException {
Object key = destinations.get(physicalName);
if (key != null) {
return (MessageContainer) containers.get(key);
}
return null;
}
/**
* @return a map of destinations
*/
public Map getDestinations() {
return Collections.unmodifiableMap(destinations);
}
/**
* 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;
}
private void doGarbageCollection() {
if (doingGarbageCollection.commit(true, false)) {
if (queueManager.getCurrentCapacity() <= GARBAGE_COLLECTION_CAPACITY_LIMIT) {
for (Iterator i = containers.values().iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
container.removeExpiredMessages();
}
}
//if still below the limit - clear queues with no subscribers
if (queueManager.getCurrentCapacity() <= GARBAGE_COLLECTION_CAPACITY_LIMIT) {
for (Iterator i = containers.values().iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
if (!container.hasActiveSubscribers()) {
container.clear();
}
}
}
for (Iterator i = containers.values().iterator();i.hasNext();) {
TransientQueueBoundedMessageContainer container = (TransientQueueBoundedMessageContainer) i.next();
if (container.isInactive()) {
try {
container.close();
log.info("closed inactive transient queue container: " + container.getDestinationName());
}
catch (JMSException e) {
log.warn("failure closing container", e);
}
removeContainer(container);
}
}
//now close any inactive queues
doingGarbageCollection.set(false);
}
}
private synchronized void addContainer(TransientQueueBoundedMessageContainer container) {
containers.put(container.getDestination(), container);
destinationMap.put(container.getDestination(), container);
}
private synchronized void removeContainer(TransientQueueBoundedMessageContainer container) {
containers.remove(container.getDestination());
destinationMap.remove(container.getDestination(), container);
}
protected Destination createDestination(String destinationName) {
return null;
}
protected MessageContainer createContainer(String destinationName) throws JMSException {
return null;
}
}