io.smallrye.reactive.messaging.providers.extension.ChannelProducer Maven / Gradle / Ivy
package io.smallrye.reactive.messaging.providers.extension;
import static io.smallrye.reactive.messaging.providers.helpers.ConverterUtils.convert;
import static io.smallrye.reactive.messaging.providers.i18n.ProviderExceptions.ex;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.Flow;
import java.util.stream.Collectors;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.Typed;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.inject.Inject;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder;
import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams;
import org.reactivestreams.Publisher;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.reactive.messaging.ChannelRegistry;
import io.smallrye.reactive.messaging.MessageConverter;
import io.smallrye.reactive.messaging.MutinyEmitter;
import io.smallrye.reactive.messaging.providers.helpers.MultiUtils;
import io.smallrye.reactive.messaging.providers.helpers.TypeUtils;
import io.smallrye.reactive.messaging.providers.i18n.ProviderExceptions;
import mutiny.zero.flow.adapters.AdaptersToReactiveStreams;
/**
* This component computes the right object to be injected into injection point using {@link Channel} and the
* deprecated {@link io.smallrye.reactive.messaging.annotations.Channel}. This includes stream and emitter injections.
*/
@ApplicationScoped
public class ChannelProducer {
@Inject
ChannelRegistry channelRegistry;
@Inject
// @Any would only be needed if we wanted to allow implementations with qualifiers
Instance converters;
/**
* Injects {@code Multi>} and {@code Multi}. It also matches the injection of
* {@code Publisher>} and {@code Publisher}.
*
* @param injectionPoint the injection point
* @param the first generic parameter (either Message or X)
* @return the Multi to be injected
*/
@Produces
@Typed({ Flow.Publisher.class, Multi.class })
@Channel("") // Stream name is ignored during type-safe resolution
Multi produceMulti(InjectionPoint injectionPoint) {
Type first = getFirstParameter(injectionPoint.getType());
if (TypeUtils.isAssignable(first, Message.class)) {
Type payloadType = getPayloadParameterFromMessageType(first);
if (payloadType == null) {
return cast(getPublisher(injectionPoint));
} else {
return cast(convert(getPublisher(injectionPoint), converters, getRawTypeIfParameterized(payloadType)));
}
} else {
return cast(convert(getPublisher(injectionPoint), converters, getRawTypeIfParameterized(first))
.onItem().call(m -> Uni.createFrom().completionStage(m.ack()))
.onItem().transform(Message::getPayload)
.broadcast().toAllSubscribers());
}
}
/**
* Injects {@code Multi>} and {@code Multi}. It also matches the injection of
* {@code Publisher>} and {@code Publisher}.
*
* @param injectionPoint the injection point
* @param the first generic parameter (either Message or X)
* @return the Multi to be injected
*/
@Produces
@Typed({ Publisher.class })
@Channel("") // Stream name is ignored during type-safe resolution
Publisher producePublisher(InjectionPoint injectionPoint) {
return AdaptersToReactiveStreams.publisher(produceMulti(injectionPoint));
}
/**
* Injects {@code Multi>} and {@code Multi}. It also matches the injection of
* {@code Publisher>} and {@code Publisher}.
*
* NOTE: this injection point is about the deprecated {@link io.smallrye.reactive.messaging.annotations.Channel} annotation.
*
* @param injectionPoint the injection point
* @param the first generic parameter (either Message or X)
* @return the stream to be injected
* @deprecated Use {@link Channel} instead.
*/
@Produces
@Deprecated
@Typed({ Flow.Publisher.class, Multi.class })
@io.smallrye.reactive.messaging.annotations.Channel("")
Multi produceMultiWithLegacyChannelAnnotation(InjectionPoint injectionPoint) {
return produceMulti(injectionPoint);
}
/**
* Injects {@code Multi>} and {@code Multi}. It also matches the injection of
* {@code Publisher>} and {@code Publisher}.
*
* NOTE: this injection point is about the deprecated {@link io.smallrye.reactive.messaging.annotations.Channel} annotation.
*
* @param injectionPoint the injection point
* @param the first generic parameter (either Message or X)
* @return the stream to be injected
* @deprecated Use {@link Channel} instead.
*/
@Produces
@Deprecated
@Typed({ Publisher.class })
@io.smallrye.reactive.messaging.annotations.Channel("")
Publisher producePublisherWithLegacyChannelAnnotation(InjectionPoint injectionPoint) {
return producePublisher(injectionPoint);
}
/**
* Injects {@code PublisherBuilder>} and {@code PublisherBuilder}
*
* @param injectionPoint the injection point
* @param the first generic parameter (either Message or X)
* @return the PublisherBuilder to be injected
*/
@Produces
@Channel("") // Stream name is ignored during type-safe resolution
PublisherBuilder producePublisherBuilder(InjectionPoint injectionPoint) {
return ReactiveStreams.fromPublisher(producePublisher(injectionPoint));
}
/**
* Injects {@code PublisherBuilder>} and {@code PublisherBuilder}
*
* NOTE: this injection point is about the deprecated {@link io.smallrye.reactive.messaging.annotations.Channel} annotation.
*
* @param injectionPoint the injection point
* @param the first generic parameter (either Message or X)
* @return the PublisherBuilder to be injected
* @deprecated Use {@link Channel} instead.
*/
@Produces
@io.smallrye.reactive.messaging.annotations.Channel("") // Stream name is ignored during type-safe resolution
PublisherBuilder producePublisherBuilderWithLegacyChannelAnnotation(InjectionPoint injectionPoint) {
return producePublisherBuilder(injectionPoint);
}
/**
* Injects an {@link Emitter} matching the channel name.
*
* @param injectionPoint the injection point
* @param the type of the emitter
* @return the emitter
*/
@Produces
@Channel("") // Stream name is ignored during type-safe resolution
Emitter produceEmitter(InjectionPoint injectionPoint) {
verify(injectionPoint);
return getEmitter(injectionPoint);
}
/**
* Injects an {@link MutinyEmitter} matching the channel name.
*
* @param injectionPoint the injection point
* @param the type of the emitter
* @return the emitter
*/
@Produces
@Channel("") // Stream name is ignored during type-safe resolution
MutinyEmitter produceMutinyEmitter(InjectionPoint injectionPoint) {
verify(injectionPoint);
return getEmitter(injectionPoint);
}
/**
* Injects an {@link io.smallrye.reactive.messaging.annotations.Emitter} (deprecated) matching the channel name.
*
* @param injectionPoint the injection point
* @param the type
* @return the legacy emitter
* @deprecated Use the new {@link Emitter} and {@link Channel} instead
*/
@Produces
@io.smallrye.reactive.messaging.annotations.Channel("") // Stream name is ignored during type-safe resolution
io.smallrye.reactive.messaging.annotations.Emitter produceEmitterLegacy(InjectionPoint injectionPoint) {
return getEmitter(injectionPoint);
}
private Multi> getPublisher(InjectionPoint injectionPoint) {
String name = getChannelName(injectionPoint);
return Multi.createFrom().deferred(() -> {
List>> list = channelRegistry.getPublishers(name);
if (list.isEmpty()) {
throw ex.illegalStateForStream(name, channelRegistry.getIncomingNames());
} else if (list.size() == 1) {
return MultiUtils.publisher(list.get(0));
} else {
return Multi.createBy().merging()
.streams(list.stream().map(p -> p).collect(Collectors.toList()));
}
});
}
private T getEmitter(InjectionPoint injectionPoint) {
String name = getChannelName(injectionPoint);
T emitter = channelRegistry.getEmitter(name, (Class) getRawTypeIfParameterized(injectionPoint.getType()));
if (emitter == null) {
throw ex.incomingNotFoundForEmitter(name);
}
return emitter;
}
private void verify(InjectionPoint injectionPoint) {
Type type = injectionPoint.getType();
if (type instanceof ParameterizedType && ((ParameterizedType) type).getActualTypeArguments().length > 0) {
Type[] arguments = ((ParameterizedType) type).getActualTypeArguments();
if ((arguments[0] instanceof Class && arguments[0].equals(Message.class)) ||
(arguments[0] instanceof ParameterizedType
&& ((ParameterizedType) arguments[0]).getRawType().equals(Message.class))) {
throw ProviderExceptions.ex.invalidEmitterOfMessage(injectionPoint);
}
} else {
throw ProviderExceptions.ex.invalidRawEmitter(injectionPoint);
}
}
private Type getFirstParameter(Type type) {
if (type instanceof ParameterizedType) {
return ((ParameterizedType) type).getActualTypeArguments()[0];
}
return null;
}
private Type getPayloadParameterFromMessageType(Type type) {
if (type instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
if (actualTypeArguments.length == 1) {
return actualTypeArguments[0];
}
}
return null;
}
private Type getRawTypeIfParameterized(Type type) {
if (type instanceof ParameterizedType) {
return ((ParameterizedType) type).getRawType();
}
return type;
}
@SuppressWarnings("deprecation")
public static String getChannelName(InjectionPoint injectionPoint) {
for (Annotation qualifier : injectionPoint.getQualifiers()) {
if (qualifier.annotationType().equals(Channel.class)) {
return ((Channel) qualifier).value();
}
if (qualifier.annotationType().equals(io.smallrye.reactive.messaging.annotations.Channel.class)) {
return ((io.smallrye.reactive.messaging.annotations.Channel) qualifier).value();
}
}
throw ex.emitterWithoutChannelAnnotation(injectionPoint);
}
@SuppressWarnings("deprecation")
static Channel getChannelQualifier(InjectionPoint injectionPoint) {
for (Annotation qualifier : injectionPoint.getQualifiers()) {
if (qualifier.annotationType().equals(Channel.class)) {
return (Channel) qualifier;
}
if (qualifier.annotationType().equals(io.smallrye.reactive.messaging.annotations.Channel.class)) {
return new Channel() {
@Override
public Class annotationType() {
return Channel.class;
}
@Override
public String value() {
return ((io.smallrye.reactive.messaging.annotations.Channel) qualifier).value();
}
};
}
}
return null;
}
@SuppressWarnings("unchecked")
private static T cast(Object obj) {
return (T) obj;
}
}