/*
* Copyright 2017 Jean-Francois Arcand
*
* 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
*
* 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.atmosphere.plugin.rabbitmq;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.MessageProperties;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.cpr.BroadcasterFuture;
import org.atmosphere.cpr.Deliver;
import org.atmosphere.util.SimpleBroadcaster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* Simple {@link org.atmosphere.cpr.Broadcaster} implementation based on RabbitMQ
*
* @author Thibault Normand
* @author Jean-Francois Arcand
*/
public class RabbitMQBroadcaster extends SimpleBroadcaster implements ShutdownListener {
private static final Logger logger = LoggerFactory.getLogger(RabbitMQBroadcaster.class);
private String queueName;
private String consumerTag;
private RabbitMQConnectionFactory factory;
private Channel channel;
private String exchangeName;
public RabbitMQBroadcaster() {
}
@Override
public Broadcaster initialize(String id, AtmosphereConfig config) {
super.initialize(id, config);
init(config);
return this;
}
public void init(AtmosphereConfig config) {
factory = RabbitMQConnectionFactory.getFactory(config);
channel = factory.channel();
channel.addShutdownListener(this);
exchangeName = factory.exchangeName();
restartConsumer();
}
@Override
public Broadcaster initialize(String name, java.net.URI uri, AtmosphereConfig config) {
super.initialize(name, uri, config);
init(config);
return this;
}
@Override
public void setID(String id) {
super.setID(id);
restartConsumer();
}
@Override
public String getID() {
String id = super.getID();
if (id.startsWith("/*")) {
id = "atmosphere";
}
return id;
}
@Override
protected void push(Deliver entry) {
if (destroyed.get()) {
return;
}
outgoingBroadcast(entry.getMessage());
}
public void outgoingBroadcast(Object message) {
try {
String id = getID();
logger.trace("Outgoing broadcast : {}", message);
channel.basicPublish(exchangeName, id,
MessageProperties.PERSISTENT_TEXT_PLAIN, message.toString().getBytes());
} catch (IOException e) {
logger.warn("Failed to send message over RabbitMQ", e);
}
}
void restartConsumer() {
try {
final String id = getID();
if (consumerTag != null) {
logger.debug("Delete consumer {}", consumerTag);
channel.basicCancel(consumerTag);
consumerTag = null;
}
if (queueName != null) {
logger.debug("Delete queue {}", queueName);
channel.queueUnbind(queueName, exchangeName, id);
channel.queueDelete(queueName);
queueName = null;
}
queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, exchangeName, id);
logger.info("Create AMQP consumer on queue {}, for routing key {}", queueName, id);
DefaultConsumer queueConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
// Not for us.
if (!envelope.getRoutingKey().equalsIgnoreCase(id)) return;
String message = new String(body);
try {
Object newMsg = filter(message);
// if newSgw == null, that means the message has been filtered.
if (newMsg != null) {
deliverPush(new Deliver(newMsg, new BroadcasterFuture<Object>(newMsg), message), true);
}
} catch (Throwable t) {
logger.error("failed to push message: " + message, t);
}
}
};
consumerTag = channel.basicConsume(queueName, true, queueConsumer);
logger.info("Consumer " + consumerTag + " for queue {}, on routing key {}", queueName, id);
} catch (Throwable ex) {
String msg = "Unable to initialize RabbitMQBroadcaster";
logger.error(msg, ex);
throw new IllegalStateException(msg, ex);
}
}
@Override
public synchronized void releaseExternalResources() {
try {
if (channel != null && channel.isOpen()) {
if (consumerTag != null) {
channel.basicCancel(consumerTag);
}
}
} catch (Exception ex) {
logger.trace("", ex);
}
}
@Override
public void shutdownCompleted(ShutdownSignalException cause) {
this.destroy();
}
}