com.rabbitmq.client.amqp.impl.AmqpManagement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of amqp-client Show documentation
Show all versions of amqp-client Show documentation
The RabbitMQ AMQP 1.0 Java client library defines an API to access RabbitMQ
with the AMQP 1.0 protocol.
// Copyright (c) 2024 Broadcom. All Rights Reserved.
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
//
// 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.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].
package com.rabbitmq.client.amqp.impl;
import static com.rabbitmq.client.amqp.impl.AmqpManagement.State.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import com.rabbitmq.client.amqp.AmqpException;
import com.rabbitmq.client.amqp.Management;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.qpid.protonj2.client.*;
import org.apache.qpid.protonj2.client.exceptions.ClientConnectionRemotelyClosedException;
import org.apache.qpid.protonj2.client.exceptions.ClientException;
import org.apache.qpid.protonj2.client.exceptions.ClientLinkRemotelyClosedException;
import org.apache.qpid.protonj2.client.exceptions.ClientSessionRemotelyClosedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class AmqpManagement implements Management {
private static final AtomicLong ID_SEQUENCE = new AtomicLong(0);
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpManagement.class);
private static final String MANAGEMENT_NODE_ADDRESS = "/management";
private static final String REPLY_TO = "$me";
private static final String GET = "GET";
private static final String POST = "POST";
private static final String PUT = "PUT";
private static final String DELETE = "DELETE";
private static final int CODE_200 = 200;
private static final int CODE_201 = 201;
private static final int CODE_204 = 204;
private static final int CODE_404 = 404;
private static final int CODE_409 = 409;
private final AmqpConnection connection;
private final Long id;
private volatile Session session;
private volatile Sender sender;
private volatile Receiver receiver;
private final AtomicBoolean closed = new AtomicBoolean(false);
private final Duration rpcTimeout = Duration.ofSeconds(10);
private final ConcurrentMap outstandingRequests =
new ConcurrentHashMap<>();
private volatile Future> receiveLoop;
private final TopologyListener topologyListener;
private final Supplier nameSupplier;
private final AtomicReference state = new AtomicReference<>(CREATED);
private final AtomicBoolean initializing = new AtomicBoolean(false);
private final Duration receiveLoopIdleTimeout;
private final Lock instanceLock = new ReentrantLock();
AmqpManagement(AmqpManagementParameters parameters) {
this.id = ID_SEQUENCE.getAndIncrement();
this.connection = parameters.connection();
this.topologyListener =
parameters.topologyListener() == null
? TopologyListener.NO_OP
: parameters.topologyListener();
this.nameSupplier = parameters.nameSupplier();
this.receiveLoopIdleTimeout =
parameters.receiveLoopIdleTimeout() == null
? Duration.ofSeconds(20)
: parameters.receiveLoopIdleTimeout();
}
@Override
public QueueSpecification queue() {
checkAvailable();
return new AmqpQueueSpecification(this);
}
@Override
public QueueSpecification queue(String name) {
checkAvailable();
return this.queue().name(name);
}
@Override
public QueueInfo queueInfo(String name) {
checkAvailable();
try {
Map queueInfo = get(queueLocation(name)).responseBodyAsMap();
return new DefaultQueueInfo(queueInfo);
} catch (ClientException e) {
throw new AmqpException("Error while fetching queue '%s' information", name);
}
}
@Override
public QueueDeletion queueDeletion() {
checkAvailable();
return name -> {
Map responseBody = delete(queueLocation(name), CODE_200);
this.topologyListener.queueDeleted(name);
if (!responseBody.containsKey("message_count")) {
throw new AmqpException("Response body should contain message_count");
}
};
}
@Override
public ExchangeSpecification exchange() {
checkAvailable();
return new AmqpExchangeSpecification(this);
}
@Override
public ExchangeSpecification exchange(String name) {
checkAvailable();
return this.exchange().name(name);
}
@Override
public ExchangeDeletion exchangeDeletion() {
checkAvailable();
return name -> {
this.delete(exchangeLocation(name), CODE_204);
this.topologyListener.exchangeDeleted(name);
};
}
@Override
public BindingSpecification binding() {
checkAvailable();
return new AmqpBindingManagement.AmqpBindingSpecification(this);
}
@Override
public UnbindSpecification unbind() {
checkAvailable();
return new AmqpBindingManagement.AmqpUnbindSpecification(this);
}
@Override
public void close() {
if (this.initializing.get()) {
throw new AmqpException.AmqpResourceInvalidStateException(
"Management is initializing, retry closing later.");
}
if (this.closed.compareAndSet(false, true)) {
this.state(CLOSED);
this.releaseResources();
if (this.receiver != null) {
try {
this.receiver.close();
} catch (Exception e) {
LOGGER.debug("Error while closing management receiver: {}", e.getMessage());
}
}
if (this.sender != null) {
try {
this.sender.close();
} catch (Exception e) {
LOGGER.debug("Error while closing management sender: {}", e.getMessage());
}
}
if (this.session != null) {
try {
this.session.close();
} catch (Exception e) {
LOGGER.debug("Error while closing management session: {}", e.getMessage());
}
}
}
}
void init() {
if (this.state() != OPEN) {
if (this.initializing.compareAndSet(false, true)) {
LOGGER.debug("Initializing management ({}).", this);
this.state(UNAVAILABLE);
try {
if (this.receiveLoop != null) {
this.receiveLoop.cancel(true);
this.receiveLoop = null;
}
LOGGER.debug("Creating management session ({}).", this);
this.session = this.connection.nativeConnection().openSession();
String linkPairName = "management-link-pair";
Map properties = Collections.singletonMap("paired", Boolean.TRUE);
LOGGER.debug("Creating management sender ({}).", this);
this.sender =
session.openSender(
MANAGEMENT_NODE_ADDRESS,
new SenderOptions()
.deliveryMode(DeliveryMode.AT_MOST_ONCE)
.linkName(linkPairName)
.properties(properties));
LOGGER.debug("Creating management receiver ({}).", this);
this.receiver =
session.openReceiver(
MANAGEMENT_NODE_ADDRESS,
new ReceiverOptions()
.deliveryMode(DeliveryMode.AT_MOST_ONCE)
.linkName(linkPairName)
.properties(properties)
.creditWindow(100));
this.sender.openFuture().get(this.rpcTimeout.toMillis(), MILLISECONDS);
LOGGER.debug("Management sender created ({}).", this);
this.receiver.openFuture().get(this.rpcTimeout.toMillis(), MILLISECONDS);
LOGGER.debug("Management receiver created ({}).", this);
this.state(OPEN);
this.initializing.set(false);
} catch (Exception e) {
throw new AmqpException(e);
}
}
}
}
private Runnable receiveTask() {
return () -> {
try {
Duration waitDuration = Duration.ofMillis(100);
long idleTime = 0;
while (!Thread.currentThread().isInterrupted()) {
Delivery delivery = receiver.receive(waitDuration.toMillis(), MILLISECONDS);
if (delivery != null) {
idleTime = 0;
Object correlationId = delivery.message().correlationId();
if (correlationId instanceof UUID) {
OutstandingRequest request = outstandingRequests.remove(correlationId);
if (request != null) {
request.complete(delivery.message());
} else {
LOGGER.info("Could not find outstanding request {}", correlationId);
}
} else {
LOGGER.info("Could not correlate inbound message with management request");
}
} else {
idleTime += waitDuration.toMillis();
if (idleTime > receiveLoopIdleTimeout.toMillis()) {
LOGGER.debug(
"Management receive loop has been idle for more than {}, finishing it.",
this.receiveLoopIdleTimeout);
this.receiveLoop = null;
return;
}
}
}
} catch (ClientConnectionRemotelyClosedException | ClientLinkRemotelyClosedException e) {
// receiver is closed
} catch (ClientSessionRemotelyClosedException e) {
this.state(UNAVAILABLE);
LOGGER.info("Management session closed in receive loop: {} ({})", e.getMessage(), this);
AmqpException exception = ExceptionUtils.convert(e);
this.failRequests(exception);
if (exception instanceof AmqpException.AmqpSecurityException) {
LOGGER.debug(
"Recovering AMQP management because the failure was a security exception ({}).",
this);
this.init();
}
} catch (ClientException e) {
java.util.function.Consumer log =
this.closed.get() ? m -> LOGGER.debug(m, e) : m -> LOGGER.info(m, e);
log.accept("Error while polling AMQP receiver");
}
};
}
private void failRequests(AmqpException exception) {
Iterator> iterator =
this.outstandingRequests.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry request = iterator.next();
LOGGER.info("Failing management request {}", request.getKey());
request.getValue().fail(exception);
iterator.remove();
}
}
void releaseResources() {
this.markUnavailable();
if (this.receiveLoop != null) {
this.receiveLoop.cancel(true);
this.receiveLoop = null;
}
}
QueueInfo declareQueue(String name, Map body) {
if (name == null || name.isBlank()) {
QueueInfo info = null;
while (info == null) {
name = this.nameSupplier.get();
Response