/**
* 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ReturnListener;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.TypeConversionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A method object for publishing to RabbitMQ
*/
public class RabbitMQMessagePublisher {
private static final Logger LOG = LoggerFactory.getLogger(RabbitMQMessagePublisher.class);
private final Exchange camelExchange;
private final Channel channel;
private final String routingKey;
private final RabbitMQEndpoint endpoint;
private final Message message;
private volatile boolean basicReturnReceived;
private final ReturnListener guaranteedDeliveryReturnListener = new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
LOG.warn("Delivery failed for exchange {} and routing key {}; replyCode = {}; replyText = {}", exchange, routingKey, replyCode, replyText);
basicReturnReceived = true;
}
};
public RabbitMQMessagePublisher(final Exchange camelExchange, final Channel channel, final String routingKey, final RabbitMQEndpoint endpoint) {
this.camelExchange = camelExchange;
this.channel = channel;
this.routingKey = routingKey;
this.endpoint = endpoint;
this.message = resolveMessageFrom(camelExchange);
}
private Message resolveMessageFrom(final Exchange camelExchange) {
Message message = camelExchange.hasOut() ? camelExchange.getOut() : camelExchange.getIn();
// Remove the SERIALIZE_HEADER in case it was previously set
if (message.getHeaders() != null && message.getHeaders().containsKey(RabbitMQEndpoint.SERIALIZE_HEADER)) {
LOG.debug("Removing the {} header", RabbitMQEndpoint.SERIALIZE_HEADER);
message.getHeaders().remove(RabbitMQEndpoint.SERIALIZE_HEADER);
}
if (routingKey != null && routingKey.startsWith(RabbitMQConstants.RABBITMQ_DIRECT_REPLY_ROUTING_KEY)) {
message.setHeader(RabbitMQConstants.EXCHANGE_NAME, RabbitMQConstants.RABBITMQ_DIRECT_REPLY_EXCHANGE); // use default exchange for reply-to messages
}
return message;
}
public void publish() throws IOException {
AMQP.BasicProperties properties;
byte[] body;
try {
// To maintain backwards compatibility try the TypeConverter (The DefaultTypeConverter seems to only work on Strings)
body = camelExchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, camelExchange, message.getBody());
properties = endpoint.getMessageConverter().buildProperties(camelExchange).build();
} catch (NoTypeConversionAvailableException | TypeConversionException e) {
if (message.getBody() instanceof Serializable) {
// Add the header so the reply processor knows to de-serialize it
message.getHeaders().put(RabbitMQEndpoint.SERIALIZE_HEADER, true);
properties = endpoint.getMessageConverter().buildProperties(camelExchange).build();
body = serializeBodyFrom(message);
} else if (message.getBody() == null) {
properties = endpoint.getMessageConverter().buildProperties(camelExchange).build();
body = null;
} else {
LOG.warn("Could not convert {} to byte[]", message.getBody());
throw new RuntimeCamelException(e);
}
}
publishToRabbitMQ(properties, body);
}
private void publishToRabbitMQ(final AMQP.BasicProperties properties, final byte[] body) throws IOException {
String rabbitExchange = endpoint.getExchangeName(message);
Boolean mandatory = camelExchange.getIn().getHeader(RabbitMQConstants.MANDATORY, endpoint.isMandatory(), Boolean.class);
Boolean immediate = camelExchange.getIn().getHeader(RabbitMQConstants.IMMEDIATE, endpoint.isImmediate(), Boolean.class);
LOG.debug("Sending message to exchange: {} with CorrelationId = {}", rabbitExchange, properties.getCorrelationId());
if (isPublisherAcknowledgements()) {
channel.confirmSelect();
}
if (endpoint.isGuaranteedDeliveries()) {
basicReturnReceived = false;
channel.addReturnListener(guaranteedDeliveryReturnListener);
}
try {
channel.basicPublish(rabbitExchange, routingKey, mandatory, immediate, properties, body);
if (isPublisherAcknowledgements()) {
waitForConfirmation();
}
} finally {
if (endpoint.isGuaranteedDeliveries()) {
channel.removeReturnListener(guaranteedDeliveryReturnListener);
}
}
}
private boolean isPublisherAcknowledgements() {
return endpoint.isPublisherAcknowledgements() || endpoint.isGuaranteedDeliveries();
}
private void waitForConfirmation() throws IOException {
try {
LOG.debug("Waiting for publisher acknowledgements for {}ms", endpoint.getPublisherAcknowledgementsTimeout());
channel.waitForConfirmsOrDie(endpoint.getPublisherAcknowledgementsTimeout());
if (basicReturnReceived) {
throw new RuntimeCamelException("Failed to deliver message; basic.return received");
}
} catch (InterruptedException | TimeoutException e) {
LOG.warn("Acknowledgement error for {}", camelExchange);
throw new RuntimeCamelException(e);
}
}
private byte[] serializeBodyFrom(final Message msg) throws IOException {
try (ByteArrayOutputStream b = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(b)) {
o.writeObject(msg.getBody());
return b.toByteArray();
} catch (NotSerializableException nse) {
LOG.warn("Can not send object " + msg.getBody().getClass() + " via RabbitMQ because it contains non-serializable objects.");
throw new RuntimeCamelException(nse);
}
}
}