package io.muoncore.extension.amqp; import io.muoncore.Discovery; import io.muoncore.channel.impl.StandardAsyncChannel; import io.muoncore.channel.support.Scheduler; import io.muoncore.codec.Codecs; import io.muoncore.exception.MuonException; import io.muoncore.exception.MuonTransportFailureException; import io.muoncore.message.MuonInboundMessage; import io.muoncore.message.MuonMessage; import io.muoncore.message.MuonMessageBuilder; import io.muoncore.message.MuonOutboundMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.Dispatcher; import java.io.IOException; import java.util.Date; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class DefaultAmqpChannel implements AmqpChannel { public static final String CHANNEL_SHUTDOWN = "ChannelShutdown"; private String sendQueue; private String receiveQueue; private QueueListenerFactory listenerFactory; private ChannelFunction<MuonInboundMessage> function; private AmqpConnection connection; private String localServiceName; private Logger log = LoggerFactory.getLogger(DefaultAmqpChannel.class.getName()); private Dispatcher dispatcher; private Codecs codecs; private Discovery discovery; private Scheduler scheduler; private CountDownLatch handshakeControl = new CountDownLatch(1); private QueueListener listener; private ChannelFunction onShutdown; private boolean ownsQueues = false; public DefaultAmqpChannel(AmqpConnection connection, QueueListenerFactory queueListenerFactory, String localServiceName, Dispatcher dispatcher, Codecs codecs, Discovery discovery, Scheduler scheduler) { this.connection = connection; this.listenerFactory = queueListenerFactory; this.localServiceName = localServiceName; this.dispatcher = dispatcher; this.codecs = codecs; this.discovery = discovery; this.scheduler = scheduler; } @Override public void onShutdown(ChannelFunction runnable) { this.onShutdown = runnable; } @Override public void initiateHandshake(String serviceName, String protocol) { ownsQueues = true; receiveQueue = localServiceName + "-receive-" + UUID.randomUUID().toString(); sendQueue = serviceName + "-receive-" + UUID.randomUUID().toString(); listener = listenerFactory.listenOnQueue(receiveQueue, msg -> { log.trace("Received a message on the receive queue " + msg.getQueueName()); if ("accepted".equals(msg.getHandshakeMessage())) { log.trace("Handshake completed"); handshakeControl.countDown(); return; } if (function != null) { MuonInboundMessage inbound = AmqpMessageTransformers.queueToInbound(msg, codecs); if (inbound.getStep().equals(CHANNEL_SHUTDOWN)) { function.apply(null); } else { dispatcher.dispatch(inbound, function::apply, Throwable::printStackTrace); } } }); try { connection.send(QueueMessageBuilder.queue("service." + serviceName) .protocol(protocol) .serverReplyTo(receiveQueue) .handshakeMessage("initiated") .recieveQueue(sendQueue) .build()); } catch (IOException e) { throw new MuonTransportFailureException("Unable to initiate handshake", e); } try { if (!handshakeControl.await(3000, TimeUnit.MILLISECONDS)) throw new MuonException("The handshake took too long! target " + serviceName + " / " + protocol + " || " + receiveQueue); } catch (InterruptedException e) { throw new MuonException("The handshake took too long!", e); } } @Override public void respondToHandshake(AmqpHandshakeMessage message) { ownsQueues = false; log.debug("Handshake received " + message); receiveQueue = message.getReceiveQueue(); sendQueue = message.getReplyQueue(); log.debug("Opening queue to listen " + receiveQueue); listener = listenerFactory.listenOnQueue(receiveQueue, msg -> { MuonInboundMessage inboundMessage = AmqpMessageTransformers.queueToInbound(msg, codecs); log.debug("Received inbound channel message [" + receiveQueue + "] of type " + message.getProtocol() + ":" + inboundMessage.getStep()); if (StandardAsyncChannel.echoOut) System.out.println(new Date() + ": Channel[ AMQP Wire >>>>> DefaultAMQPChannel]: Received " + inboundMessage); if (inboundMessage.getChannelOperation() == MuonMessage.ChannelOperation.closed) { function.apply(null); } else if (function != null) { function.apply(inboundMessage); } }); try { QueueListener.QueueMessage handshakeResponse = QueueMessageBuilder.queue(message.getReplyQueue()) .protocol(message.getProtocol()) .handshakeMessage("accepted").build(); log.debug("Handshake Response sent " + handshakeResponse); connection.send(handshakeResponse); } catch (IOException e) { throw new MuonTransportFailureException("Unable to respond to handshake", e); } finally { handshakeControl.countDown(); } } @Override public void shutdown() { try { listener.cancel(); } catch (Exception e) { } // if (ownsQueues) { connection.deleteQueue(sendQueue); connection.deleteQueue(receiveQueue); // } if (onShutdown != null) { this.onShutdown.apply(null); } } @Override public void receive(ChannelFunction<MuonInboundMessage> function) { this.function = function; } @Override public void send(MuonOutboundMessage message) { if (StandardAsyncChannel.echoOut) System.out.println(new Date() + ": Channel[ DefaultAMQPChannel >>>>> AMQP Wire]: Sending " + message); if (message != null) { log.debug("Sending inbound channel message of type " + message.getProtocol() + "||" + message.getStep()); dispatcher.dispatch(message, msg -> { try { connection.send(AmqpMessageTransformers.outboundToQueue(sendQueue, message, codecs, discovery)); if (msg.getChannelOperation() == MuonMessage.ChannelOperation.closed) { shutdown(); } } catch (IOException e) { e.printStackTrace(); } }, Throwable::printStackTrace); } else { send( MuonMessageBuilder.fromService(localServiceName) .step(CHANNEL_SHUTDOWN) .operation(MuonMessage.ChannelOperation.closed) .contentType("text/plain") .payload(new byte[0]) .build()); } } }