Java tutorial
/* * Copyright 2002-2019 the original author or authors. * * 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 * * https://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 org.springframework.amqp.rabbit.connection; import java.io.IOException; import java.util.Collection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.AmqpIOException; import org.springframework.amqp.rabbit.support.RabbitExceptionTranslator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.AlreadyClosedException; import com.rabbitmq.client.Channel; import com.rabbitmq.client.DefaultSaslConfig; import com.rabbitmq.client.JDKSaslConfig; import com.rabbitmq.client.Method; import com.rabbitmq.client.SaslConfig; import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.client.impl.CRDemoMechanism; import com.rabbitmq.client.impl.recovery.AutorecoveringChannel; /** * @author Mark Fisher * @author Mark Pollack * @author Gary Russell * @author Artem Bilan */ public abstract class RabbitUtils { /** * AMQP declare method. */ public static final int DECLARE_METHOD_ID_10 = 10; /** * AMQP consume method. */ public static final int CONSUME_METHOD_ID_20 = 20; /** * AMQP exchange class id. */ public static final int EXCHANGE_CLASS_ID_40 = 40; /** * AMQP queue class id. */ public static final int QUEUE_CLASS_ID_50 = 50; /** * AMQP basic class id. */ public static final int BASIC_CLASS_ID_60 = 60; /** * AMQP Connection protocol class id. */ public static final int CONNECTION_PROTOCOL_CLASS_ID_10 = 10; /** * AMQP Channel protocol class id. */ public static final int CHANNEL_PROTOCOL_CLASS_ID_20 = 20; private static final Log logger = LogFactory.getLog(RabbitUtils.class); // NOSONAR - lower case private static final ThreadLocal<Boolean> physicalCloseRequired = new ThreadLocal<>(); // NOSONAR - lower case /** * Close the given RabbitMQ Connection and ignore any thrown exception. This is useful for typical * <code>finally</code> blocks in manual RabbitMQ code. * @param connection the RabbitMQ Connection to close (may be <code>null</code>) */ public static void closeConnection(@Nullable Connection connection) { if (connection != null) { try { connection.close(); } catch (AlreadyClosedException ace) { // empty } catch (Exception ex) { logger.debug("Ignoring Connection exception - assuming already closed: " + ex.getMessage(), ex); } } } /** * Close the given RabbitMQ Channel and ignore any thrown exception. This is useful for typical <code>finally</code> * blocks in manual RabbitMQ code. * @param channel the RabbitMQ Channel to close (may be <code>null</code>) */ public static void closeChannel(@Nullable Channel channel) { if (channel != null) { try { channel.close(); } catch (AlreadyClosedException ace) { // empty } catch (IOException ex) { logger.debug("Could not close RabbitMQ Channel", ex); } catch (ShutdownSignalException sig) { if (!isNormalShutdown(sig)) { logger.debug("Unexpected exception on closing RabbitMQ Channel", sig); } } catch (Exception ex) { logger.debug("Unexpected exception on closing RabbitMQ Channel", ex); } } } /** * Commit the Channel if not within a JTA transaction. * @param channel the RabbitMQ Channel to commit */ public static void commitIfNecessary(Channel channel) { Assert.notNull(channel, "Channel must not be null"); try { channel.txCommit(); } catch (IOException ex) { throw new AmqpIOException(ex); } } public static void rollbackIfNecessary(Channel channel) { Assert.notNull(channel, "Channel must not be null"); try { channel.txRollback(); } catch (IOException ex) { throw new AmqpIOException(ex); } } public static void closeMessageConsumer(Channel channel, Collection<String> consumerTags, boolean transactional) { if (!channel.isOpen() && !(channel instanceof ChannelProxy && ((ChannelProxy) channel).getTargetChannel() instanceof AutorecoveringChannel) && !(channel instanceof AutorecoveringChannel)) { return; } try { for (String consumerTag : consumerTags) { cancel(channel, consumerTag); } if (transactional) { /* * Re-queue in-flight messages if any (after the consumer is cancelled to prevent the broker from simply * sending them back to us). Does not require a tx.commit. */ channel.basicRecover(true); } /* * If not transactional then we are auto-acking (at least as of 1.0.0.M2) so there is nothing to recover. * Messages are going to be lost in general. */ } catch (Exception ex) { throw RabbitExceptionTranslator.convertRabbitAccessException(ex); } } private static void cancel(Channel channel, String consumerTag) { try { channel.basicCancel(consumerTag); } catch (IOException e) { if (logger.isDebugEnabled()) { logger.debug("Error performing 'basicCancel'", e); } } catch (AlreadyClosedException e) { if (logger.isTraceEnabled()) { logger.trace(channel + " is already closed"); } } } /** * Declare to that broker that a channel is going to be used transactionally, and convert exceptions that arise. * @param channel the channel to use */ public static void declareTransactional(Channel channel) { try { channel.txSelect(); } catch (IOException e) { throw RabbitExceptionTranslator.convertRabbitAccessException(e); } } /** * Sets a ThreadLocal indicating the channel MUST be physically closed. * @param channel the channel. * @param b true if the channel must be closed (if it's a proxy). */ public static void setPhysicalCloseRequired(Channel channel, boolean b) { if (channel instanceof ChannelProxy) { physicalCloseRequired.set(b); } } /** * Gets and removes a ThreadLocal indicating the channel MUST be physically closed. * @return true if the channel must be physically closed */ public static boolean isPhysicalCloseRequired() { Boolean mustClose = physicalCloseRequired.get(); if (mustClose == null) { mustClose = Boolean.FALSE; } else { physicalCloseRequired.remove(); } return mustClose; } /** * Return true if the {@link ShutdownSignalException} reason is AMQP.Connection.Close and * the reply code was AMQP.REPLY_SUCCESS (200) and the text equals "OK". * @param sig the exception. * @return true for a normal connection close. */ public static boolean isNormalShutdown(ShutdownSignalException sig) { Method shutdownReason = sig.getReason(); return shutdownReason instanceof AMQP.Connection.Close && AMQP.REPLY_SUCCESS == ((AMQP.Connection.Close) shutdownReason).getReplyCode() && "OK".equals(((AMQP.Connection.Close) shutdownReason).getReplyText()); } /** * Return true if the {@link ShutdownSignalException} reason is AMQP.Channel.Close and * the reply code was AMQP.REPLY_SUCCESS (200) and the text equals "OK". * @param sig the exception. * @return true for a normal channel close. */ public static boolean isNormalChannelClose(ShutdownSignalException sig) { Method shutdownReason = sig.getReason(); return isNormalShutdown(sig) || (shutdownReason instanceof AMQP.Channel.Close && AMQP.REPLY_SUCCESS == ((AMQP.Channel.Close) shutdownReason).getReplyCode() && "OK".equals(((AMQP.Channel.Close) shutdownReason).getReplyText())); } /** * Return true if the {@link ShutdownSignalException} reason is AMQP.Channel.Close * and the operation that failed was exchangeDeclare or queueDeclare. * @param sig the exception. * @return true if the failure meets the conditions. */ public static boolean isPassiveDeclarationChannelClose(ShutdownSignalException sig) { Method shutdownReason = sig.getReason(); return shutdownReason instanceof AMQP.Channel.Close // NOSONAR boolean complexity && AMQP.NOT_FOUND == ((AMQP.Channel.Close) shutdownReason).getReplyCode() && ((((AMQP.Channel.Close) shutdownReason).getClassId() == EXCHANGE_CLASS_ID_40 || ((AMQP.Channel.Close) shutdownReason).getClassId() == QUEUE_CLASS_ID_50) && ((AMQP.Channel.Close) shutdownReason).getMethodId() == DECLARE_METHOD_ID_10); } /** * Return true if the {@link ShutdownSignalException} reason is AMQP.Channel.Close * and the operation that failed was basicConsumer and the failure text contains * "exclusive". * @param sig the exception. * @return true if the declaration failed because of an exclusive queue. */ public static boolean isExclusiveUseChannelClose(ShutdownSignalException sig) { Method shutdownReason = sig.getReason(); return shutdownReason instanceof AMQP.Channel.Close // NOSONAR boolean complexity && AMQP.ACCESS_REFUSED == ((AMQP.Channel.Close) shutdownReason).getReplyCode() && ((AMQP.Channel.Close) shutdownReason).getClassId() == BASIC_CLASS_ID_60 && ((AMQP.Channel.Close) shutdownReason).getMethodId() == CONSUME_METHOD_ID_20 && ((AMQP.Channel.Close) shutdownReason).getReplyText().contains("exclusive"); } /** * Return true if there is a {@link ShutdownSignalException} in the cause tree and its * reason is "PRECONDITION_FAILED" and the operation being performed was queueDeclare. * This can happen if a queue has mismatched properties (auto-delete etc) or arguments * (x-message-ttl etc). * @param e the exception. * @return true if the exception was due to queue declaration precondition failed. * @since 1.6 */ public static boolean isMismatchedQueueArgs(Exception e) { Throwable cause = e; ShutdownSignalException sig = null; while (cause != null && sig == null) { if (cause instanceof ShutdownSignalException) { sig = (ShutdownSignalException) cause; } cause = cause.getCause(); } if (sig == null) { return false; } else { Method shutdownReason = sig.getReason(); return shutdownReason instanceof AMQP.Channel.Close && AMQP.PRECONDITION_FAILED == ((AMQP.Channel.Close) shutdownReason).getReplyCode() && ((AMQP.Channel.Close) shutdownReason).getClassId() == QUEUE_CLASS_ID_50 && ((AMQP.Channel.Close) shutdownReason).getMethodId() == DECLARE_METHOD_ID_10; } } /** * Return true if there is a {@link ShutdownSignalException} in the cause tree and its * reason is "COMMAND_INVALID" and the operation being performed was exchangeDeclare. * For example attempting to declare an exchange that is not supported by the broker or * its plugins. * @param e the exception. * @return true if the exception was due to exchange declaration failed. * @since 1.6 */ public static boolean isExchangeDeclarationFailure(Exception e) { Throwable cause = e; ShutdownSignalException sig = null; while (cause != null && sig == null) { if (cause instanceof ShutdownSignalException) { sig = (ShutdownSignalException) cause; } cause = cause.getCause(); } if (sig == null) { return false; } else { Method shutdownReason = sig.getReason(); return shutdownReason instanceof AMQP.Connection.Close && AMQP.COMMAND_INVALID == ((AMQP.Connection.Close) shutdownReason).getReplyCode() && ((AMQP.Connection.Close) shutdownReason).getClassId() == EXCHANGE_CLASS_ID_40 && ((AMQP.Connection.Close) shutdownReason).getMethodId() == DECLARE_METHOD_ID_10; } } /** * Return the negotiated frame_max. * @param connectionFactory the connection factory. * @return the size or -1 if it cannot be determined. */ public static int getMaxFrame(ConnectionFactory connectionFactory) { try (Connection connection = connectionFactory.createConnection()) { com.rabbitmq.client.Connection rcon = connection.getDelegate(); if (rcon != null) { return rcon.getFrameMax(); } } catch (@SuppressWarnings("unused") RuntimeException e) { // NOSONAR } return -1; } /** * Convert a String value to a {@link SaslConfig}. * Valid string values: * <ul> * <li>{@code DefaultSaslConfig.PLAIN}</li> * <li>{@code DefaultSaslConfig.EXTERNAL}</li> * <li>{@code JDKSaslConfig}</li> * <li>{@code CRDemoSaslConfig}</li> * </ul> * @param saslConfig the string value. * @param connectionFactory the connection factory to get the name, pw, host. * @return the saslConfig. */ public static SaslConfig stringToSaslConfig(String saslConfig, com.rabbitmq.client.ConnectionFactory connectionFactory) { switch (saslConfig) { case "DefaultSaslConfig.PLAIN": return DefaultSaslConfig.PLAIN; case "DefaultSaslConfig.EXTERNAL": return DefaultSaslConfig.EXTERNAL; case "JDKSaslConfig": return new JDKSaslConfig(connectionFactory); case "CRDemoSaslConfig": return new CRDemoMechanism.CRDemoSaslConfig(); default: throw new IllegalStateException("Unrecognized SaslConfig: " + saslConfig); } } }