/*
* Copyright 2003,2004 Colin Crist
*
* 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 hermes.impl;
import hermes.HermesDispatcher;
import hermes.HermesException;
import hermes.HermesMessageListener;
import hermes.util.JMSUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.apache.log4j.Category;
/**
* @author colincrist@hermesjms.com
* @version $Id: DefaultHermesDispatcherImpl.java,v 1.1 2004/05/01 15:52:35
* colincrist Exp $
*/
public class DefaultHermesDispatcherImpl implements HermesDispatcher, Runnable
{
private static final Category cat = Category.getInstance(DefaultHermesDispatcherImpl.class);
private static int numDispatchers = 0;
private Map<DestinationKeyWrapper, MessageListener> destinations = new HashMap<DestinationKeyWrapper, MessageListener>();
private Set<DestinationKeyWrapper> removedDestinations = new HashSet<DestinationKeyWrapper>();
private List queue = new ArrayList();
private DefaultHermesImpl hermes;
private boolean keepRunning = true;
private long sleepPeriod = 50;
private Thread dispatchThread;
private boolean synchronizeThreadStart = false;
/**
* Constructor for Dispatcher.
*/
public DefaultHermesDispatcherImpl(DefaultHermesImpl hermes)
{
super();
this.hermes = hermes;
}
/**
* Constructor for Dispatcher.
*/
public DefaultHermesDispatcherImpl(DefaultHermesImpl hermes, boolean synchronizeThreadStart)
{
super();
this.hermes = hermes;
this.synchronizeThreadStart = synchronizeThreadStart;
}
private final String getName(Destination d) throws JMSException
{
return hermes.getDestinationName(d);
}
/**
* Add a desination and message listener for messages to be dispatched to.
*/
public void addDestination(Destination d, MessageListener ml) throws JMSException
{
synchronized (destinations)
{
if (dispatchThread == null)
{
start();
}
destinations.put(new DestinationKeyWrapper(d), ml);
}
cat.debug("new destination: " + getName(d));
}
/**
* Remove a destination from dispatching
*/
public void removeDestination(final Destination d) throws JMSException
{
synchronized (destinations)
{
DestinationKeyWrapper key = new DestinationKeyWrapper(d) ;
if (destinations.remove(key) == null)
{
//
// If the thread is running give some extra error information
if (dispatchThread != null)
{
throw new JMSException("destination " + getName(d) + " not being dispatched on " + dispatchThread.getName());
}
else
{
throw new JMSException("destination " + getName(d) + " not registered");
}
}
else
{
removedDestinations.add(key);
}
}
cat.debug("removed destination: " + JMSUtils.getDestinationName(d));
}
/**
* Dispatch a runnable on this thread at the next opportunity, you can add
* events to be dispatched even though the object is not running on a thread,
* they will be queued.
*/
public void invoke(Runnable runnable) throws JMSException
{
synchronized (queue)
{
queue.add(runnable);
queue.notifyAll();
}
}
/**
* Thread mainline, alternate between pulling events off the dispatch queue
* as well as from the thread local session from the Hermes impl.
*/
public void run()
{
//
// Cache the thread and notify anyone who's waiting to be informed that
// the dispatcher is up and running
dispatchThread = Thread.currentThread();
synchronized (dispatchThread)
{
dispatchThread.notifyAll();
}
cat.debug("dispatcher starting");
while (keepRunning)
{
int messagesRead = 0;
//
// Drain any internal events, if message dispatching took a long
// time you may get several
// timer triggered runnables of the same type bunched up together.
synchronized (queue)
{
while (queue.size() > 0)
{
Runnable r = (Runnable) queue.remove(0);
r.run();
}
}
//
// Drain messages, just do a single pass accross all the
// destinations so that we don't
// starve any Runnable's on the queue.
synchronized (destinations)
{
if (removedDestinations.size() > 0)
{
for (DestinationKeyWrapper key : removedDestinations)
{
destinations.remove(key);
try
{
hermes.closeConsumer(key.getDestination());
}
catch (JMSException e)
{
cat.error("closing async consumer: " + e.getMessage(), e);
}
}
removedDestinations.clear();
}
if (destinations.size() == 0 && hermes.isOpen())
{
try
{
cat.debug("nothing to dispatch, closing Hermes " + hermes.getId());
hermes.close();
}
catch (JMSException e)
{
cat.error(e.getMessage(), e);
}
}
else
{
for (Map.Entry<DestinationKeyWrapper, MessageListener> entry : destinations.entrySet())
{
MessageListener ml = null;
try
{
ml = entry.getValue();
final Destination d = (Destination) entry.getKey().getDestination() ;
final Message m = hermes.receiveNoWait(d);
if (m != null)
{
if (ml != null)
{
ml.onMessage(m);
}
else
{
cat.fatal("no message listener available for destination " + hermes.getDestinationName(d) + " message discarded");
}
messagesRead++;
}
}
catch (JMSException ex)
{
cat.error(ex.getMessage(), ex);
removedDestinations.add(entry.getKey()) ;
if (ml instanceof HermesMessageListener)
{
((HermesMessageListener) ml).onException(ex);
}
break ;
}
}
}
}
//
// Only sleep if nothing happened last time round otherwise you'll
// soak the CPU.
if (messagesRead == 0)
{
try
{
Thread.sleep(sleepPeriod);
}
catch (InterruptedException e)
{
// NOP
}
}
}
dispatchThread = null;
cat.debug("dispatcher stopping");
}
/**
* Returns the sleepPeriod which is the time for the thread to sleep if no
* messages where dispatched on the last poll of all the destinations
* registered.
*
* @return long
*/
public long getSleepPeriod()
{
return sleepPeriod;
}
/**
* Sets the sleepPeriod which is the time for the thread to sleep if no
* messages where dispatched on the last poll of all the destinations
* registered.
*
* @param sleepPeriod
* The sleepPeriod to set
*/
public void setSleepPeriod(long sleepPeriod)
{
this.sleepPeriod = sleepPeriod;
}
/**
* @see hermes.HermesDispatcher#invokeAndWait(Runnable)
*/
public void invokeAndWait(final Runnable runnable) throws JMSException
{
if (dispatchThread == null)
{
throw new HermesException("dispatcher thread not running so cannot invokeAndWait");
}
Runnable myRunnable = new Runnable()
{
public void run()
{
runnable.run();
synchronized (this)
{
notifyAll();
}
}
};
synchronized (myRunnable)
{
invoke(myRunnable);
try
{
myRunnable.wait();
}
catch (Exception ex)
{
cat.error(ex.getMessage(), ex);
}
}
}
/**
* Helper to start the dispatcher on a thread. If synchronizeThreadStart is
* set then this will not return until this Runnable is active on the thread -
* this ensures that any subsequent dispatchAndWait() will not throw a
* DispatcherNotRunningException
*/
public Thread start() throws JMSException
{
synchronized (DefaultHermesDispatcherImpl.class)
{
return start("dispatcher-" + numDispatchers++);
}
}
/**
* Helper to start the dispatcher on a thread. If synchronizeThreadStart is
* set then this will not return until this Runnable is active on the thread -
* this ensures that any subsequent dispatchAndWait() will not throw a
* DispatcherNotRunningException
*
* @param threadName
* The name to assign the thread.
*/
public Thread start(String threadName) throws JMSException
{
if (dispatchThread != null)
{
throw new HermesException("Dispatcher thread not running");
}
Thread thread = new Thread(this, threadName);
synchronized (thread)
{
thread.start();
if (synchronizeThreadStart)
{
try
{
thread.wait();
}
catch (InterruptedException ex)
{
cat.error(ex.getMessage(), ex);
}
}
}
return thread;
}
/**
* @see hermes.HermesDispatcher#close()
*/
public void close() throws JMSException
{
keepRunning = false;
hermes.removeDispatcher(this);
}
/**
* @see hermes.HermesDispatcher#setMessageListener(Destination,
* MessageListener)
*/
public void setMessageListener(Destination from, MessageListener ml) throws JMSException
{
if (ml != null)
{
addDestination(from, ml);
}
else
{
removeDestination(from);
}
}
}