io.micronaut.configuration.rabbitmq.reactive.RxJavaReactivePublisher Maven / Gradle / Ivy
/*
* Copyright 2017-2018 original authors
*
* 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.
*/
package io.micronaut.configuration.rabbitmq.reactive;
import com.rabbitmq.client.*;
import io.micronaut.configuration.rabbitmq.bind.RabbitConsumerState;
import io.micronaut.configuration.rabbitmq.connect.ChannelPool;
import io.micronaut.configuration.rabbitmq.connect.RabbitConnectionFactoryConfig;
import io.micronaut.configuration.rabbitmq.exception.RabbitClientException;
import io.micronaut.configuration.rabbitmq.intercept.DefaultConsumer;
import io.micronaut.core.annotation.Internal;
import io.micronaut.messaging.exceptions.MessagingClientException;
import io.reactivex.*;
import io.reactivex.disposables.Disposable;
import javax.inject.Singleton;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* A reactive publisher implementation that uses a single channel per publish
* operation and returns an RxJava2 {@link Completable}.
*
* This is an internal API and may change at any time
*
* @author James Kleeh
* @since 1.1.0
*/
@Singleton
@Internal
public class RxJavaReactivePublisher implements ReactivePublisher {
private final ChannelPool channelPool;
private final RabbitConnectionFactoryConfig config;
/**
* Default constructor.
*
* @param channelPool The channel pool to retrieve channels
* @param config Any configuration used in building the publishers
*/
public RxJavaReactivePublisher(ChannelPool channelPool,
RabbitConnectionFactoryConfig config) {
this.channelPool = channelPool;
this.config = config;
}
@Override
public Flowable publishAndConfirm(RabbitPublishState publishState) {
return getChannel()
.flatMap(this::initializePublish)
.flatMapCompletable(channel -> publishInternal(channel, publishState))
.toFlowable();
}
@Override
public Flowable publish(RabbitPublishState publishState) {
return getChannel()
.flatMapCompletable(channel -> publishInternalNoConfirm(channel, publishState))
.toFlowable();
}
@Override
public Flowable publishAndReply(RabbitPublishState publishState) {
Flowable flowable = getChannel()
.flatMap(channel -> publishRpcInternal(channel, publishState))
.toFlowable();
Optional optionalDuration = config.getRpc().getTimeout();
if (optionalDuration.isPresent()) {
long nanos = optionalDuration.get().toNanos();
flowable = flowable.timeout(nanos, TimeUnit.NANOSECONDS);
}
return flowable;
}
/**
* Creates a {@link Single} from a channel, emitting an error
* if the channel could not be retrieved.
*
* @return A {@link Single} that emits the channel on success
*/
protected Single getChannel() {
return Single.create(emitter -> {
try {
Channel channel = channelPool.getChannel();
emitter.onSuccess(channel);
} catch (IOException e) {
emitter.onError(new RabbitClientException("Failed to retrieve a channel from the pool", e));
}
});
}
/**
* Publishes the message to the channel. The {@link Completable} returned from this
* method should ensure the {@link ConfirmListener} is added to the channel prior
* to publishing and the {@link ConfirmListener} is removed from the channel after
* the publish has been acknowledged.
*
* @see Channel#basicPublish(String, String, AMQP.BasicProperties, byte[])
*
* @param channel The channel to publish the message to
* @param publishState The publishing state
*
* @return A completable that terminates when the publish has been acknowledged
*/
protected Completable publishInternal(Channel channel, RabbitPublishState publishState) {
return Completable.create(subscriber -> {
AtomicReference acknowledgement = new AtomicReference<>(BrokerResponse.NONE);
Disposable listener = createListener(channel, acknowledgement);
try {
channel.basicPublish(
publishState.getExchange(),
publishState.getRoutingKey(),
publishState.getProperties(),
publishState.getBody()
);
synchronized (acknowledgement) {
if (subscriber.isDisposed()) {
listener.dispose();
} else {
while (acknowledgement.get() == BrokerResponse.NONE) {
acknowledgement.wait();
}
if (acknowledgement.get() == BrokerResponse.ACK) {
subscriber.onComplete();
} else {
subscriber.onError(new RabbitClientException("Message could not be delivered to the broker", Collections.singletonList(publishState)));
}
}
}
} catch (IOException | InterruptedException e) {
listener.dispose();
subscriber.onError(e);
}
}).doFinally(() -> returnChannel(channel));
}
/**
* Publishes the message to the channel. The {@link Completable} returned from this
* method should ensure the {@link ConfirmListener} is added to the channel prior
* to publishing and the {@link ConfirmListener} is removed from the channel after
* the publish has been acknowledged.
*
* @see Channel#basicPublish(String, String, AMQP.BasicProperties, byte[])
*
* @param channel The channel to publish the message to
* @param publishState The publishing state
*
* @return A completable that terminates when the publish has been acknowledged
*/
protected Single publishRpcInternal(Channel channel, RabbitPublishState publishState) {
return Single.create(subscriber -> {
Object noResponse = new Object();
AtomicReference © 2015 - 2025 Weber Informatics LLC | Privacy Policy