
com.rabbitmq.jms.admin.RMQConnectionFactory Maven / Gradle / Ivy
/* Copyright (c) 2013-2018 Pivotal Software, Inc. All rights reserved. */
package com.rabbitmq.jms.admin;
import com.rabbitmq.client.Address;
import com.rabbitmq.jms.client.AmqpPropertiesCustomiser;
import com.rabbitmq.jms.client.ConnectionParams;
import com.rabbitmq.jms.client.RMQConnection;
import com.rabbitmq.jms.util.RMQJMSException;
import com.rabbitmq.jms.util.RMQJMSSecurityException;
import com.rabbitmq.jms.util.WhiteListObjectInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.*;
import javax.naming.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.TimeoutException;
import static com.rabbitmq.jms.util.UriCodec.*;
/**
* RabbitMQ Implementation of JMS {@link ConnectionFactory}
* TODO - implement SslContext option
*/
public class RMQConnectionFactory implements ConnectionFactory, Referenceable, Serializable, QueueConnectionFactory,
TopicConnectionFactory {
private final Logger logger = LoggerFactory.getLogger(RMQConnectionFactory.class);
private static final long serialVersionUID = -4953157213762979615L;
private static final int DEFAULT_RABBITMQ_SSL_PORT = com.rabbitmq.client.ConnectionFactory.DEFAULT_AMQP_OVER_SSL_PORT;
private static final int DEFAULT_RABBITMQ_PORT = com.rabbitmq.client.ConnectionFactory.DEFAULT_AMQP_PORT;
/** Default username to RabbitMQ broker */
private String username = "guest";
/** Default password to RabbitMQ broker */
private String password = "guest";
/** Default virtualhost */
private String virtualHost = "/";
/** Default host to RabbitMQ broker */
private String host = "localhost";
/** Default port NOT SET - determined by the type of connection (ssl or non-ssl) */
private int port = -1;
/** How long to wait for onMessage to return, in milliseconds */
private int onMessageTimeoutMs = 2000;
/**
* Whether {@link MessageProducer} properties (delivery mode,
* priority, TTL) take precedence over respective {@link Message}
* properties or not.
* Default is true (which is compliant to the JMS specification).
*/
private boolean preferProducerMessageProperty = true;
/**
* Whether requeue message on {@link RuntimeException} in the
* {@link javax.jms.MessageListener} or not.
* Default is false.
*/
private boolean requeueOnMessageListenerException = false;
/**
* Whether using auto-delete for server-named queues for non-durable topics.
* If set to true, those queues will be deleted when the session is closed.
* If set to false, queues will be deleted when the owning connection is closed.
* Default is false.
* @since 1.8.0
*/
private boolean cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose = false;
/**
* Callback to customise properties of outbound AMQP messages.
* @since 1.9.0
*/
private AmqpPropertiesCustomiser amqpPropertiesCustomiser;
/** Default not to use ssl */
private boolean ssl = false;
private String tlsProtocol;
private SSLContext sslContext;
private boolean useDefaultSslContext = false;
/** The maximum number of messages to read on a queue browser, which must be non-negative;
* 0 means unlimited and is the default; negative values are interpreted as 0. */
private int queueBrowserReadMax = Math.max(0, Integer.getInteger("rabbit.jms.queueBrowserReadMax", 0));
/** The time to wait for threads/messages to terminate during {@link Connection#close()} */
private volatile long terminationTimeout = Long.getLong("rabbit.jms.terminationTimeout", 15000);
/**
* QoS setting for channels created by this connection factory.
*
* @see com.rabbitmq.client.Channel#basicQos(int)
*/
private int channelsQos = RMQConnection.NO_CHANNEL_QOS;
/**
* Classes in these packages can be transferred via ObjectMessage.
*
* @see WhiteListObjectInputStream
*/
private List trustedPackages = WhiteListObjectInputStream.DEFAULT_TRUSTED_PACKAGES;
/**
* {@inheritDoc}
*/
@Override
public Connection createConnection() throws JMSException {
return this.createConnection(username, password);
}
public Connection createConnection(List endpoints) throws JMSException {
return this.createConnection(username, password, endpoints);
}
/**
* {@inheritDoc}
*/
@Override
public Connection createConnection(String username, String password) throws JMSException {
logger.trace("Creating a connection for username '{}', password 'xxxxxxxx'.", username);
this.username = username;
this.password = password;
// Create a new factory and set the properties
com.rabbitmq.client.ConnectionFactory factory = new com.rabbitmq.client.ConnectionFactory();
setRabbitUri(logger, this, factory, this.getUri());
maybeEnableTLS(factory);
com.rabbitmq.client.Connection rabbitConnection = instantiateNodeConnection(factory);
RMQConnection conn = new RMQConnection(new ConnectionParams()
.setRabbitConnection(rabbitConnection)
.setTerminationTimeout(getTerminationTimeout())
.setQueueBrowserReadMax(getQueueBrowserReadMax())
.setOnMessageTimeoutMs(getOnMessageTimeoutMs())
.setChannelsQos(channelsQos)
.setPreferProducerMessageProperty(preferProducerMessageProperty)
.setRequeueOnMessageListenerException(requeueOnMessageListenerException)
.setCleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose(this.cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose)
.setAmqpPropertiesCustomiser(this.amqpPropertiesCustomiser)
);
conn.setTrustedPackages(this.trustedPackages);
logger.debug("Connection {} created.", conn);
return conn;
}
public Connection createConnection(String username, String password, List endpoints)
throws JMSException {
logger.trace("Creating a connection for username '{}', password 'xxxxxxxx'.", username);
this.username = username;
this.password = password;
com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory();
maybeEnableTLS(cf);
com.rabbitmq.client.Connection rabbitConnection = instantiateNodeConnection(cf, endpoints);
RMQConnection conn = new RMQConnection(new ConnectionParams()
.setRabbitConnection(rabbitConnection)
.setTerminationTimeout(getTerminationTimeout())
.setQueueBrowserReadMax(getQueueBrowserReadMax())
.setOnMessageTimeoutMs(getOnMessageTimeoutMs())
.setChannelsQos(channelsQos)
.setPreferProducerMessageProperty(preferProducerMessageProperty)
.setRequeueOnMessageListenerException(requeueOnMessageListenerException)
.setCleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose(this.cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose)
);
conn.setTrustedPackages(this.trustedPackages);
logger.debug("Connection {} created.", conn);
return conn;
}
private com.rabbitmq.client.Connection instantiateNodeConnection(com.rabbitmq.client.ConnectionFactory cf)
throws JMSException {
try {
return cf.newConnection();
} catch (SSLException ssle) {
throw new RMQJMSSecurityException("SSL Exception establishing RabbitMQ Connection", ssle);
} catch (Exception x) {
if (x instanceof IOException) {
IOException ioe = (IOException) x;
String msg = ioe.getMessage();
if (msg!=null) {
if (msg.contains("authentication failure") || msg.contains("refused using authentication"))
throw new RMQJMSSecurityException(ioe);
else if (msg.contains("Connection refused"))
throw new RMQJMSException("RabbitMQ connection was refused. RabbitMQ broker may not be available.", ioe);
}
throw new RMQJMSException(ioe);
} else if (x instanceof TimeoutException) {
TimeoutException te = (TimeoutException) x;
throw new RMQJMSException("Timed out establishing RabbitMQ Connection", te);
} else {
throw new RMQJMSException("Unexpected exception thrown by newConnection()", x);
}
}
}
private com.rabbitmq.client.Connection instantiateNodeConnection(
com.rabbitmq.client.ConnectionFactory cf, List endpoints) throws JMSException {
try {
return cf.newConnection(endpoints);
} catch (SSLException ssle) {
throw new RMQJMSSecurityException("SSL Exception establishing RabbitMQ Connection", ssle);
} catch (Exception x) {
if (x instanceof IOException) {
IOException ioe = (IOException) x;
String msg = ioe.getMessage();
if (msg!=null) {
if (msg.contains("authentication failure") || msg.contains("refused using authentication"))
throw new RMQJMSSecurityException(ioe);
else if (msg.contains("Connection refused"))
throw new RMQJMSException("RabbitMQ connection was refused. RabbitMQ broker may not be available.", ioe);
}
throw new RMQJMSException(ioe);
} else if (x instanceof TimeoutException) {
TimeoutException te = (TimeoutException) x;
throw new RMQJMSException("Timed out establishing RabbitMQ Connection", te);
} else {
throw new RMQJMSException("Unexpected exception thrown by newConnection()", x);
}
}
}
/**
* Returns the current factory connection parameters in a URI String.
* @return URI for RabbitMQ connection (as a coded String)
*/
public String getUri() {
StringBuilder sb = new StringBuilder(scheme(isSsl())).append("://");
sb.append(uriUInfoEscape(this.username, this.password)).append('@');
sb.append(uriHostEscape(this.host)).append(':').append(this.getPort()).append("/");
sb.append(uriVirtualHostEscape(this.virtualHost));
return sb.toString();
}
public String toString() {
StringBuilder sb = new StringBuilder("RMQConnectionFactory{");
return (this.isSsl() ? sb.append("SSL, ") : sb)
.append("user='").append(this.username)
.append("', password").append(this.password!=null ? "=xxxxxxxx" : " not set")
.append(", host='").append(this.host)
.append("', port=").append(this.getPort())
.append(", virtualHost='").append(this.virtualHost)
.append("', onMessageTimeoutMs=").append(this.onMessageTimeoutMs)
.append(", queueBrowserReadMax=").append(this.queueBrowserReadMax)
.append('}').toString();
}
/**
* Set connection factory parameters by URI String.
* @param uriString URI to use for instantiated connection
* @throws JMSException if connection URI is invalid
*/
public void setUri(String uriString) throws JMSException {
logger.trace("Set connection factory parameters by URI '{}'", uriString);
// Create a temp factory and set the properties by uri
com.rabbitmq.client.ConnectionFactory factory = new com.rabbitmq.client.ConnectionFactory();
setRabbitUri(logger, this, factory, uriString);
// Now extract our properties from this factory, leaving the rest unchanged.
this.host = factory.getHost();
this.password = factory.getPassword();
this.port = factory.getPort();
this.ssl = factory.isSSL();
this.username = factory.getUsername();
this.virtualHost = factory.getVirtualHost();
}
/**
* @param value list of trusted package prefixes
*/
public void setTrustedPackages(List value) {
this.trustedPackages = value;
}
/**
* @return list of package prefixes that are whitelisted for transfer over {@link javax.jms.ObjectMessage}
*/
public List getTrustedPackages() {
return trustedPackages;
}
private static void setRabbitUri(Logger logger, RMQConnectionFactory rmqFactory, com.rabbitmq.client.ConnectionFactory factory, String uriString) throws RMQJMSException {
if (uriString != null) { // we get the defaults if the uri is null
try {
factory.setUri(uriString);
} catch (Exception e) {
logger.error("Could not set URI on {}", rmqFactory, e);
throw new RMQJMSException("Could not set URI on RabbitMQ connection factory.", e);
}
}
}
private void maybeEnableTLS(com.rabbitmq.client.ConnectionFactory factory) {
if (this.ssl)
try {
if(this.useDefaultSslContext) {
factory.useSslProtocol(SSLContext.getDefault());
} else {
if (this.sslContext != null) {
factory.useSslProtocol(this.sslContext);
} else if (this.tlsProtocol != null) {
factory.useSslProtocol(this.tlsProtocol);
} else {
factory.useSslProtocol();
}
}
} catch (Exception e) {
this.logger.warn("Could not set SSL protocol on connection factory, {}. SSL set off.", this, e);
this.ssl = false;
}
}
public boolean isSsl() {
return this.ssl;
}
/**
* @deprecated Use {@link #useSslProtocol()}, {@link #useSslProtocol(String)}
* or {@link #useSslProtocol(SSLContext)}.
* @param ssl if true, enables TLS for connections opened
*/
@Deprecated
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
/**
* Enables TLS on opened connections with the highest TLS
* version available.
* @throws NoSuchAlgorithmException see {@link NoSuchAlgorithmException}
*/
public void useSslProtocol() throws NoSuchAlgorithmException {
this.useSslProtocol(
com.rabbitmq.client.ConnectionFactory.computeDefaultTlsProcotol(
SSLContext.getDefault().getSupportedSSLParameters().getProtocols()));
}
/**
* Enables TLS on opened connections using the provided TLS protocol
* version.
* @param protocol TLS or SSL protocol version.
* @see JDK documentation on protocol names
*/
public void useSslProtocol(String protocol)
{
this.tlsProtocol = protocol;
this.ssl = true;
}
/**
* Enables TLS on opened connections using the provided {@link SSLContext}.
* @param context {@link SSLContext} to use
*/
public void useSslProtocol(SSLContext context) {
this.sslContext = context;
this.ssl = true;
}
/**
* Whether to use the default {@link SSLContext} or not.
* Default is false.
*
* When this option is enabled, the default {@link SSLContext}
* will always be used and will override any other {@link SSLContext}
* set.
*
* @param useDefaultSslContext
* @see SSLContext#getDefault()
*/
public void useDefaultSslContext(boolean useDefaultSslContext) {
this.useDefaultSslContext = useDefaultSslContext;
this.ssl = true;
}
/**
* Whether to use the default {@link SSLContext} or not.
*
* @see SSLContext#getDefault()
*/
public boolean isUseDefaultSslContext() {
return useDefaultSslContext;
}
/**
* Whether to use the default {@link SSLContext} or not.
* Default is false.
*
* When this option is enabled, the default {@link SSLContext}
* will always be used and will override any other {@link SSLContext}
* set.
*
* @param useDefaultSslContext
* @see SSLContext#getDefault()
*/
public void setUseDefaultSslContext(boolean useDefaultSslContext) {
this.useDefaultSslContext(useDefaultSslContext);
}
private static String scheme(boolean isSsl) {
return (isSsl ? "amqps" : "amqp");
}
private static String uriUInfoEscape(String user, String pass) {
if (null == user) return null;
if (null == pass) return encUserinfo(user, "UTF-8");
return encUserinfo(user + ":" + pass, "UTF-8");
}
private static String uriHostEscape(String host) {
return encHost(host, "UTF-8");
}
private static String uriVirtualHostEscape(String vHost) {
return encSegment(vHost, "UTF-8");
}
/**
* {@inheritDoc}
*/
@Override
public Reference getReference() throws NamingException {
Reference ref = new Reference(RMQConnectionFactory.class.getName());
addStringRefProperty(ref, "uri", this.getUri());
addIntegerRefProperty(ref, "queueBrowserReadMax", this.getQueueBrowserReadMax());
addIntegerRefProperty(ref, "onMessageTimeoutMs", this.getOnMessageTimeoutMs());
return ref;
}
/**
* Adds a String valued property to a Reference (as a RefAddr)
* @param ref - the reference to contain the value
* @param propertyName - the name of the property
* @param value - the value to store with the property
*/
private static void addStringRefProperty(Reference ref,
String propertyName,
String value) {
if (value==null || propertyName==null) return;
RefAddr ra = new StringRefAddr(propertyName, value);
ref.add(ra);
}
/**
* Adds an integer valued property to a Reference (as a RefAddr).
* @param ref - the reference to contain the value
* @param propertyName - the name of the property
* @param value - the value to store with the property
*/
private static void addIntegerRefProperty(Reference ref,
String propertyName,
Integer value) {
if (value == null || propertyName == null) return;
RefAddr ra = new StringRefAddr(propertyName, String.valueOf(value));
ref.add(ra);
}
/**
* {@inheritDoc}
*/
@Override
public TopicConnection createTopicConnection() throws JMSException {
return (TopicConnection) this.createConnection();
}
/**
* {@inheritDoc}
*/
@Override
public TopicConnection createTopicConnection(String userName, String password) throws JMSException {
return (TopicConnection) this.createConnection(userName, password);
}
/**
* {@inheritDoc}
*/
@Override
public QueueConnection createQueueConnection() throws JMSException {
return (QueueConnection) this.createConnection();
}
/**
* {@inheritDoc}
*/
@Override
public QueueConnection createQueueConnection(String userName, String password) throws JMSException {
return (QueueConnection) this.createConnection(userName, password);
}
/**
* Returns the configured username used when creating a connection If
* {@link RMQConnectionFactory#setUsername(String)} has not been called the default value of 'guest' is returned.
*
* @return a string representing the username for a RabbitMQ connection
*/
public String getUsername() {
return username;
}
/**
* Sets the username to be used when creating a connection to the RabbitMQ broker.
* If the parameter is null
the current username is not changed.
*
* @param username - username to be used when creating a connection to the RabbitMQ broker
*/
public void setUsername(String username) {
if (username != null) this.username = username;
else this.logger.warn("Cannot set username to null (on {})", this);
}
/**
* Returns the configured password used when creating a connection If
* {@link RMQConnectionFactory#setPassword(String)} has not been called the default value of 'guest' is returned.
*
* @return a string representing the password for a Rabbit connection
*/
public String getPassword() {
return password;
}
/**
* Sets the password to be used when creating a connection to the RabbitMQ broker
*
* @param password - password to be used when creating a connection to the RabbitMQ broker
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Returns the virtual host used when creating a connection. If
* {@link RMQConnectionFactory#setVirtualHost(String)} has not been called the default value of '/' is returned.
*
* @return a String representing the virtual host for a RabbitMQ connection
*/
public String getVirtualHost() {
return virtualHost;
}
/**
* Sets the virtualHost to be used when creating a connection to the RabbitMQ broker.
* If the parameter is null
the current virtualHost is not changed.
*
* @param virtualHost - virtual host to be used when creating a connection to the RabbitMQ broker
*/
public void setVirtualHost(String virtualHost) {
if (virtualHost != null) this.virtualHost = virtualHost;
else this.logger.warn("Cannot set virtualHost to null (on {})", this);
}
/**
* Returns the host name to be used when creating a connection to the RabbitMQ broker.
*
* @return the host name of the RabbitMQ broker
*/
public String getHost() {
return host;
}
/**
* Sets the host of the RabbitMQ broker. The host name can be an IP address or a host name.
* If the parameter is null
the current host name is not changed.
*
* @param host - IP address or a host name of the RabbitMQ broker, in String form
*/
public void setHost(String host) {
if (host != null) this.host = host;
else this.logger.warn("Cannot set host to null (on {})", this);
}
/**
* Returns the port the RabbitMQ broker listens to; this port is used to connect to the broker.
* If the port has not been set (defaults to -1) then the default port for this type of connection is returned.
*
* @return the port the RabbitMQ broker listens to
*/
public int getPort() {
return this.port!=-1 ? this.port
: isSsl() ? DEFAULT_RABBITMQ_SSL_PORT
: DEFAULT_RABBITMQ_PORT;
}
/**
* Set the port to be used when making a connection to the RabbitMQ broker. This is the port number the broker will listen on.
* Setting this to -1 means take the RabbitMQ default (which depends on the type of connection).
*
* @param port - a TCP port number
*/
public void setPort(int port) {
this.port = port;
}
/**
* Returns the time to wait in milliseconds when {@link Connection#close()} has been called for listeners and threads to
* complete.
*
* @return the duration in milliseconds for which the {@link Connection#close()} waits before continuing shutdown sequence
*/
public long getTerminationTimeout() {
return terminationTimeout;
}
/**
* Sets terminationTimeout: the time in milliseconds a {@link Connection#close()} should wait for threads/tasks/listeners to complete
*
* @param terminationTimeout - duration in milliseconds
*/
public void setTerminationTimeout(long terminationTimeout) {
this.terminationTimeout = terminationTimeout;
}
/**
* Returns the maximum number of messages to read on a queue browser, or zero if there is no limit.
*
* @return the maximum number of messages to read on a queue browser
*/
public int getQueueBrowserReadMax() {
return this.queueBrowserReadMax;
}
/**
* Sets queueBrowserReadMax: the maximum number of messages to read on a queue browser.
* Non-positive values are set to zero, which is interpreted as no limit.
*
* @param queueBrowserReadMax - read no more than this number of messages on a queue browser.
*/
public void setQueueBrowserReadMax(int queueBrowserReadMax) {
this.queueBrowserReadMax = Math.max(0, queueBrowserReadMax);
}
/**
* Returns the time in milliseconds {@link MessageListener#onMessage(Message)} can take to process a message
* @return the time in milliseconds {@link MessageListener#onMessage(Message)} can take to process a message
*/
public int getOnMessageTimeoutMs() { return this.onMessageTimeoutMs; }
/**
* Sets onMessageTimeoutMs: the time in milliseconds {@link MessageListener#onMessage(Message)} can take to process a message.
* Non-positive values are rejected.
* @param onMessageTimeoutMs - duration in milliseconds
*/
public void setOnMessageTimeoutMs(int onMessageTimeoutMs){
if (onMessageTimeoutMs > 0) this.onMessageTimeoutMs = onMessageTimeoutMs;
else this.logger.warn("Cannot set onMessageTimeoutMs to non-positive value {} (on {})", onMessageTimeoutMs, this);
}
/**
* QoS setting for channels created by this connection factory.
*
* @see com.rabbitmq.client.Channel#basicQos(int)
*/
public int getChannelsQos() {
return channelsQos;
}
/**
* QoS setting for channels created by this connection factory.
*
* @see com.rabbitmq.client.Channel#basicQos(int)
* @param channelsQos maximum number of messages that the server
* will deliver, 0 if unlimited
*/
public void setChannelsQos(int channelsQos) {
this.channelsQos = channelsQos;
}
/**
* Whether {@link MessageProducer} properties (delivery mode,
* priority, TTL) take precedence over respective {@link Message}
* properties or not.
* Default is true (which is compliant to the JMS specification).
*/
public void setPreferProducerMessageProperty(boolean preferProducerMessageProperty) {
this.preferProducerMessageProperty = preferProducerMessageProperty;
}
public boolean isPreferProducerMessageProperty() {
return preferProducerMessageProperty;
}
/**
* Whether requeue message on {@link RuntimeException} in the
* {@link javax.jms.MessageListener} or not.
* Default is false.
*/
public void setRequeueOnMessageListenerException(boolean requeueOnMessageListenerException) {
this.requeueOnMessageListenerException = requeueOnMessageListenerException;
}
public boolean isRequeueOnMessageListenerException() {
return requeueOnMessageListenerException;
}
public void setCleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose(boolean cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose) {
this.cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose = cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose;
}
public boolean isCleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose() {
return this.cleanUpServerNamedQueuesForNonDurableTopicsOnSessionClose;
}
public void setAmqpPropertiesCustomiser(AmqpPropertiesCustomiser amqpPropertiesCustomiser) {
this.amqpPropertiesCustomiser = amqpPropertiesCustomiser;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy