
io.quarkus.reactivemessaging.http.deployment.ReactiveHttpProcessor Maven / Gradle / Ivy
package io.quarkus.reactivemessaging.http.deployment;
import static io.quarkus.arc.processor.DotNames.OBJECT;
import static io.quarkus.arc.processor.DotNames.STRING;
import static java.util.Arrays.asList;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.reactive.messaging.Incoming;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder;
import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder;
import org.eclipse.microprofile.reactive.streams.operators.SubscriberBuilder;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.ParameterizedType;
import org.jboss.jandex.Type;
import org.reactivestreams.Processor;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.reactivemessaging.http.runtime.QuarkusHttpConnector;
import io.quarkus.reactivemessaging.http.runtime.QuarkusWebSocketConnector;
import io.quarkus.reactivemessaging.http.runtime.ReactiveHttpHandlerBean;
import io.quarkus.reactivemessaging.http.runtime.ReactiveHttpRecorder;
import io.quarkus.reactivemessaging.http.runtime.ReactiveWebSocketHandlerBean;
import io.quarkus.reactivemessaging.http.runtime.config.HttpStreamConfig;
import io.quarkus.reactivemessaging.http.runtime.config.ReactiveHttpConfig;
import io.quarkus.reactivemessaging.http.runtime.config.WebSocketStreamConfig;
import io.quarkus.reactivemessaging.http.runtime.converters.JsonArrayConverter;
import io.quarkus.reactivemessaging.http.runtime.converters.JsonObjectConverter;
import io.quarkus.reactivemessaging.http.runtime.converters.ObjectConverter;
import io.quarkus.reactivemessaging.http.runtime.converters.StringConverter;
import io.quarkus.reactivemessaging.http.runtime.serializers.Serializer;
import io.quarkus.reactivemessaging.http.runtime.serializers.SerializerFactoryBase;
import io.quarkus.vertx.http.deployment.BodyHandlerBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.smallrye.mutiny.Multi;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
public class ReactiveHttpProcessor {
private static final String FEATURE = "smallrye-reactive-messaging-http";
private static final DotName JSON_ARRAY = DotName.createSimple(JsonArray.class.getName());
private static final DotName JSON_OBJECT = DotName.createSimple(JsonObject.class.getName());
private static final DotName MESSAGE = DotName.createSimple(Message.class.getName());
private static final DotName MULTI = DotName.createSimple(Multi.class.getName());
private static final DotName PROCESSOR = DotName.createSimple(Processor.class.getName());
private static final DotName PROCESSOR_BUILDER = DotName.createSimple(ProcessorBuilder.class.getName());
private static final DotName PUBLISHER = DotName.createSimple(Publisher.class.getName());
private static final DotName PUBLISHER_BUILDER = DotName.createSimple(PublisherBuilder.class.getName());
private static final DotName SUBSCRIBER = DotName.createSimple(Subscriber.class.getName());
private static final DotName SUBSCRIBER_BUILDER = DotName.createSimple(SubscriberBuilder.class.getName());
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void registerHttpConnector(BuildProducer beanProducer,
BuildProducer generatedBeanProducer,
BuildProducer routeProducer,
BodyHandlerBuildItem bodyHandler,
ReactiveHttpRecorder recorder,
// keep the indexBuildItem to control the order of build steps, any other build step that contributes to
// CombinedIndex will be invoked before this build step
CombinedIndexBuildItem indexBuildItem) {
beanProducer.produce(new AdditionalBeanBuildItem(QuarkusHttpConnector.class));
beanProducer.produce(new AdditionalBeanBuildItem(QuarkusWebSocketConnector.class));
beanProducer.produce(new AdditionalBeanBuildItem(ReactiveHttpConfig.class));
beanProducer.produce(new AdditionalBeanBuildItem(ReactiveHttpHandlerBean.class));
beanProducer.produce(new AdditionalBeanBuildItem(ReactiveWebSocketHandlerBean.class));
beanProducer.produce(new AdditionalBeanBuildItem(JsonArrayConverter.class));
beanProducer.produce(new AdditionalBeanBuildItem(JsonObjectConverter.class));
beanProducer.produce(new AdditionalBeanBuildItem(ObjectConverter.class));
beanProducer.produce(new AdditionalBeanBuildItem(StringConverter.class));
List httpConfigs = ReactiveHttpConfig.readIncomingHttpConfigs();
List wsConfigs = ReactiveHttpConfig.readIncomingWebSocketConfigs();
if (!httpConfigs.isEmpty()) {
Handler handler = recorder.createHttpHandler();
httpConfigs.stream()
.map(HttpStreamConfig::path)
.distinct()
.forEach(path -> {
routeProducer.produce(RouteBuildItem.builder().route(path).handler(bodyHandler.getHandler()).build());
routeProducer.produce(RouteBuildItem.builder().route(path).handler(handler).build());
});
}
if (!wsConfigs.isEmpty()) {
Handler handler = recorder.createWebSocketHandler();
wsConfigs.stream()
.map(WebSocketStreamConfig::path)
.distinct()
.forEach(path -> routeProducer.produce(RouteBuildItem.builder().route(path).handler(handler).build()));
}
initSerializers(ReactiveHttpConfig.readSerializers(), generatedBeanProducer);
}
@BuildStep
void registerMessagePayloadClassesForReflection(BeanArchiveIndexBuildItem index,
BuildProducer reflectiveClasses) {
Set payloadClasses = new HashSet<>();
for (AnnotationInstance incoming : index.getIndex().getAnnotations(DotName.createSimple(Incoming.class.getName()))) {
MethodInfo methodInfo = incoming.target().asMethod();
List parameters = methodInfo.parameters();
if (parameters.size() == 1) {
Type type = parameters.get(0);
// payload can be consumed as Publisher[Builder] or Publisher[Builder]>
// or Multi, Multi>
DotName typeName = type.name();
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE
&& (typeName.equals(PUBLISHER_BUILDER) || typeName.equals(PUBLISHER) || typeName.equals(MULTI))) {
List arguments = type.asParameterizedType().arguments();
if (arguments.size() > 0) {
collectPayloadType(payloadClasses, arguments.get(0));
}
} else {
collectPayloadType(payloadClasses, type);
}
} else if (parameters.size() == 0) {
// @Incoming method can also return a Subscriber[Builder] or Processor[Builder] for message payloads:
Type returnType = methodInfo.returnType();
if ((returnType.name().equals(SUBSCRIBER_BUILDER)
|| returnType.name().equals(PROCESSOR_BUILDER)
|| returnType.name().equals(SUBSCRIBER)
|| returnType.name().equals(PROCESSOR))
&& returnType.kind() == Type.Kind.PARAMETERIZED_TYPE) {
ParameterizedType parameterizedType = returnType.asParameterizedType();
List arguments = parameterizedType.arguments();
if (arguments.size() > 0) {
collectPayloadType(payloadClasses, arguments.get(0));
}
}
}
}
payloadClasses.removeAll(asList(JSON_OBJECT.toString(), OBJECT.toString(), JSON_ARRAY.toString(), STRING.toString()));
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, payloadClasses.toArray(new String[] {})));
}
private void collectPayloadType(Set payloadClasses, Type type) {
if (type.kind() != Type.Kind.CLASS && type.kind() != Type.Kind.PARAMETERIZED_TYPE) {
return;
}
if (type.name().equals(MESSAGE)) {
// wrapped in a message
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) {
Type payloadType = type.asParameterizedType().arguments().get(0);
if (payloadType.kind() == Type.Kind.CLASS) {
payloadClasses.add(payloadType.name().toString());
}
}
} else {
// or used directly
payloadClasses.add(type.name().toString());
}
}
private void initSerializers(List serializers, BuildProducer generatedBeans) {
ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(generatedBeans);
try (ClassCreator factory = ClassCreator.builder().classOutput(classOutput)
.className("io.quarkus.reactivemessaging.http.runtime.serializers.SerializerFactory")
.superClass(SerializerFactoryBase.class)
.build()) {
factory.addAnnotation(ApplicationScoped.class);
try (MethodCreator init = factory.getMethodCreator("initAdditionalSerializers", void.class)) {
init.setModifiers(Modifier.PROTECTED);
MethodDescriptor addSerializer = MethodDescriptor.ofMethod(SerializerFactoryBase.class, "addSerializer",
void.class, String.class, Serializer.class);
for (String serializerName : serializers) {
ResultHandle serializer = init.newInstance(MethodDescriptor.ofConstructor(serializerName));
init.invokeVirtualMethod(addSerializer, init.getThis(), init.load(serializerName), serializer);
}
init.returnValue(null);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy