net.timewalker.ffmq4.common.connection.AbstractConnection.java Source code

Java tutorial

Introduction

Here is the source code for net.timewalker.ffmq4.common.connection.AbstractConnection.java

Source

/*
 * 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.ffmq4.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 java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

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.ffmq4.FFMQException;
import net.timewalker.ffmq4.common.session.AbstractSession;
import net.timewalker.ffmq4.utils.ErrorTools;
import net.timewalker.ffmq4.utils.StringTools;
import net.timewalker.ffmq4.utils.id.IntegerID;
import net.timewalker.ffmq4.utils.id.IntegerIDProvider;
import net.timewalker.ffmq4.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().getShortUUID();
    protected String clientID;
    private static ConnectionMetaData metaData = new ConnectionMetaDataImpl();

    // Runtime
    protected boolean started;
    protected boolean closed;
    private ExceptionListener exceptionListener;
    private Set<String> temporaryQueues = new HashSet<>();
    private Set<String> temporaryTopics = new HashSet<>();
    protected IntegerIDProvider idProvider = new IntegerIDProvider();
    protected ReadWriteLock externalAccessLock = new ReentrantReadWriteLock();
    private Object exceptionListenerLock = new Object();

    // Children
    private Map<IntegerID, AbstractSession> sessions = new Hashtable<>();

    /**
     * Constructor
     */
    public AbstractConnection(String clientID) {
        this.clientID = clientID;
    }

    /**
     * Get the connection id
     * @return the id
     */
    public String getId() {
        return id;
    }

    /* (non-Javadoc)
     * @see javax.jms.Connection#getClientID()
     */
    @Override
    public String getClientID() throws JMSException {
        externalAccessLock.readLock().lock();
        try {
            if (clientID == null)
                throw new InvalidClientIDException("Client ID not set");
            return clientID;
        } finally {
            externalAccessLock.readLock().unlock();
        }
    }

    /* (non-Javadoc)
     * @see javax.jms.Connection#getMetaData()
     */
    @Override
    public ConnectionMetaData getMetaData() {
        return metaData;
    }

    /* (non-Javadoc)
     * @see javax.jms.Connection#setClientID(java.lang.String)
     */
    @Override
    public void setClientID(String clientID) throws JMSException {
        externalAccessLock.readLock().lock();
        try {
            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;
        } finally {
            externalAccessLock.readLock().unlock();
        }
    }

    /* (non-Javadoc)
     * @see javax.jms.Connection#getExceptionListener()
     */
    @Override
    public ExceptionListener getExceptionListener() {
        synchronized (exceptionListenerLock) {
            return exceptionListener;
        }
    }

    /* (non-Javadoc)
     * @see javax.jms.Connection#setExceptionListener(javax.jms.ExceptionListener)
     */
    @Override
    public void setExceptionListener(ExceptionListener listener) throws JMSException {
        synchronized (exceptionListenerLock) {
            checkNotClosed();
            this.exceptionListener = listener;
        }
    }

    /**
     * Triggered when a JMSException is internally catched
     */
    public final 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 final void registerTemporaryQueue(String queueName) {
        synchronized (temporaryQueues) {
            temporaryQueues.add(queueName);
        }
    }

    /**
     * Unregister a temporary queue name
     */
    public final void unregisterTemporaryQueue(String queueName) {
        synchronized (temporaryQueues) {
            temporaryQueues.remove(queueName);
        }
    }

    /**
     * Check if a temporary queue was registered with this connection
     */
    public final boolean isRegisteredTemporaryQueue(String queueName) {
        synchronized (temporaryQueues) {
            return temporaryQueues.contains(queueName);
        }
    }

    /**
     * Register a temporary topic name
     */
    public final void registerTemporaryTopic(String topicName) {
        synchronized (temporaryTopics) {
            temporaryTopics.add(topicName);
        }
    }

    /**
     * Unregister a temporary topic name
     */
    public final void unregisterTemporaryTopic(String topicName) {
        synchronized (temporaryTopics) {
            temporaryTopics.remove(topicName);
        }
    }

    /**
     * Check if a temporary topic was registered with this connection
     */
    public final boolean isRegisteredTemporaryTopic(String topicName) {
        synchronized (temporaryTopics) {
            return temporaryTopics.contains(topicName);
        }
    }

    /**
     * Drop all registered temporary queues
     */
    private void dropTemporaryQueues() {
        synchronized (temporaryQueues) {
            Iterator<String> remainingQueues = temporaryQueues.iterator();
            while (remainingQueues.hasNext()) {
                String queueName = 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()
     */
    @Override
    public final void close() {
        externalAccessLock.writeLock().lock();
        try {
            if (closed)
                return;
            closed = true;
            onConnectionClose();
        } finally {
            externalAccessLock.writeLock().unlock();
        }
        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<AbstractSession> sessionsToClose = new ArrayList<>(sessions.size());
        synchronized (sessions) {
            sessionsToClose.addAll(sessions.values());
        }
        for (int n = 0; n < sessionsToClose.size(); n++) {
            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
     * (Used by LocalConnection only)
     */
    protected final void wakeUpLocalConsumers() {
        try {
            List<AbstractSession> sessionsSnapshot = new ArrayList<>(sessions.size());
            synchronized (sessions) {
                sessionsSnapshot.addAll(sessions.values());
            }
            for (int n = 0; n < sessionsSnapshot.size(); n++) {
                AbstractSession session = sessionsSnapshot.get(n);
                session.wakeUpConsumers();
            }
        } catch (JMSException e) {
            ErrorTools.log(e, log);
        }
    }

    /**
     * Wait for sessions to finish the current deliveridispatching
     */
    protected final void waitForDeliverySync() {
        List<AbstractSession> sessionsSnapshot = new ArrayList<>(sessions.size());
        synchronized (sessions) {
            sessionsSnapshot.addAll(sessions.values());
        }
        for (int n = 0; n < sessionsSnapshot.size(); n++) {
            AbstractSession session = sessionsSnapshot.get(n);
            session.waitForDeliverySync();
        }
    }

    /**
     * Lookup a registered session
     */
    public final AbstractSession lookupRegisteredSession(IntegerID sessionId) {
        return 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)
     */
    @Override
    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)
     */
    @Override
    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()
     */
    @Override
    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<AbstractSession> sessionsIterator = sessions.values().iterator();
            while (sessionsIterator.hasNext()) {
                AbstractSession session = 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<AbstractSession> sessionsIterator = sessions.values().iterator();
            while (sessionsIterator.hasNext()) {
                AbstractSession session = sessionsIterator.next();
                total += session.getProducersCount();
            }
            return total;
        }
    }

    /*
     *  (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        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(StringBuilder sb) {
        sb.append(toString());
        sb.append("{");
        synchronized (sessions) {
            if (!sessions.isEmpty()) {
                int pos = 0;
                Iterator<AbstractSession> sessionsIterator = sessions.values().iterator();
                while (sessionsIterator.hasNext()) {
                    AbstractSession session = sessionsIterator.next();
                    if (pos++ > 0)
                        sb.append(",");
                    session.getEntitiesDescription(sb);
                }
            }
        }
        sb.append("}");
    }
}