All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rabbitmq.client.amqp.impl.AmqpPublisher Maven / Gradle / Ivy

Go to download

The RabbitMQ AMQP 1.0 Java client library defines an API to access RabbitMQ with the AMQP 1.0 protocol.

There is a newer version: 0.1.0
Show newest version
// 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.Resource.State.OPEN;

import com.rabbitmq.client.amqp.Message;
import com.rabbitmq.client.amqp.ObservationCollector;
import com.rabbitmq.client.amqp.Publisher;
import com.rabbitmq.client.amqp.metrics.MetricsCollector;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import org.apache.qpid.protonj2.client.*;
import org.apache.qpid.protonj2.client.exceptions.ClientException;
import org.apache.qpid.protonj2.client.exceptions.ClientIllegalStateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class AmqpPublisher extends ResourceBase implements Publisher {

  private static final AtomicLong ID_SEQUENCE = new AtomicLong(0);

  private static final Logger LOGGER = LoggerFactory.getLogger(AmqpPublisher.class);

  private final Long id;
  private volatile Sender sender;
  private final ExecutorService executorService;
  private final String address;
  private final AmqpConnection connection;
  private final AtomicBoolean closed = new AtomicBoolean(false);
  private final MetricsCollector metricsCollector;
  private final ObservationCollector observationCollector;
  private final Function publishCall;
  private final DefaultAddressBuilder.DestinationSpec destinationSpec;
  private final Duration publishTimeout;
  private final SessionHandler sessionHandler;
  private volatile ObservationCollector.ConnectionInfo connectionInfo;

  AmqpPublisher(AmqpPublisherBuilder builder) {
    super(builder.listeners());
    this.id = ID_SEQUENCE.getAndIncrement();
    this.executorService = builder.connection().environment().publisherExecutorService();
    this.address = builder.address();
    this.destinationSpec = builder.destination();
    this.connection = builder.connection();
    this.publishTimeout = builder.publishTimeout();
    this.sessionHandler = this.connection.createSessionHandler();
    this.sender = this.createSender(sessionHandler.session(), this.address, this.publishTimeout);
    this.metricsCollector = this.connection.metricsCollector();
    this.observationCollector = this.connection.observationCollector();
    this.state(OPEN);
    this.metricsCollector.openPublisher();
    this.publishCall =
        msg -> {
          try {
            org.apache.qpid.protonj2.client.Message nativeMessage =
                ((AmqpMessage) msg).nativeMessage();
            return this.sender.send(nativeMessage.durable(true));
          } catch (ClientIllegalStateException e) {
            // the link is closed
            LOGGER.debug("Error while publishing: '{}'. Closing publisher.", e.getMessage());
            this.close(ExceptionUtils.convert(e));
            throw ExceptionUtils.convert(e);
          } catch (ClientException e) {
            LOGGER.debug("Error while publishing: '{}'.", e.getMessage());
            throw ExceptionUtils.convert(e);
          }
        };
    this.connectionInfo = new Utils.ObservationConnectionInfo(this.connection.connectionAddress());
  }

  @Override
  public Message message() {
    return new AmqpMessage();
  }

  @Override
  public Message message(byte[] body) {
    return new AmqpMessage(body);
  }

  @Override
  public void publish(Message message, Callback callback) {
    checkOpen();
    Tracker tracker =
        this.observationCollector.publish(
            destinationSpec.exchange(),
            destinationSpec.routingKey(),
            message,
            this.connectionInfo,
            publishCall);
    tracker
        .settlementFuture()
        .handleAsync(
            (t, ex) -> {
              Status status;
              if (ex == null) {
                status = mapDeliveryState(t.remoteState());
              } else {
                status = Status.REJECTED;
              }
              DefaultContext defaultContext = new DefaultContext(message, status);
              this.metricsCollector.publishDisposition(mapToPublishDisposition(status));
              callback.handle(defaultContext);
              return null;
            },
            this.executorService);
    this.metricsCollector.publish();
  }

  private Status mapDeliveryState(DeliveryState in) {
    if (in.isAccepted()) {
      return Status.ACCEPTED;
    } else if (in.getType() == DeliveryState.Type.REJECTED) {
      return Status.REJECTED;
    } else if (in.getType() == DeliveryState.Type.RELEASED) {
      return Status.RELEASED;
    } else {
      LOGGER.warn("Delivery state not supported: " + in.getType());
      throw new IllegalStateException("This delivery state is not supported: " + in.getType());
    }
  }

  private static MetricsCollector.PublishDisposition mapToPublishDisposition(Status status) {
    if (status == Status.ACCEPTED) {
      return MetricsCollector.PublishDisposition.ACCEPTED;
    } else if (status == Status.REJECTED) {
      return MetricsCollector.PublishDisposition.REJECTED;
    } else if (status == Status.RELEASED) {
      return MetricsCollector.PublishDisposition.RELEASED;
    } else {
      return null;
    }
  }

  void recoverAfterConnectionFailure() {
    this.connectionInfo = new Utils.ObservationConnectionInfo(this.connection.connectionAddress());
    this.sender =
        this.createSender(this.sessionHandler.sessionNoCheck(), this.address, this.publishTimeout);
  }

  @Override
  public void close() {
    this.close(null);
  }

  // internal API

  private Sender createSender(Session session, String address, Duration publishTimeout) {
    SenderOptions senderOptions =
        new SenderOptions()
            .deliveryMode(DeliveryMode.AT_LEAST_ONCE)
            .sendTimeout(
                publishTimeout.isNegative()
                    ? ConnectionOptions.INFINITE
                    : publishTimeout.toMillis());
    try {
      Sender s =
          address == null
              ? session.openAnonymousSender()
              : session.openSender(address, senderOptions);
      return ExceptionUtils.wrapGet(s.openFuture());
    } catch (ClientException e) {
      throw ExceptionUtils.convert(e, "Error while creating publisher to %s", address);
    }
  }

  private void close(Throwable cause) {
    if (this.closed.compareAndSet(false, true)) {
      this.state(State.CLOSING, cause);
      this.connection.removePublisher(this);
      try {
        this.sender.close();
        this.sessionHandler.close();
      } catch (Exception e) {
        LOGGER.warn("Error while closing sender", e);
      }
      this.state(State.CLOSED, cause);
      this.metricsCollector.closePublisher();
    }
  }

  private static class DefaultContext implements Publisher.Context {

    private final Message message;
    private final Status status;

    private DefaultContext(Message message, Status status) {
      this.message = message;
      this.status = status;
    }

    @Override
    public Message message() {
      return this.message;
    }

    @Override
    public Status status() {
      return this.status;
    }
  }

  Long id() {
    return this.id;
  }

  String address() {
    return this.address;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy