All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jabb.dstream.WrappedJmsConnection Maven / Gradle / Ivy

/**
 * 
 */
package net.sf.jabb.dstream;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.ConnectionFactory;
import javax.jms.ConnectionMetaData;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;

import net.sf.jabb.util.jms.JmsUtility;
import net.sf.jabb.util.parallel.BackoffStrategy;
import net.sf.jabb.util.parallel.WaitStrategy;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.qpid.amqp_1_0.client.ConnectionClosedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Wrapped JMS Connection. The following features are added to the original JMS Connection:
 * 
    *
  • It re-connects whenever got disconnected.
  • *
  • The stop() and start() methods are now thread-safe.
  • *
* @author James Hu * */ public class WrappedJmsConnection implements Connection { static private final Logger logger = LoggerFactory.getLogger(WrappedJmsConnection.class); protected ConnectionFactory connectionFactory; protected volatile Connection connection; protected ExceptionListener exceptionListener; protected Predicate connectionValidator; protected AtomicInteger stopStartLatch = new AtomicInteger(0); protected AtomicBoolean isConnecting = new AtomicBoolean(false); protected int connectAttempts = 0; protected BackoffStrategy connectBackoffStrategy; protected WaitStrategy connectWaitStrategy; protected static ExecutorService threadPool; // shared across all connections /** * Constructor. Connection will be established immediately. * @param connectionFactory the factory for creating connections * @param connectionValidator validator returning true if a connection is considered to be valid * @param connectBackoffStrategy how to backoff between connect attempts * @param connectWaitStrategy how to wait for the backoff */ public WrappedJmsConnection(ConnectionFactory connectionFactory, Predicate connectionValidator, BackoffStrategy connectBackoffStrategy, WaitStrategy connectWaitStrategy){ this(connectionFactory, connectionValidator, connectBackoffStrategy, connectWaitStrategy, true); } /** * Constructor * @param connectionFactory the factory for creating connections * @param connectionValidator validator returning true if a connection is considered to be valid * @param connectBackoffStrategy how to backoff between connect attempts * @param connectWaitStrategy how to wait for the backoff * @param connect true if should connect now, false if no need to connect at this time */ public WrappedJmsConnection(ConnectionFactory connectionFactory, Predicate connectionValidator, BackoffStrategy connectBackoffStrategy, WaitStrategy connectWaitStrategy, boolean connect){ if (threadPool == null){ synchronized(WrappedJmsConnection.class){ if (threadPool == null){ threadPool = new ThreadPoolExecutor(Integer.MAX_VALUE, Integer.MAX_VALUE, 2, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new BasicThreadFactory.Builder() .namingPattern(WrappedJmsConnection.class.getSimpleName() + "-%d") .priority(Thread.MIN_PRIORITY) .build()); ((ThreadPoolExecutor)threadPool).allowCoreThreadTimeOut(true); } } } this.connectionFactory = connectionFactory; this.connectionValidator = connectionValidator; this.connectBackoffStrategy = connectBackoffStrategy; this.connectWaitStrategy = connectWaitStrategy; this.exceptionListener = new ExceptionListener(){ @Override public void onException(JMSException exception) { if (isConnectionClosed(exception)){ threadPool.execute(()->establishConnection()); }else{ logger.debug("[{}] Connection related {}", connectionFactory, JmsUtility.exceptionSummary(exception)); } } }; if (connect){ establishConnection(); } } /** * Check if the exception is caused by connection closed/shutdown * @param exception the JMS exception * @return true if the exception is caused by connection closed/shutdown, false otherwise. */ protected boolean isConnectionClosed(JMSException exception){ Exception linked = exception.getLinkedException(); if(linked != null && linked instanceof ConnectionClosedException){ return true; } String message = exception.getMessage(); if (message != null && (message.contains("class java.net.SocketException") || message.contains("Connection has been shutdown") || message.contains("Connection closed by remote host") || message.contains("The connection was inactive for more than the allowed period of time"))){ return true; } return false; } /** * Check if the exception is caused by connection closed/shutdown * @param exception the IllegalStateException * @return true if the exception is caused by connection closed/shutdown, false otherwise. */ protected boolean isConnectionClosed(IllegalStateException exception){ if ("Cannot create a session on a closed connection".equals(exception.getMessage())){ return true; } return false; } /** * Check if the exception is caused by connection closed/shutdown * @param exception the exception * @return true if the exception is caused by connection closed/shutdown, false otherwise. */ protected boolean isConnectionClosed(Exception exception){ if (exception instanceof JMSException){ return isConnectionClosed((JMSException)exception); } if (exception instanceof IllegalStateException){ return isConnectionClosed((IllegalStateException)exception); } return false; } /** * Establish or re-establish connection in case it has been closed. * It is thread-safe and has backoff between attempts. * @return true if a new connection established within this invocation, false otherwise */ public boolean establishConnection(){ return establishConnection(true); } /** * Is the connection being established? * @return true if the connection is being established, false otherwise */ public boolean isConnecting(){ return isConnecting.get(); } /** * Establish or re-establish connection in case it has been closed. * It is thread-safe and can optionally apply backoff between attempts. * @param backoff apply back off after failed attempt * @return true if a new connection established within this invocation, false otherwise */ public boolean establishConnection(boolean backoff){ if (isConnecting.compareAndSet(false, true)){ long startTime = System.currentTimeMillis(); try{ if (connection == null || !connectionValidator.test(connection)){ if (connection != null){ logger.debug("Connection closed: {}", connection); } connectAttempts ++; Connection newConn = null; try{ newConn = connectionFactory.createConnection(); if (connectionValidator.test(newConn)){ newConn.setExceptionListener(exceptionListener); }else{ closeSilently(newConn); newConn = null; } }catch(Exception e){ logger.warn("[{}] Failed to establish new connection to replace closed one: {}. {}", connectionFactory, connection, e instanceof JMSException? JmsUtility.exceptionSummary((JMSException) e) : "", e); closeSilently(newConn); newConn = null; } if (newConn != null){ Connection oldConn = connection; synchronized(stopStartLatch){ if (stopStartLatch.get() == 0){ try { newConn.start(); } catch (JMSException e) { logger.warn("[{}] Failed to start newly established connection: {}. {}", connectionFactory, newConn, JmsUtility.exceptionSummary(e), e); } } connection = newConn; } if (connection == newConn){ // successfully (optionally) started and replaced logger.info("[{}] New connection {} established for replacing {} in {}ms", connectionFactory, newConn, oldConn, System.currentTimeMillis() - startTime); closeSilently(oldConn); connectAttempts = 0; return true; }else{ closeSilently(newConn); newConn = null; } } // we arrived here because we failed to replace the old connection with a good new one if (backoff){ try { connectWaitStrategy.await(connectBackoffStrategy.computeBackoffMilliseconds(connectAttempts)); } catch (InterruptedException e1) { connectWaitStrategy.handleInterruptedException(e1); } } } }finally{ isConnecting.set(false); } }else{ // just ignore because someone else is handling it } return false; } /** * Validate a connection by creating a consumer to a destination * @param conn the connection to be tested * @param testConsumingDestination the destination * @return true if a consumer can be successfully created; false otherwise. */ public static boolean validateConnectionByCreatingConsumer(Connection conn, Destination testConsumingDestination){ Session session = null; MessageConsumer consumer = null; try { session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); consumer = session.createConsumer(testConsumingDestination); return true; }catch(Exception e){ logger.debug("[{}] Connection is not valid for receiving: {}", conn, e instanceof JMSException ? JmsUtility.exceptionSummary((JMSException)e) : e.getMessage() ); return false; }finally{ closeSilently(consumer, session); } } /** * Validate a connection by creating a producer to a destination * @param conn the connection to be tested * @param testProducingDestination the destination * @return true if a producer can be successfully created; false otherwise. */ public static boolean validateConnectionByCreatingProducer(Connection conn, Destination testProducingDestination){ Session session = null; MessageProducer producer = null; try { session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); producer = session.createProducer(testProducingDestination); return true; }catch(Exception e){ logger.debug("[{}] Connection is not valid for sending: {}", conn, e instanceof JMSException ? JmsUtility.exceptionSummary((JMSException)e) : e.getMessage() ); return false; }finally{ closeSilently(producer, session); } } /** * Get underlying connection * @return the original JMS connection created by the ConnectionFactory */ public Connection getConnection(){ for(int i = 0; i < 10 && isConnecting(); i ++){ try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } return connection; } /* (non-Javadoc) * @see javax.jms.Connection#createSession(boolean, int) */ @Override public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { Connection conn = getConnection(); if (conn == null){ establishConnection(false); } try{ return getConnection().createSession(transacted, acknowledgeMode); }catch(JMSException|IllegalStateException e){ if (isConnectionClosed(e)){ // Event Hub closed the connection if (establishConnection(false)){ return getConnection().createSession(transacted, acknowledgeMode); } } throw e; } } /* (non-Javadoc) * @see javax.jms.Connection#getClientID() */ @Override public String getClientID() throws JMSException { return getConnection().getClientID(); } /* (non-Javadoc) * @see javax.jms.Connection#setClientID(java.lang.String) */ @Override public void setClientID(String clientID) throws JMSException { getConnection().setClientID(clientID); } /* (non-Javadoc) * @see javax.jms.Connection#getMetaData() */ @Override public ConnectionMetaData getMetaData() throws JMSException { return getConnection().getMetaData(); } /* (non-Javadoc) * @see javax.jms.Connection#getExceptionListener() */ @Override public ExceptionListener getExceptionListener() throws JMSException { return getConnection().getExceptionListener(); } /* (non-Javadoc) * @see javax.jms.Connection#setExceptionListener(javax.jms.ExceptionListener) */ @Override public void setExceptionListener(ExceptionListener listener) throws JMSException { getConnection().setExceptionListener(listener); } /* (non-Javadoc) * @see javax.jms.Connection#start() */ @Override public void start() throws JMSException { synchronized(stopStartLatch){ if (stopStartLatch.decrementAndGet() == 0){ try{ getConnection().start(); }catch(JMSException | RuntimeException e){ throw e; } } } } /* (non-Javadoc) * @see javax.jms.Connection#stop() */ @Override public void stop() throws JMSException { synchronized(stopStartLatch){ if (stopStartLatch.getAndIncrement() == 0){ try{ getConnection().stop(); }catch(JMSException | RuntimeException e){ throw e; } } } } /* (non-Javadoc) * @see javax.jms.Connection#close() */ @Override public void close() throws JMSException { Connection conn = getConnection(); if (conn != null){ conn.close(); } } @Override public void finalize(){ try{ close(); }catch(Throwable t){ // ignore } } /* (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 { return getConnection().createConnectionConsumer(destination, messageSelector, sessionPool, maxMessages); } /* (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 { return getConnection().createDurableConnectionConsumer(topic, subscriptionName, messageSelector, sessionPool, maxMessages); } static protected void closeSilently(Connection conn){ if (conn != null){ try { conn.close(); } catch (Exception e) { // ignore } } } static protected void closeSilently(MessageConsumer consumer, Session session){ if (consumer != null){ try{ consumer.close(); }catch(Exception e){ // ignore } } if (session != null){ try{ session.close(); }catch(Exception e){ // ignore } } } static protected void closeSilently(MessageProducer producer, Session session){ if (producer != null){ try{ producer.close(); }catch(Exception e){ // ignore } } if (session != null){ try{ session.close(); }catch(Exception e){ // ignore } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy