170 lines
7.7 KiB
Java
170 lines
7.7 KiB
Java
package tc.oc.api.queue;
|
|
|
|
import java.util.concurrent.TimeoutException;
|
|
import javax.annotation.Nullable;
|
|
import javax.inject.Inject;
|
|
|
|
import com.google.common.reflect.TypeToken;
|
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
import java.time.Duration;
|
|
import tc.oc.api.exceptions.ApiException;
|
|
import tc.oc.api.message.Message;
|
|
import tc.oc.api.message.MessageHandler;
|
|
import tc.oc.api.message.types.Reply;
|
|
import tc.oc.commons.core.concurrent.TimeoutFuture;
|
|
import tc.oc.commons.core.util.ExceptionUtils;
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
|
|
/**
|
|
* This is a {@link ListenableFuture} that sends a {@link Message} to an {@link Exchange}
|
|
* and then waits to receive a reply on a given {@link Queue}. The reply-to header on the
|
|
* request message is set to the name of the reply queue, and the eventual reply message
|
|
* is identified by its correlation-id matching the message-id of the request. There is
|
|
* also a timeout, which defaults to 30 seconds, that will cause the future to fail with
|
|
* a {@link TimeoutException}.
|
|
*/
|
|
public class Transaction<T extends Message> extends TimeoutFuture<T> {
|
|
|
|
public static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
|
|
|
|
private final String requestId;
|
|
private final Class<? extends Message> requestType;
|
|
private final Queue replyQueue;
|
|
private final MessageHandler<Message> messageHandler;
|
|
private final StackTraceElement[] callSite;
|
|
|
|
public static class Factory {
|
|
private final Exchange.Direct exchange;
|
|
private final PrimaryQueue primaryQueue;
|
|
|
|
@Inject Factory(Exchange.Direct exchange, PrimaryQueue primaryQueue) {
|
|
this.exchange = exchange;
|
|
this.primaryQueue = primaryQueue;
|
|
}
|
|
|
|
/**
|
|
* @param request Outgoing request message
|
|
* @param properties Metadata for the request (null for default properties)
|
|
* @param publish Publishing options for the request (null for default options)
|
|
* @param replyType Type of the reply message (if the reply isn't assignable to this, it will be missed)
|
|
* @param timeout Time to wait for the reply before failing (null for DEFAULT_TIMEOUT)
|
|
*/
|
|
public <T extends Message> Transaction<T> request(Message request,
|
|
@Nullable Metadata properties,
|
|
@Nullable Publish publish,
|
|
TypeToken<T> replyType,
|
|
@Nullable Duration timeout) {
|
|
|
|
return new Transaction<>(exchange, primaryQueue, request, properties, publish, replyType, timeout);
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, @Nullable Metadata properties, @Nullable Publish publish, Class<T> replyType, @Nullable Duration timeout) {
|
|
return request(request, properties, publish, TypeToken.of(replyType), timeout);
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, TypeToken<T> replyType, @Nullable Publish publish) {
|
|
return request(request, null, publish, replyType, null);
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, Class<T> replyType, @Nullable Publish publish) {
|
|
return request(request, null, publish, replyType, null);
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, TypeToken<T> replyType, String routingKey) {
|
|
return request(request, replyType, new Publish(routingKey));
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, Class<T> replyType, String routingKey) {
|
|
return request(request, replyType, new Publish(routingKey));
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, TypeToken<T> replyType) {
|
|
return request(request, replyType, (Publish) null);
|
|
}
|
|
|
|
public <T extends Message> Transaction<T> request(Message request, Class<T> replyType) {
|
|
return request(request, replyType, (Publish) null);
|
|
}
|
|
|
|
public Transaction<Reply> request(Message request, @Nullable Publish publish) {
|
|
return request(request, Reply.class, publish);
|
|
}
|
|
|
|
public Transaction<Reply> request(Message request, String routingKey) {
|
|
return request(request, new Publish(routingKey));
|
|
}
|
|
|
|
public Transaction<Reply> request(Message request) {
|
|
return request(request, (Publish) null);
|
|
}
|
|
}
|
|
|
|
private Transaction(Exchange.Direct exchange,
|
|
final Queue replyQueue,
|
|
Message request,
|
|
@Nullable Metadata requestProps,
|
|
@Nullable Publish publish,
|
|
TypeToken<T> replyType,
|
|
@Nullable Duration timeout) {
|
|
|
|
super(timeout != null ? timeout : DEFAULT_TIMEOUT);
|
|
|
|
checkNotNull(request, "request");
|
|
checkNotNull(replyType, "replyType");
|
|
|
|
this.callSite = new Exception().getStackTrace();
|
|
this.replyQueue = replyQueue;
|
|
|
|
final Metadata finalRequestProps = requestProps = replyQueue.client().getProperties(request, requestProps);
|
|
finalRequestProps.setReplyTo(this.replyQueue.name());
|
|
|
|
this.requestId = checkNotNull(finalRequestProps.getMessageId());
|
|
this.requestType = request.getClass();
|
|
|
|
this.messageHandler = new MessageHandler<Message>() {
|
|
@Override public void handleDelivery(Message message, TypeToken type) {
|
|
Metadata replyProps = Queue.METADATA.need();
|
|
if(requestId.equals(replyProps.getCorrelationId())) {
|
|
Transaction.this.replyQueue.unsubscribe(messageHandler);
|
|
|
|
if(replyProps.protocolVersion() != finalRequestProps.protocolVersion()) {
|
|
setException(new ApiException("Received a protocol " + replyProps.protocolVersion() +
|
|
" reply to a protocol " + finalRequestProps.protocolVersion() + " request",
|
|
callSite));
|
|
} else {
|
|
final Reply reply = message instanceof Reply ? (Reply) message : null;
|
|
if(reply != null && !reply.success()) {
|
|
setException(new ApiException(reply.error() != null ? reply.error()
|
|
: "Generic failure reply to request " + requestId,
|
|
callSite));
|
|
} else if(!replyType.isAssignableFrom(type)) {
|
|
setException(new ApiException("Wrong reply type to request " + requestId +
|
|
", expected a " + replyType +
|
|
", received a " + type,
|
|
callSite));
|
|
} else {
|
|
set((T) message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.replyQueue.subscribe(Message.class, messageHandler, null);
|
|
|
|
exchange.publishAsync(request, finalRequestProps, publish);
|
|
}
|
|
|
|
@Override
|
|
protected void interruptTask() {
|
|
replyQueue.unsubscribe(messageHandler);
|
|
}
|
|
|
|
@Override
|
|
protected String makeTimeoutMessage() {
|
|
return "Timed out waiting for reply to request " + requestId + " (" + requestType +
|
|
") created at " + ExceptionUtils.formatStackTrace(callSite);
|
|
}
|
|
}
|