
io.vertx.amqp.impl.AmqpSenderImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2018-2019 The original author or authors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.amqp.impl;
import io.vertx.amqp.AmqpConnection;
import io.vertx.amqp.AmqpMessage;
import io.vertx.amqp.AmqpSender;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.proton.ProtonDelivery;
import io.vertx.proton.ProtonSender;
import io.vertx.proton.impl.ProtonSenderImpl;
import org.apache.qpid.proton.amqp.messaging.Rejected;
public class AmqpSenderImpl implements AmqpSender {
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpSender.class);
private final ProtonSender sender;
private final AmqpConnectionImpl connection;
private boolean closed;
private Handler exceptionHandler;
private Handler drainHandler;
private long remoteCredit = 0;
private AmqpSenderImpl(ProtonSender sender, AmqpConnectionImpl connection,
Handler> completionHandler) {
this.sender = sender;
this.connection = connection;
sender
.closeHandler(res -> onClose(sender, res, false))
.detachHandler(res -> onClose(sender, res, true));
sender.sendQueueDrainHandler(s -> {
Handler dh = null;
synchronized (AmqpSenderImpl.this) {
// Update current state of remote credit
remoteCredit = ((ProtonSenderImpl) sender).getRemoteCredit();
// check the user drain handler, fire it outside synchronized block if not null
if (drainHandler != null) {
dh = drainHandler;
}
}
if (dh != null) {
dh.handle(null);
}
});
sender.openHandler(done -> {
if (done.failed()) {
completionHandler.handle(done.mapEmpty());
} else {
connection.register(this);
completionHandler.handle(Future.succeededFuture(this));
}
});
sender.open();
}
/**
* Creates a new instance of {@link AmqpSenderImpl}. The created sender is passed into the {@code completionHandler}
* once opened. This method must be called on the connection context.
*
* @param sender the underlying proton sender
* @param connection the connection
* @param completionHandler the completion handler
*/
static void create(ProtonSender sender, AmqpConnectionImpl connection,
Handler> completionHandler) {
new AmqpSenderImpl(sender, connection, completionHandler);
}
private void onClose(ProtonSender sender, AsyncResult res, boolean detach) {
Handler eh = null;
boolean closeSender = false;
synchronized (AmqpSenderImpl.this) {
if (!closed && exceptionHandler != null) {
eh = exceptionHandler;
}
if (!closed) {
closed = true;
closeSender = true;
}
}
if (eh != null) {
if (res.succeeded()) {
eh.handle(new Exception("Sender closed remotely"));
} else {
eh.handle(new Exception("Sender closed remotely with error", res.cause()));
}
}
if (closeSender) {
if (detach) {
sender.detach();
} else {
sender.close();
}
}
}
@Override
public synchronized boolean writeQueueFull() {
return remoteCredit <= 0;
}
@Override
public AmqpConnection connection() {
return connection;
}
@Override
public AmqpSender send(AmqpMessage message) {
return doSend(message, null);
}
private AmqpSender doSend(AmqpMessage message, Handler> acknowledgmentHandler) {
Handler ack = delivery -> {
Handler> handler = acknowledgmentHandler;
if (acknowledgmentHandler == null) {
handler = ar -> {
if (ar.failed()) {
LOGGER.warn("Message rejected by remote peer", ar.cause());
}
};
}
switch (delivery.getRemoteState().getType()) {
case Rejected:
handler.handle(Future.failedFuture("message rejected (REJECTED): " + ((Rejected) delivery.getRemoteState()).getError()));
break;
case Modified:
handler.handle(Future.failedFuture("message rejected (MODIFIED)"));
break;
case Released:
handler.handle(Future.failedFuture("message rejected (RELEASED)"));
break;
case Accepted:
handler.handle(Future.succeededFuture());
break;
default:
handler.handle(Future.failedFuture("Unsupported delivery type: " + delivery.getRemoteState().getType()));
}
};
synchronized (AmqpSenderImpl.this) {
// Update the credit tracking. We only need to adjust this here because the sends etc may not be on the context
// thread and if that is the case we can't use the ProtonSender sendQueueFull method to check that credit has been
// exhausted following this doSend call since we will have only scheduled the actual send for later.
remoteCredit--;
}
connection.runWithTrampoline(x -> {
AmqpMessage updated;
if (message.address() == null) {
updated = AmqpMessage.create(message).address(address()).build();
} else {
updated = message;
}
sender.send(updated.unwrap(), ack);
synchronized (AmqpSenderImpl.this) {
// Update the credit tracking *again*. We need to reinitialise it here in case the doSend call was performed on
// a thread other than the client context, to ensure we didn't fall foul of a race between the above pre-send
// update on that thread, the above send on the context thread, and the sendQueueDrainHandler based updates on
// the context thread.
remoteCredit = ((ProtonSenderImpl) sender).getRemoteCredit();
}
});
return this;
}
@Override
public synchronized AmqpSender exceptionHandler(Handler handler) {
exceptionHandler = handler;
return this;
}
@Override
public Future write(AmqpMessage data) {
Promise promise = Promise.promise();
doSend(data, promise);
return promise.future();
}
@Override
public void write(AmqpMessage data, Handler> handler) {
doSend(data, handler);
}
@Override
public AmqpSender setWriteQueueMaxSize(int maxSize) {
// No-op, available sending credit is controlled by recipient peer in AMQP 1.0.
return this;
}
@Override
public void end(Handler> handler) {
close(handler);
}
@Override
public synchronized AmqpSender drainHandler(Handler handler) {
drainHandler = handler;
return this;
}
@Override
public AmqpSender sendWithAck(AmqpMessage message, Handler> acknowledgementHandler) {
return doSend(message, acknowledgementHandler);
}
@Override
public Future sendWithAck(AmqpMessage message) {
Promise promise = Promise.promise();
sendWithAck(message, promise);
return promise.future();
}
@Override
public void close(Handler> handler) {
Handler> actualHandler;
if (handler == null) {
actualHandler = x -> { /* NOOP */ };
} else {
actualHandler = handler;
}
synchronized (this) {
if (closed) {
actualHandler.handle(Future.succeededFuture());
return;
}
closed = true;
}
connection.unregister(this);
connection.runWithTrampoline(x -> {
if (sender.isOpen()) {
try {
sender
.closeHandler(v -> actualHandler.handle(v.mapEmpty()))
.close();
} catch (Exception e) {
// Somehow closed remotely
actualHandler.handle(Future.failedFuture(e));
}
} else {
actualHandler.handle(Future.succeededFuture());
}
});
}
@Override
public Future close() {
Promise promise = Promise.promise();
close(promise);
return promise.future();
}
@Override
public String address() {
return sender.getRemoteAddress();
}
@Override
public long remainingCredits() {
return ((ProtonSenderImpl) sender).getRemoteCredit();
}
@Override
public ProtonSender unwrap() {
return sender;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy