/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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.apache.camel.component.rabbitmq;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.ShutdownSignalException;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.RuntimeCamelException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class RabbitConsumer implements com.rabbitmq.client.Consumer {
private final Logger log = LoggerFactory.getLogger(getClass());
private final RabbitMQConsumer consumer;
private Channel channel;
private String tag;
/** Consumer tag for this consumer. */
private volatile String consumerTag;
private volatile boolean stopping;
private final Semaphore lock = new Semaphore(1);
/**
* Constructs a new instance and records its association to the passed-in
* channel.
*/
RabbitConsumer(RabbitMQConsumer consumer) {
// super(channel);
this.consumer = consumer;
try {
Connection conn = consumer.getConnection();
this.channel = openChannel(conn);
} catch (IOException | TimeoutException e) {
log.warn("Unable to open channel for RabbitMQConsumer. Continuing and will try again", e);
}
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
if (!consumer.getEndpoint().isAutoAck()) {
lock.acquire();
}
//Channel might be open because while we were waiting for the lock, stop() has been succesfully called.
if (!channel.isOpen()) {
return;
}
try {
doHandleDelivery(consumerTag, envelope, properties, body);
} finally {
if (!consumer.getEndpoint().isAutoAck()) {
lock.release();
}
}
} catch (InterruptedException e) {
log.warn("Thread Interrupted!");
}
}
public void doHandleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
Exchange exchange = consumer.getEndpoint().createRabbitExchange(envelope, properties, body);
consumer.getEndpoint().getMessageConverter().mergeAmqpProperties(exchange, properties);
boolean sendReply = properties.getReplyTo() != null;
if (sendReply && !exchange.getPattern().isOutCapable()) {
log.debug("In an inOut capable route");
exchange.setPattern(ExchangePattern.InOut);
}
log.trace("Created exchange [exchange={}]", exchange);
long deliveryTag = envelope.getDeliveryTag();
try {
consumer.getProcessor().process(exchange);
} catch (Exception e) {
exchange.setException(e);
}
// obtain the message after processing
Message msg;
if (exchange.hasOut()) {
msg = exchange.getOut();
} else {
msg = exchange.getIn();
}
if (exchange.getException() != null) {
consumer.getExceptionHandler().handleException("Error processing exchange", exchange, exchange.getException());
}
if (!exchange.isFailed()) {
// processing success
if (sendReply && exchange.getPattern().isOutCapable()) {
try {
consumer.getEndpoint().publishExchangeToChannel(exchange, channel, properties.getReplyTo());
} catch (RuntimeCamelException e) {
// set the exception on the exchange so it can send the
// exception back to the producer
exchange.setException(e);
consumer.getExceptionHandler().handleException("Error processing exchange", exchange, e);
}
}
if (!consumer.getEndpoint().isAutoAck()) {
log.trace("Acknowledging receipt [delivery_tag={}]", deliveryTag);
channel.basicAck(deliveryTag, false);
}
}
// The exchange could have failed when sending the above message
if (exchange.isFailed()) {
if (consumer.getEndpoint().isTransferException() && exchange.getPattern().isOutCapable()) {
// the inOut exchange failed so put the exception in the body
// and send back
msg.setBody(exchange.getException());
exchange.setOut(msg);
exchange.getOut().setHeader(RabbitMQConstants.CORRELATIONID, exchange.getIn().getHeader(RabbitMQConstants.CORRELATIONID));
try {
consumer.getEndpoint().publishExchangeToChannel(exchange, channel, properties.getReplyTo());
} catch (RuntimeCamelException e) {
consumer.getExceptionHandler().handleException("Error processing exchange", exchange, e);
}
if (!consumer.getEndpoint().isAutoAck()) {
log.trace("Acknowledging receipt when transferring exception [delivery_tag={}]", deliveryTag);
channel.basicAck(deliveryTag, false);
}
} else {
boolean isRequeueHeaderSet = msg.getHeader(RabbitMQConstants.REQUEUE, false, boolean.class);
// processing failed, then reject and handle the exception
if (deliveryTag != 0 && !consumer.getEndpoint().isAutoAck()) {
log.trace("Rejecting receipt [delivery_tag={}] with requeue={}", deliveryTag, isRequeueHeaderSet);
if (isRequeueHeaderSet) {
channel.basicReject(deliveryTag, true);
} else {
channel.basicReject(deliveryTag, false);
}
}
}
}
}
/**
* Bind consumer to channel
*/
public void start() throws IOException {
if (channel == null) {
throw new IOException("The RabbitMQ channel is not open");
}
tag = channel.basicConsume(consumer.getEndpoint().getQueue(), consumer.getEndpoint().isAutoAck(), this);
}
/**
* Unbind consumer from channel
*/
public void stop() throws IOException, TimeoutException {
stopping = true;
if (channel == null) {
return;
}
if (tag != null && isChannelOpen()) {
channel.basicCancel(tag);
}
try {
lock.acquire();
if (isChannelOpen()) {
channel.close();
}
} catch (TimeoutException e) {
log.error("Timeout occured");
throw e;
} catch (InterruptedException e1) {
log.error("Thread Interrupted!");
} finally {
lock.release();
}
}
/**
* Stores the most recently passed-in consumerTag - semantically, there
* should be only one.
*
* @see Consumer#handleConsumeOk
*/
public void handleConsumeOk(String consumerTag) {
this.consumerTag = consumerTag;
}
/**
* Retrieve the consumer tag.
*
* @return the most recently notified consumer tag.
*/
public String getConsumerTag() {
return consumerTag;
}
/**
* No-op implementation of {@link Consumer#handleCancelOk}.
*
* @param consumerTag
* the defined consumer tag (client- or server-generated)
*/
public void handleCancelOk(String consumerTag) {
// no work to do
log.debug("Received cancelOk signal on the rabbitMQ channel");
}
/**
* No-op implementation of {@link Consumer#handleCancel(String)}
*
* @param consumerTag
* the defined consumer tag (client- or server-generated)
*/
public void handleCancel(String consumerTag) throws IOException {
// no work to do
log.debug("Received cancel signal on the rabbitMQ channel");
}
/**
* No-op implementation of {@link Consumer#handleShutdownSignal}.
*/
public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
log.info("Received shutdown signal on the rabbitMQ channel");
// Check if the consumer closed the connection or something else
if (!sig.isInitiatedByApplication()) {
// Something else closed the connection so reconnect
boolean connected = false;
while (!connected && !stopping) {
try {
reconnect();
connected = true;
} catch (IOException | TimeoutException e) {
log.warn("Unable to obtain a RabbitMQ channel. Will try again");
Integer networkRecoveryInterval = consumer.getEndpoint().getNetworkRecoveryInterval();
final long connectionRetryInterval = networkRecoveryInterval != null && networkRecoveryInterval > 0
? networkRecoveryInterval : 100L;
try {
Thread.sleep(connectionRetryInterval);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
}
}
}
}
/**
* No-op implementation of {@link Consumer#handleRecoverOk}.
*/
public void handleRecoverOk(String consumerTag) {
// no work to do
log.debug("Received recover ok signal on the rabbitMQ channel");
}
/**
* If the RabbitMQ connection is good this returns without changing
* anything. If the connection is down it will attempt to reconnect
*/
public void reconnect() throws IOException, TimeoutException {
if (isChannelOpen()) {
// The connection is good, so nothing to do
return;
}
log.info("Attempting to open a new rabbitMQ channel");
Connection conn = consumer.getConnection();
channel = openChannel(conn);
// Register the channel to the tag
start();
}
private boolean isChannelOpen() {
return channel != null && channel.isOpen();
}
/**
* Open channel
*/
private Channel openChannel(Connection conn) throws IOException {
log.trace("Creating channel...");
Channel channel = conn.createChannel();
log.debug("Created channel: {}", channel);
// setup the basicQos
if (consumer.getEndpoint().isPrefetchEnabled()) {
channel.basicQos(consumer.getEndpoint().getPrefetchSize(), consumer.getEndpoint().getPrefetchCount(),
consumer.getEndpoint().isPrefetchGlobal());
}
// This really only needs to be called on the first consumer or on
// reconnections.
if (consumer.getEndpoint().isDeclare()) {
consumer.getEndpoint().declareExchangeAndQueue(channel);
}
return channel;
}
}