/*
* 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.common.connection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.ConnectionMetaData;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.IllegalStateException;
import javax.jms.InvalidClientIDException;
import javax.jms.JMSException;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;
import net.timewalker.ffmq3.FFMQException;
import net.timewalker.ffmq3.common.session.AbstractSession;
import net.timewalker.ffmq3.utils.ErrorTools;
import net.timewalker.ffmq3.utils.StringTools;
import net.timewalker.ffmq3.utils.id.IntegerID;
import net.timewalker.ffmq3.utils.id.IntegerIDProvider;
import net.timewalker.ffmq3.utils.id.UUIDProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>Base implementation for a JMS connection</p>
*/
public abstract class AbstractConnection implements Connection
{
private static final Log log = LogFactory.getLog(AbstractConnection.class);
// ID and client ID handling
protected String id = UUIDProvider.getInstance().getUUID();
protected String clientID;
private static ConnectionMetaData metaData = new ConnectionMetaDataImpl();
// Runtime
protected boolean started;
protected boolean closed;
private ExceptionListener exceptionListener;
private Set temporaryQueues = new HashSet();
private Set temporaryTopics = new HashSet();
protected IntegerIDProvider idProvider = new IntegerIDProvider();
protected Object externalAccessLock = new Object();
private Object exceptionListenerLock = new Object();
// Children
private Map sessions = new Hashtable();
/**
* Constructor
*/
public AbstractConnection( String clientID )
{
this.clientID = clientID;
}
/**
* Test if the connection is closed
*/
public boolean isClosed()
{
synchronized (externalAccessLock)
{
return closed;
}
}
/**
* Get the connection id
* @return the id
*/
public String getId()
{
return id;
}
/* (non-Javadoc)
* @see javax.jms.Connection#getClientID()
*/
public String getClientID() throws JMSException
{
synchronized (externalAccessLock)
{
if (clientID == null)
throw new InvalidClientIDException("Client ID not set");
return clientID;
}
}
/* (non-Javadoc)
* @see javax.jms.Connection#getMetaData()
*/
public ConnectionMetaData getMetaData()
{
return metaData;
}
/* (non-Javadoc)
* @see javax.jms.Connection#setClientID(java.lang.String)
*/
public void setClientID(String clientID) throws JMSException
{
synchronized (externalAccessLock)
{
checkNotClosed();
if (StringTools.isEmpty(clientID))
throw new InvalidClientIDException("Empty client ID");
if (this.clientID != null)
throw new IllegalStateException("Client ID is already set"); // [JMS SPEC]
this.clientID = clientID;
}
}
/* (non-Javadoc)
* @see javax.jms.Connection#getExceptionListener()
*/
public ExceptionListener getExceptionListener()
{
synchronized (exceptionListenerLock)
{
return exceptionListener;
}
}
/* (non-Javadoc)
* @see javax.jms.Connection#setExceptionListener(javax.jms.ExceptionListener)
*/
public void setExceptionListener(ExceptionListener listener) throws JMSException
{
synchronized (exceptionListenerLock)
{
checkNotClosed();
this.exceptionListener = listener;
}
}
/**
* Triggered when a JMSException is internally catched
*/
public void exceptionOccured( JMSException exception )
{
try
{
synchronized (exceptionListenerLock)
{
if (exceptionListener != null)
exceptionListener.onException(exception);
}
}
catch (Exception e)
{
log.error("Exception listener failed",e);
}
}
/**
* Register a temporary queue name
*/
public void registerTemporaryQueue( String queueName )
{
synchronized (temporaryQueues)
{
temporaryQueues.add(queueName);
}
}
/**
* Unregister a temporary queue name
*/
public void unregisterTemporaryQueue( String queueName )
{
synchronized (temporaryQueues)
{
temporaryQueues.remove(queueName);
}
}
/**
* Check if a temporary queue was registered with this connection
*/
public boolean isRegisteredTemporaryQueue( String queueName )
{
synchronized (temporaryQueues)
{
return temporaryQueues.contains(queueName);
}
}
/**
* Register a temporary topic name
*/
public void registerTemporaryTopic( String topicName )
{
synchronized (temporaryTopics)
{
temporaryTopics.add(topicName);
}
}
/**
* Unregister a temporary topic name
*/
public void unregisterTemporaryTopic( String topicName )
{
synchronized (temporaryTopics)
{
temporaryTopics.remove(topicName);
}
}
/**
* Check if a temporary topic was registered with this connection
*/
public boolean isRegisteredTemporaryTopic( String topicName )
{
synchronized (temporaryTopics)
{
return temporaryTopics.contains(topicName);
}
}
/**
* Drop all registered temporary queues
*/
private void dropTemporaryQueues()
{
synchronized (temporaryQueues)
{
Iterator remainingQueues = temporaryQueues.iterator();
while (remainingQueues.hasNext())
{
String queueName = (String)remainingQueues.next();
try
{
deleteTemporaryQueue(queueName);
}
catch (JMSException e)
{
ErrorTools.log(e, log);
}
}
}
}
/**
* Delete a temporary queue
*/
public abstract void deleteTemporaryQueue( String queueName ) throws JMSException;
/**
* Delete a temporary topic
*/
public abstract void deleteTemporaryTopic( String topicName ) throws JMSException;
/* (non-Javadoc)
* @see javax.jms.Connection#close()
*/
public final void close()
{
synchronized (externalAccessLock)
{
if (closed)
return;
closed = true;
onConnectionClose();
}
onConnectionClosed();
}
protected void onConnectionClose()
{
// Close remaining sessions
closeRemainingSessions();
// Drop temporary queues
dropTemporaryQueues();
}
protected void onConnectionClosed()
{
// Nothing
}
/**
* Close remaining sessions
*/
private void closeRemainingSessions()
{
if (sessions == null)
return;
List sessionsToClose = new ArrayList(sessions.size());
synchronized (sessions)
{
sessionsToClose.addAll(sessions.values());
}
for (int n = 0 ; n < sessionsToClose.size() ; n++)
{
Session session = (Session)sessionsToClose.get(n);
log.debug("Auto-closing unclosed session : "+session);
try
{
session.close();
}
catch (JMSException e)
{
ErrorTools.log(e, log);
}
}
}
/**
* Wake up all children consumers
*/
protected final void wakeUpConsumers()
{
try
{
List sessionsSnapshot = new ArrayList(sessions.size());
synchronized (sessions)
{
sessionsSnapshot.addAll(sessions.values());
}
for(int n=0;n<sessionsSnapshot.size();n++)
{
AbstractSession session = (AbstractSession)sessionsSnapshot.get(n);
session.wakeUpConsumers();
}
}
catch (JMSException e)
{
ErrorTools.log(e, log);
exceptionOccured(e);
}
}
/**
* Lookup a registered session
*/
public final AbstractSession lookupRegisteredSession( IntegerID sessionId )
{
return (AbstractSession)sessions.get(sessionId);
}
/**
* Register a session
*/
protected final void registerSession( AbstractSession sessionToAdd )
{
if (sessions.put(sessionToAdd.getId(),sessionToAdd) != null)
throw new IllegalArgumentException("Session "+sessionToAdd.getId()+" already exists");
}
/**
* Unregister a session
*/
public final void unregisterSession( AbstractSession sessionToRemove )
{
if (sessions.remove(sessionToRemove.getId()) == null)
log.warn("Unknown session : "+sessionToRemove);
}
/**
* Check that the connection is not closed
*/
protected final void checkNotClosed() throws JMSException
{
if (closed)
throw new FFMQException("Connection is closed","CONNECTION_CLOSED");
}
/*
* (non-Javadoc)
* @see javax.jms.Connection#createConnectionConsumer(javax.jms.Destination, java.lang.String, javax.jms.ServerSessionPool, int)
*/
public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException
{
throw new FFMQException("Unsupported feature","UNSUPPORTED_FEATURE");
}
/*
* (non-Javadoc)
* @see javax.jms.Connection#createDurableConnectionConsumer(javax.jms.Topic, java.lang.String, java.lang.String, javax.jms.ServerSessionPool, int)
*/
public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException
{
throw new FFMQException("Unsupported feature","UNSUPPORTED_FEATURE");
}
/* (non-Javadoc)
* @see java.lang.Object#finalize()
*/
protected void finalize() throws Throwable
{
if (externalAccessLock != null && !closed)
{
log.warn("Connection was not properly closed, closing it now.");
try
{
close();
}
catch (Throwable e)
{
log.error("Could not auto-close connection",e);
}
}
}
/**
* Check if the connection is started
* NOT SYNCHRONIZED TO AVOID DEADLOCKS
*/
public boolean isStarted()
{
return started && !closed;
}
/**
* Get the number of active sessions for this connection
* @return the number of active sessions for this connection
*/
public int getSessionsCount()
{
return sessions.size();
}
/**
* Get the number of active producers for this connection
* @return the number of active producers for this connection
*/
public int getConsumersCount()
{
synchronized (sessions)
{
if (sessions.isEmpty())
return 0;
int total = 0;
Iterator sessionsIterator = sessions.values().iterator();
while (sessionsIterator.hasNext())
{
AbstractSession session = (AbstractSession)sessionsIterator.next();
total += session.getConsumersCount();
}
return total;
}
}
/**
* Get the number of active producers for this connection
* @return the number of active producers for this connection
*/
public int getProducersCount()
{
synchronized (sessions)
{
if (sessions.isEmpty())
return 0;
int total = 0;
Iterator sessionsIterator = sessions.values().iterator();
while (sessionsIterator.hasNext())
{
AbstractSession session = (AbstractSession)sessionsIterator.next();
total += session.getProducersCount();
}
return total;
}
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("Connection[#");
sb.append(id);
sb.append("](started=");
sb.append(started);
sb.append(")");
return sb.toString();
}
/**
* Get a description of entities held by this object
*/
public void getEntitiesDescription( StringBuffer sb )
{
sb.append(toString());
sb.append("{");
synchronized (sessions)
{
if (!sessions.isEmpty())
{
int pos = 0;
Iterator sessionsIterator = sessions.values().iterator();
while (sessionsIterator.hasNext())
{
AbstractSession session = (AbstractSession)sessionsIterator.next();
if (pos++ > 0)
sb.append(",");
session.getEntitiesDescription(sb);
}
}
}
sb.append("}");
}
}