diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 01eeb74a9c98b..aab5bfae1850a 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -3,8 +3,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -15,28 +13,24 @@ import java.util.stream.Collectors; import javax.ws.rs.Priorities; -import javax.ws.rs.RuntimeType; import javax.ws.rs.ext.RuntimeDelegate; -import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.jandex.Indexer; -import org.jboss.jandex.Type; import org.jboss.resteasy.reactive.common.jaxrs.RuntimeDelegateImpl; import org.jboss.resteasy.reactive.common.model.InterceptorContainer; import org.jboss.resteasy.reactive.common.model.PreMatchInterceptorContainer; import org.jboss.resteasy.reactive.common.model.ResourceInterceptor; import org.jboss.resteasy.reactive.common.model.ResourceInterceptors; -import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; -import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult.KeepProviderResult; import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult; import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveInterceptorScanner; import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner; +import org.jboss.resteasy.reactive.common.processor.scanning.SerializerScanningResult; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; @@ -49,7 +43,6 @@ import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; -import io.quarkus.deployment.util.JandexUtil; import io.quarkus.resteasy.reactive.common.runtime.JaxRsSecurityConfig; import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig; import io.quarkus.resteasy.reactive.spi.AbstractInterceptorBuildItem; @@ -276,70 +269,16 @@ public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, } IndexView index = beanArchiveIndexBuildItem.getIndex(); - Collection writers = index - .getAllKnownImplementors(ResteasyReactiveDotNames.MESSAGE_BODY_WRITER); - Collection readers = index - .getAllKnownImplementors(ResteasyReactiveDotNames.MESSAGE_BODY_READER); - - for (ClassInfo writerClass : writers) { - KeepProviderResult keepProviderResult = applicationResultBuildItem.getResult().keepProvider(writerClass); - if (keepProviderResult != KeepProviderResult.DISCARD) { - RuntimeType runtimeType = null; - if (keepProviderResult == KeepProviderResult.SERVER_ONLY) { - runtimeType = RuntimeType.SERVER; - } - List mediaTypeStrings = Collections.emptyList(); - AnnotationInstance producesAnnotation = writerClass.classAnnotation(ResteasyReactiveDotNames.PRODUCES); - if (producesAnnotation != null) { - mediaTypeStrings = Arrays.asList(producesAnnotation.value().asStringArray()); - } - List typeParameters = JandexUtil.resolveTypeParameters(writerClass.name(), - ResteasyReactiveDotNames.MESSAGE_BODY_WRITER, - index); - String writerClassName = writerClass.name().toString(); - AnnotationInstance constrainedToInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); - if (constrainedToInstance != null) { - runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); - } - int priority = Priorities.USER; - AnnotationInstance priorityInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); - if (priorityInstance != null) { - priority = priorityInstance.value().asInt(); - } - messageBodyWriterBuildItemBuildProducer.produce(new MessageBodyWriterBuildItem(writerClassName, - typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority)); - } + SerializerScanningResult serializers = ResteasyReactiveScanner.scanForSerializers(index, + applicationResultBuildItem.getResult()); + for (var i : serializers.getReaders()) { + messageBodyReaderBuildItemBuildProducer.produce(new MessageBodyReaderBuildItem(i.getClassName(), + i.getHandledClassName(), i.getMediaTypeStrings(), i.getRuntimeType(), i.isBuiltin(), i.getPriority())); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, i.getClassName())); } - - for (ClassInfo readerClass : readers) { - KeepProviderResult keepProviderResult = applicationResultBuildItem.getResult().keepProvider(readerClass); - if (keepProviderResult != KeepProviderResult.DISCARD) { - List typeParameters = JandexUtil.resolveTypeParameters(readerClass.name(), - ResteasyReactiveDotNames.MESSAGE_BODY_READER, - index); - RuntimeType runtimeType = null; - if (keepProviderResult == KeepProviderResult.SERVER_ONLY) { - runtimeType = RuntimeType.SERVER; - } - List mediaTypeStrings = Collections.emptyList(); - String readerClassName = readerClass.name().toString(); - AnnotationInstance consumesAnnotation = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSUMES); - if (consumesAnnotation != null) { - mediaTypeStrings = Arrays.asList(consumesAnnotation.value().asStringArray()); - } - AnnotationInstance constrainedToInstance = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); - if (constrainedToInstance != null) { - runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); - } - int priority = Priorities.USER; - AnnotationInstance priorityInstance = readerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); - if (priorityInstance != null) { - priority = priorityInstance.value().asInt(); - } - messageBodyReaderBuildItemBuildProducer.produce(new MessageBodyReaderBuildItem(readerClassName, - typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority)); - reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, readerClassName)); - } + for (var i : serializers.getWriters()) { + messageBodyWriterBuildItemBuildProducer.produce(new MessageBodyWriterBuildItem(i.getClassName(), + i.getHandledClassName(), i.getMediaTypeStrings(), i.getRuntimeType(), i.isBuiltin(), i.getPriority())); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java index 43b6427f271f6..97e3010459765 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java @@ -6,15 +6,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Predicate; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.PathSegment; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; @@ -23,11 +20,9 @@ import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig; import org.jboss.resteasy.reactive.common.processor.DefaultProducesHandler; import org.jboss.resteasy.reactive.server.core.Deployment; -import org.jboss.resteasy.reactive.server.core.parameters.converters.GeneratedParameterConverter; -import org.jboss.resteasy.reactive.server.core.parameters.converters.NoopParameterConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.LoadedParameterConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverterSupplier; -import org.jboss.resteasy.reactive.server.core.parameters.converters.PathSegmentParamConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.RuntimeResolvedConverter; import org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer; import org.jboss.resteasy.reactive.server.processor.ServerIndexedParameter; @@ -43,9 +38,6 @@ import io.quarkus.gizmo.ResultHandle; import io.quarkus.resteasy.reactive.server.common.runtime.EndpointInvokerFactory; import io.quarkus.resteasy.reactive.server.runtime.ResteasyReactiveRecorder; -import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.HttpServerResponse; -import io.vertx.ext.web.RoutingContext; public class QuarkusServerEndpointIndexer extends ServerEndpointIndexer { @@ -59,11 +51,6 @@ public class QuarkusServerEndpointIndexer private final Map multipartGeneratedPopulators = new HashMap<>(); private final Predicate applicationClassPredicate; - private static final Set CONTEXT_TYPES = Set.of( - DotName.createSimple(HttpServerRequest.class.getName()), - DotName.createSimple(HttpServerResponse.class.getName()), - DotName.createSimple(RoutingContext.class.getName())); - QuarkusServerEndpointIndexer(Builder builder) { super(builder); this.initConverters = builder.initConverters; @@ -75,10 +62,6 @@ public class QuarkusServerEndpointIndexer this.resteasyReactiveRecorder = builder.resteasyReactiveRecorder; } - protected boolean isContextType(ClassType klass) { - return super.isContextType(klass) || CONTEXT_TYPES.contains(klass.name()); - } - @Override protected String[] applyAdditionalDefaults(Type nonAsyncReturnType) { List defaultMediaTypes = defaultProducesHandler.handle(new DefaultProducesHandler.Context() { @@ -116,28 +99,8 @@ protected boolean handleCustomParameter(Map anns, S } @Override - protected ParameterConverterSupplier extractConverter(String elementType, IndexView indexView, + protected ParameterConverterSupplier extractConverterImpl(String elementType, IndexView indexView, Map existingConverters, String errorLocation, boolean hasRuntimeConverters) { - if (elementType.equals(String.class.getName())) { - if (hasRuntimeConverters) - return new RuntimeResolvedConverter.Supplier().setDelegate(new NoopParameterConverter.Supplier()); - // String needs no conversion - return null; - } else if (existingConverters.containsKey(elementType)) { - String className = existingConverters.get(elementType); - ParameterConverterSupplier delegate; - if (className == null) - delegate = null; - else - delegate = new GeneratedParameterConverter().setClassName(className); - if (hasRuntimeConverters) - return new RuntimeResolvedConverter.Supplier().setDelegate(delegate); - if (delegate == null) - throw new RuntimeException("Failed to find converter for " + elementType); - return delegate; - } else if (elementType.equals(PathSegment.class.getName())) { - return new PathSegmentParamConverter.Supplier(); - } MethodDescriptor fromString = null; MethodDescriptor valueOf = null; @@ -199,7 +162,7 @@ protected ParameterConverterSupplier extractConverter(String elementType, IndexV mc.returnValue(ret); } } - delegate = new GeneratedParameterConverter().setClassName(baseName); + delegate = new LoadedParameterConverter().setClassName(baseName); } else { // let's not try this again baseName = null; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index e67bc02816495..4dfd25e2f8166 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -2,9 +2,6 @@ import static java.util.stream.Collectors.toList; -import java.io.File; -import java.io.InputStream; -import java.io.Reader; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -25,7 +22,6 @@ import javax.ws.rs.RuntimeType; import javax.ws.rs.core.Application; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; @@ -39,8 +35,6 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import org.jboss.resteasy.reactive.FilePart; -import org.jboss.resteasy.reactive.PathPart; import org.jboss.resteasy.reactive.ResponseHeader; import org.jboss.resteasy.reactive.ResponseStatus; import org.jboss.resteasy.reactive.common.core.Serialisers; @@ -78,20 +72,6 @@ import org.jboss.resteasy.reactive.server.model.ServerMethodParameter; import org.jboss.resteasy.reactive.server.model.ServerResourceMethod; import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerBooleanMessageBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerByteArrayMessageBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerCharArrayMessageBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerCharacterMessageBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerDefaultTextPlainBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFileBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFilePartBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFormUrlEncodedProvider; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerInputStreamMessageBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerNumberMessageBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerPathBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerPathPartBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerReaderBodyHandler; -import org.jboss.resteasy.reactive.server.providers.serialisers.ServerStringMessageBodyHandler; import org.jboss.resteasy.reactive.spi.BeanFactory; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -168,6 +148,8 @@ import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; import io.vertx.ext.web.RoutingContext; public class ResteasyReactiveProcessor { @@ -178,53 +160,10 @@ public class ResteasyReactiveProcessor { private static final DotName RESPONSE_HEADER_LIST = DotName.createSimple(ResponseHeader.List.class.getName()); private static final DotName RESPONSE_STATUS = DotName.createSimple(ResponseStatus.class.getName()); - private final static Serialisers.BuiltinReader[] BUILTIN_READERS = new Serialisers.BuiltinReader[] { - new Serialisers.BuiltinReader(String.class, ServerStringMessageBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinReader(Boolean.class, ServerBooleanMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinReader(Character.class, ServerCharacterMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinReader(Number.class, ServerNumberMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinReader(InputStream.class, ServerInputStreamMessageBodyHandler.class, MediaType.WILDCARD), - new Serialisers.BuiltinReader(Reader.class, ServerReaderBodyHandler.class, MediaType.WILDCARD), - new Serialisers.BuiltinReader(File.class, ServerFileBodyHandler.class, MediaType.WILDCARD), - - new Serialisers.BuiltinReader(byte[].class, ServerByteArrayMessageBodyHandler.class, MediaType.WILDCARD), - new Serialisers.BuiltinReader(Object.class, ServerDefaultTextPlainBodyHandler.class, MediaType.TEXT_PLAIN, - RuntimeType.SERVER), - }; - private final static Serialisers.BuiltinWriter[] BUILTIN_WRITERS = new Serialisers.BuiltinWriter[] { - new Serialisers.BuiltinWriter(String.class, ServerStringMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinWriter(Number.class, ServerStringMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinWriter(Boolean.class, ServerStringMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinWriter(Character.class, ServerStringMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinWriter(Object.class, ServerStringMessageBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(char[].class, ServerCharArrayMessageBodyHandler.class, - MediaType.TEXT_PLAIN), - new Serialisers.BuiltinWriter(byte[].class, ServerByteArrayMessageBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(MultivaluedMap.class, ServerFormUrlEncodedProvider.class, - MediaType.APPLICATION_FORM_URLENCODED), - new Serialisers.BuiltinWriter(InputStream.class, ServerInputStreamMessageBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(Reader.class, ServerReaderBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(File.class, ServerFileBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(FilePart.class, ServerFilePartBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(java.nio.file.Path.class, ServerPathBodyHandler.class, - MediaType.WILDCARD), - new Serialisers.BuiltinWriter(PathPart.class, ServerPathPartBodyHandler.class, - MediaType.WILDCARD), - }; + private static final Set CONTEXT_TYPES = Set.of( + DotName.createSimple(HttpServerRequest.class.getName()), + DotName.createSimple(HttpServerResponse.class.getName()), + DotName.createSimple(RoutingContext.class.getName())); @BuildStep public FeatureBuildItem buildSetup() { @@ -447,7 +386,6 @@ public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, ResourceScanningResult result = resourceScanningResultBuildItem.get().getResult(); Map scannedResources = result.getScannedResources(); Map scannedResourcePaths = result.getScannedResourcePaths(); - Map possibleSubResources = result.getPossibleSubResources(); Map pathInterfaces = result.getPathInterfaces(); ApplicationScanningResult appResult = applicationResultBuildItem.getResult(); @@ -477,6 +415,7 @@ public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, .addMethodScanners( methodScanners.stream().map(MethodScannerBuildItem::getMethodScanner).collect(toList())) .setIndex(index) + .addContextTypes(CONTEXT_TYPES) .setFactoryCreator(new QuarkusFactoryCreator(recorder, beanContainerBuildItem.getValue())) .setEndpointInvokerFactory(new QuarkusInvokerFactory(generatedClassBuildItemBuildProducer, recorder)) .setGeneratedClassBuildItemBuildProducer(generatedClassBuildItemBuildProducer) @@ -490,6 +429,7 @@ public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, .setInjectableBeans(injectableBeans) .setAdditionalWriters(additionalWriters) .setDefaultBlocking(appResult.getBlockingDefault()) + .setApplicationScanningResult(appResult) .setHasRuntimeConverters(!paramConverterProviders.getParamConverterProviders().isEmpty()) .setClassLevelExceptionMappers( classLevelExceptionMappers.isPresent() ? classLevelExceptionMappers.get().getMappers() @@ -584,15 +524,12 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an serverEndpointIndexer = serverEndpointIndexerBuilder.build(); for (ClassInfo i : scannedResources.values()) { - if (!appResult.keepClass(i.name().toString())) { - continue; - } - ResourceClass endpoints = serverEndpointIndexer.createEndpoints(i); - if (singletonClasses.contains(i.name().toString())) { - endpoints.setFactory(new SingletonBeanFactory<>(i.name().toString())); - } - if (endpoints != null) { - resourceClasses.add(endpoints); + Optional endpoints = serverEndpointIndexer.createEndpoints(i, true); + if (endpoints.isPresent()) { + if (singletonClasses.contains(i.name().toString())) { + endpoints.get().setFactory(new SingletonBeanFactory<>(i.name().toString())); + } + resourceClasses.add(endpoints.get()); } } //now index possible sub resources. These are all classes that have method annotations @@ -614,6 +551,7 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an toScan.add(classInfo); } } + Map possibleSubResources = new HashMap<>(); while (!toScan.isEmpty()) { ClassInfo classInfo = toScan.poll(); if (scannedResources.containsKey(classInfo.name()) || @@ -622,9 +560,9 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an continue; } possibleSubResources.put(classInfo.name(), classInfo); - ResourceClass endpoints = serverEndpointIndexer.createEndpoints(classInfo); - if (endpoints != null) { - subResourceClasses.add(endpoints); + Optional endpoints = serverEndpointIndexer.createEndpoints(classInfo, false); + if (endpoints.isPresent()) { + subResourceClasses.add(endpoints.get()); } //we need to also look for all sub classes and interfaces //they may have type variables that need to be handled @@ -660,13 +598,13 @@ public void serverSerializers(ResteasyReactiveRecorder recorder, RuntimeType.SERVER); // built-ins - for (Serialisers.BuiltinWriter builtinWriter : BUILTIN_WRITERS) { + for (Serialisers.BuiltinWriter builtinWriter : ServerSerialisers.BUILTIN_WRITERS) { registerWriter(recorder, serialisers, builtinWriter.entityClass, builtinWriter.writerClass, beanContainerBuildItem.getValue(), builtinWriter.mediaType); reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, builtinWriter.writerClass.getName())); } - for (Serialisers.BuiltinReader builtinReader : BUILTIN_READERS) { + for (Serialisers.BuiltinReader builtinReader : ServerSerialisers.BUILTIN_READERS) { registerReader(recorder, serialisers, builtinReader.entityClass, builtinReader.readerClass, beanContainerBuildItem.getValue(), builtinReader.mediaType, builtinReader.constraint); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java index 6f8201be317f8..c9ea62bdfec8f 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/ResteasyReactiveRecorder.java @@ -31,6 +31,7 @@ import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.server.util.RuntimeResourceVisitor; import org.jboss.resteasy.reactive.server.util.ScoreSystem; +import org.jboss.resteasy.reactive.server.vertx.BlockingInputHandler; import org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler; import org.jboss.resteasy.reactive.spi.BeanFactory; import org.jboss.resteasy.reactive.spi.ThreadSetupAction; diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java index 8a886684a676f..9d4625bc0eb0c 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/scanning/ClientEndpointIndexer.java @@ -68,7 +68,7 @@ public MaybeRestClientInterface createClientProxy(ClassInfo classInfo, clazz.setPath(path); } List methods = createEndpoints(classInfo, classInfo, new HashSet<>(), - clazz.getPathParameters(), clazz.getPath()); + clazz.getPathParameters(), clazz.getPath(), false); clazz.getMethods().addAll(methods); warnForUnsupportedAnnotations(classInfo); @@ -100,7 +100,7 @@ protected void handleClientSubResource(ResourceMethod resourceMethod, MethodInfo } List endpoints = createEndpoints(subResourceClass, subResourceClass, new HashSet<>(), new HashSet<>(), - ""); + "", false); resourceMethod.setSubResourceMethods(endpoints); } diff --git a/independent-projects/resteasy-reactive/common/processor/pom.xml b/independent-projects/resteasy-reactive/common/processor/pom.xml index 37eea267b9199..5f40b8e51d344 100644 --- a/independent-projects/resteasy-reactive/common/processor/pom.xml +++ b/independent-projects/resteasy-reactive/common/processor/pom.xml @@ -19,10 +19,6 @@ jandex - - io.quarkus.gizmo - gizmo - io.quarkus.resteasy.reactive resteasy-reactive-common diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java index 750b68ef5e3e6..6c2a05645ce0a 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java @@ -1,26 +1,10 @@ package org.jboss.resteasy.reactive.common.processor; -import static java.util.Arrays.asList; -import static org.objectweb.asm.Type.BOOLEAN_TYPE; -import static org.objectweb.asm.Type.BYTE_TYPE; -import static org.objectweb.asm.Type.CHAR_TYPE; -import static org.objectweb.asm.Type.DOUBLE_TYPE; -import static org.objectweb.asm.Type.FLOAT_TYPE; -import static org.objectweb.asm.Type.INT_TYPE; -import static org.objectweb.asm.Type.LONG_TYPE; -import static org.objectweb.asm.Type.SHORT_TYPE; -import static org.objectweb.asm.Type.VOID_TYPE; -import static org.objectweb.asm.Type.getType; - import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Function; import org.jboss.jandex.ArrayType; import org.jboss.jandex.ClassType; -import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.PrimitiveType.Primitive; @@ -29,8 +13,6 @@ import org.jboss.jandex.TypeVariable; import org.jboss.jandex.UnresolvedTypeVariable; import org.jboss.jandex.WildcardType; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; /** * A collection of ASM and Jandex utilities. @@ -38,103 +20,6 @@ * signature and getting the signature of a class. */ public class AsmUtil { - - public static final List PRIMITIVES = asList( - VOID_TYPE, - BOOLEAN_TYPE, - CHAR_TYPE, - BYTE_TYPE, - SHORT_TYPE, - INT_TYPE, - FLOAT_TYPE, - LONG_TYPE, - DOUBLE_TYPE); - public static final List WRAPPERS = asList( - getType(Void.class), - getType(Boolean.class), - getType(Character.class), - getType(Byte.class), - getType(Short.class), - getType(Integer.class), - getType(Float.class), - getType(Long.class), - getType(Double.class)); - public static final Map WRAPPER_TO_PRIMITIVE = new HashMap<>(); - - static { - for (int i = 0; i < AsmUtil.PRIMITIVES.size(); i++) { - AsmUtil.WRAPPER_TO_PRIMITIVE.put(AsmUtil.WRAPPERS.get(i), AsmUtil.PRIMITIVES.get(i)); - } - } - - public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { - return WRAPPERS.get(primitive.getSort()); - } - - /** - * Returns the Java bytecode signature of a given Jandex MethodInfo. - * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. - * - * @param method the method you want the signature for - * @return a bytecode signature for that method, or {@code null} if signature is not required - */ - public static String getSignatureIfRequired(MethodInfo method) { - return getSignatureIfRequired(method, ignored -> null); - } - - /** - * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. - * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. - * - * @param method the method you want the signature for - * @param typeArgMapper a mapping between type argument names and their bytecode signatures - * @return a bytecode signature for that method, or {@code null} if signature is not required - */ - public static String getSignatureIfRequired(MethodInfo method, Function typeArgMapper) { - if (!hasSignature(method)) { - return null; - } - - return getSignature(method, typeArgMapper); - } - - private static boolean hasSignature(MethodInfo method) { - // JVMS 16, chapter 4.7.9.1. Signatures: - // - // Java compiler must emit ... - // - // A method signature for any method or constructor declaration which is either generic, - // or has a type variable or parameterized type as the return type or a formal parameter type, - // or has a type variable in a throws clause, or any combination thereof. - - if (!method.typeParameters().isEmpty()) { - return true; - } - - { - Type type = method.returnType(); - if (type.kind() == Kind.TYPE_VARIABLE - || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE - || type.kind() == Kind.PARAMETERIZED_TYPE) { - return true; - } - } - - for (Type type : method.parameters()) { - if (type.kind() == Kind.TYPE_VARIABLE - || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE - || type.kind() == Kind.PARAMETERIZED_TYPE) { - return true; - } - } - - if (hasThrowsSignature(method)) { - return true; - } - - return false; - } - private static boolean hasThrowsSignature(MethodInfo method) { // JVMS 16, chapter 4.7.9.1. Signatures: // @@ -157,56 +42,6 @@ private static boolean hasThrowsSignature(MethodInfo method) { return false; } - /** - * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. - * For example, given this method: - * - *
-     * {@code
-     * public class Foo {
-     *  public  List method(int a, T t){...}
-     * }
-     * }
-     * 
- * - * This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if - * your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}. - * - * @param method the method you want the signature for. - * @param typeArgMapper a mapping between type argument names and their bytecode signature. - * @return a bytecode signature for that method. - */ - public static String getSignature(MethodInfo method, Function typeArgMapper) { - // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures - - StringBuilder signature = new StringBuilder(); - - if (!method.typeParameters().isEmpty()) { - signature.append('<'); - for (TypeVariable typeParameter : method.typeParameters()) { - typeParameter(typeParameter, signature, typeArgMapper); - } - signature.append('>'); - } - - signature.append('('); - for (Type type : method.parameters()) { - toSignature(signature, type, typeArgMapper, false); - } - signature.append(')'); - - toSignature(signature, method.returnType(), typeArgMapper, false); - - if (hasThrowsSignature(method)) { - for (Type exception : method.exceptions()) { - signature.append('^'); - toSignature(signature, exception, typeArgMapper, false); - } - } - - return signature.toString(); - } - private static void typeParameter(TypeVariable typeParameter, StringBuilder result, Function typeArgMapper) { result.append(typeParameter.identifier()); @@ -264,21 +99,6 @@ public static String getDescriptor(MethodInfo method, Function t return descriptor.toString(); } - /** - * Returns the Java bytecode descriptor of a given Jandex Type using the given type argument mappings. - * For example, given this type: List<T>, this will return Ljava/util/List; if - * your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}. - * - * @param type the type you want the descriptor for. - * @param typeArgMapper a mapping between type argument names and their bytecode descriptor. - * @return a bytecode descriptor for that type. - */ - public static String getDescriptor(Type type, Function typeArgMapper) { - StringBuilder sb = new StringBuilder(); - toSignature(sb, type, typeArgMapper, true); - return sb.toString(); - } - /** * Returns the Java bytecode signature of a given Jandex Type using the given type argument mappings. * For example, given this type: List<T>, this will return Ljava/util/List<Ljava/lang/Integer;>; if @@ -402,420 +222,4 @@ private static void toSignature(StringBuilder sb, Type type, FunctionIRETURN, LRETURN, FRETURN, DRETURN, RETURN for primitives/void, - * and ARETURN otherwise; - * - * @param typeDescriptor the return type descriptor. - * @return the correct bytecode return instruction for that return type descriptor. - */ - public static int getReturnInstruction(String typeDescriptor) { - switch (typeDescriptor) { - case "Z": - case "B": - case "C": - case "S": - case "I": - return Opcodes.IRETURN; - case "J": - return Opcodes.LRETURN; - case "F": - return Opcodes.FRETURN; - case "D": - return Opcodes.DRETURN; - case "V": - return Opcodes.RETURN; - default: - return Opcodes.ARETURN; - } - } - - /** - * Returns a return bytecode instruction suitable for the given return Jandex Type. This will return - * specialised return instructions IRETURN, LRETURN, FRETURN, DRETURN, RETURN for primitives/void, - * and ARETURN otherwise; - * - * @param jandexType the return Jandex Type. - * @return the correct bytecode return instruction for that return type descriptor. - */ - public static int getReturnInstruction(Type jandexType) { - if (jandexType.kind() == Kind.PRIMITIVE) { - switch (jandexType.asPrimitiveType().primitive()) { - case BOOLEAN: - case BYTE: - case SHORT: - case INT: - case CHAR: - return Opcodes.IRETURN; - case DOUBLE: - return Opcodes.DRETURN; - case FLOAT: - return Opcodes.FRETURN; - case LONG: - return Opcodes.LRETURN; - default: - throw new IllegalArgumentException("Unknown primitive type: " + jandexType); - } - } else if (jandexType.kind() == Kind.VOID) { - return Opcodes.RETURN; - } - return Opcodes.ARETURN; - } - - /** - * Invokes the proper LDC Class Constant instructions for the given Jandex Type. This will properly create LDC instructions - * for array types, class/parameterized classes, and primitive types by loading their equivalent TYPE - * constants in their box types, as well as type variables (using the first bound or Object) and Void. - * - * @param mv The MethodVisitor on which to visit the LDC instructions - * @param jandexType the Jandex Type whose Class Constant to load. - */ - public static void visitLdc(MethodVisitor mv, Type jandexType) { - switch (jandexType.kind()) { - case ARRAY: - mv.visitLdcInsn(org.objectweb.asm.Type.getType(jandexType.name().toString('/').replace('.', '/'))); - break; - case CLASS: - case PARAMETERIZED_TYPE: - mv.visitLdcInsn(org.objectweb.asm.Type.getType("L" + jandexType.name().toString('/') + ";")); - break; - case PRIMITIVE: - switch (jandexType.asPrimitiveType().primitive()) { - case BOOLEAN: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); - break; - case BYTE: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;"); - break; - case CHAR: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;"); - break; - case DOUBLE: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;"); - break; - case FLOAT: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;"); - break; - case INT: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); - break; - case LONG: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;"); - break; - case SHORT: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;"); - break; - default: - throw new IllegalArgumentException("Unknown primitive type: " + jandexType); - } - break; - case TYPE_VARIABLE: - List bounds = jandexType.asTypeVariable().bounds(); - if (bounds.isEmpty()) - mv.visitLdcInsn(org.objectweb.asm.Type.getType(Object.class)); - else - visitLdc(mv, bounds.get(0)); - break; - case UNRESOLVED_TYPE_VARIABLE: - mv.visitLdcInsn(org.objectweb.asm.Type.getType(Object.class)); - break; - case VOID: - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;"); - break; - case WILDCARD_TYPE: - visitLdc(mv, jandexType.asWildcardType().extendsBound()); - break; - default: - throw new IllegalArgumentException("Unknown jandex type: " + jandexType); - } - } - - /** - * Calls the right boxing method for the given Jandex Type if it is a primitive. - * - * @param mv The MethodVisitor on which to visit the boxing instructions - * @param jandexType The Jandex Type to box if it is a primitive. - */ - public static void boxIfRequired(MethodVisitor mv, Type jandexType) { - if (jandexType.kind() == Kind.PRIMITIVE) { - switch (jandexType.asPrimitiveType().primitive()) { - case BOOLEAN: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); - break; - case BYTE: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); - break; - case CHAR: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", - false); - break; - case DOUBLE: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); - break; - case FLOAT: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); - break; - case INT: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); - break; - case LONG: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); - break; - case SHORT: - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); - break; - default: - throw new IllegalArgumentException("Unknown primitive type: " + jandexType); - } - } - } - - /** - * Returns the bytecode instruction to load the given Jandex Type. This returns the specialised - * bytecodes ILOAD, DLOAD, FLOAD and LLOAD for primitives, or ALOAD otherwise. - * - * @param jandexType The Jandex Type whose load instruction to return. - * @return The bytecode instruction to load the given Jandex Type. - */ - public static int getLoadOpcode(Type jandexType) { - if (jandexType.kind() == Kind.PRIMITIVE) { - switch (jandexType.asPrimitiveType().primitive()) { - case BOOLEAN: - case BYTE: - case SHORT: - case INT: - case CHAR: - return Opcodes.ILOAD; - case DOUBLE: - return Opcodes.DLOAD; - case FLOAT: - return Opcodes.FLOAD; - case LONG: - return Opcodes.LLOAD; - default: - throw new IllegalArgumentException("Unknown primitive type: " + jandexType); - } - } - return Opcodes.ALOAD; - } - - /** - * Calls the right unboxing method for the given Jandex Type if it is a primitive. - * - * @param mv The MethodVisitor on which to visit the unboxing instructions - * @param jandexType The Jandex Type to unbox if it is a primitive. - */ - public static void unboxIfRequired(MethodVisitor mv, Type jandexType) { - if (jandexType.kind() == Kind.PRIMITIVE) { - switch (jandexType.asPrimitiveType().primitive()) { - case BOOLEAN: - unbox(mv, "java/lang/Boolean", "booleanValue", "Z"); - break; - case BYTE: - unbox(mv, "java/lang/Byte", "byteValue", "B"); - break; - case CHAR: - unbox(mv, "java/lang/Character", "charValue", "C"); - break; - case DOUBLE: - unbox(mv, "java/lang/Double", "doubleValue", "D"); - break; - case FLOAT: - unbox(mv, "java/lang/Float", "floatValue", "F"); - break; - case INT: - unbox(mv, "java/lang/Integer", "intValue", "I"); - break; - case LONG: - unbox(mv, "java/lang/Long", "longValue", "J"); - break; - case SHORT: - unbox(mv, "java/lang/Short", "shortValue", "S"); - break; - default: - throw new IllegalArgumentException("Unknown primitive type: " + jandexType); - } - } - } - - /** - * Calls the right unboxing method for the given Jandex Type if it is a primitive. - * - * @param mv The MethodVisitor on which to visit the unboxing instructions - * @param type The Jandex Type to unbox if it is a primitive. - */ - public static void unboxIfRequired(MethodVisitor mv, org.objectweb.asm.Type type) { - if (type.getSort() <= org.objectweb.asm.Type.DOUBLE) { - switch (type.getSort()) { - case org.objectweb.asm.Type.BOOLEAN: - unbox(mv, "java/lang/Boolean", "booleanValue", "Z"); - break; - case org.objectweb.asm.Type.BYTE: - unbox(mv, "java/lang/Byte", "byteValue", "B"); - break; - case org.objectweb.asm.Type.CHAR: - unbox(mv, "java/lang/Character", "charValue", "C"); - break; - case org.objectweb.asm.Type.DOUBLE: - unbox(mv, "java/lang/Double", "doubleValue", "D"); - break; - case org.objectweb.asm.Type.FLOAT: - unbox(mv, "java/lang/Float", "floatValue", "F"); - break; - case org.objectweb.asm.Type.INT: - unbox(mv, "java/lang/Integer", "intValue", "I"); - break; - case org.objectweb.asm.Type.LONG: - unbox(mv, "java/lang/Long", "longValue", "J"); - break; - case org.objectweb.asm.Type.SHORT: - unbox(mv, "java/lang/Short", "shortValue", "S"); - break; - } - } - } - - private static void unbox(MethodVisitor mv, String owner, String methodName, String returnTypeSignature) { - mv.visitTypeInsn(Opcodes.CHECKCAST, owner); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, methodName, "()" + returnTypeSignature, false); - } - - /** - * Returns the Jandex Types of the parameters of the given method descriptor. - * - * @param methodDescriptor a method descriptor - * @return the list of Jandex Type objects representing the parameters of the given method descriptor. - */ - public static Type[] getParameterTypes(String methodDescriptor) { - String argsSignature = methodDescriptor.substring(methodDescriptor.indexOf('(') + 1, methodDescriptor.lastIndexOf(')')); - List args = new ArrayList<>(); - char[] chars = argsSignature.toCharArray(); - int dimensions = 0; - int start = 0; - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - switch (c) { - case 'Z': - args.add(Type.create(DotName.createSimple("boolean"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'B': - args.add(Type.create(DotName.createSimple("byte"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'C': - args.add(Type.create(DotName.createSimple("char"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'D': - args.add(Type.create(DotName.createSimple("double"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'F': - args.add(Type.create(DotName.createSimple("float"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'I': - args.add(Type.create(DotName.createSimple("int"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'J': - args.add(Type.create(DotName.createSimple("long"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'S': - args.add(Type.create(DotName.createSimple("short"), - dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); - dimensions = 0; - start = i + 1; - break; - case 'L': - int end = argsSignature.indexOf(';', i); - String binaryName = argsSignature.substring(i + 1, end); - // arrays take the entire signature - if (dimensions > 0) { - args.add(Type.create(DotName.createSimple(argsSignature.substring(start, end + 1).replace('/', '.')), - Kind.ARRAY)); - dimensions = 0; - } else { - // class names take only the binary name - args.add(Type.create(DotName.createSimple(binaryName.replace('/', '.')), Kind.CLASS)); - } - i = end; // we will have a ++ to get after the ; - start = i + 1; - break; - case '[': - dimensions++; - break; - default: - throw new IllegalStateException("Invalid signature char: " + c); - } - } - return args.toArray(new Type[0]); - } - - /** - * Returns the number of underlying bytecode parameters taken by the given Jandex parameter Type. - * This will be 2 for doubles and longs, 1 otherwise. - * - * @param paramType the Jandex parameter Type - * @return the number of underlying bytecode parameters required. - */ - public static int getParameterSize(Type paramType) { - if (paramType.kind() == Kind.PRIMITIVE) { - switch (paramType.asPrimitiveType().primitive()) { - case DOUBLE: - case LONG: - return 2; - } - } - return 1; - } - - /** - * Prints the value pushed on the stack (must be an Object) by the given valuePusher - * to STDERR. - * - * @param mv The MethodVisitor to forward printing to. - * @param valuePusher The function to invoke to push an Object to print on the stack. - */ - public static void printValueOnStderr(MethodVisitor mv, Runnable valuePusher) { - mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;"); - valuePusher.run(); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", - "(Ljava/lang/Object;)V", false); - } - - /** - * Copy the parameter names to the given MethodVisitor, unless we don't have parameter name info - * - * @param mv the visitor to copy to - * @param method the method to copy from - */ - public static void copyParameterNames(MethodVisitor mv, MethodInfo method) { - int parameterSize = method.parameters().size(); - if (parameterSize > 0) { - // perhaps we don't have parameter names - if (method.parameterName(0) == null) - return; - for (int i = 0; i < parameterSize; i++) { - mv.visitParameter(method.parameterName(i), 0 /* modifiers */); - } - } - } } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/CalculatingIndexView.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/CalculatingIndexView.java new file mode 100644 index 0000000000000..ed12e32808283 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/CalculatingIndexView.java @@ -0,0 +1,226 @@ +package org.jboss.resteasy.reactive.common.processor; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; +import org.jboss.jandex.ModuleInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; + +public class CalculatingIndexView implements IndexView { + + private static final Logger LOGGER = Logger.getLogger(CalculatingIndexView.class); + + private final IndexView index; + private final ClassLoader classLoader; + final Map> additionalClasses; + + public CalculatingIndexView(IndexView index, ClassLoader classLoader, Map> additionalClasses) { + this.index = index; + this.classLoader = classLoader; + this.additionalClasses = additionalClasses; + } + + @Override + public Collection getKnownClasses() { + if (additionalClasses.isEmpty()) { + return index.getKnownClasses(); + } + Collection known = index.getKnownClasses(); + Collection additional = additionalClasses.values().stream().filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + List all = new ArrayList<>(known.size() + additional.size()); + all.addAll(known); + all.addAll(additional); + return all; + } + + @Override + public ClassInfo getClassByName(DotName className) { + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + classInfo = additionalClasses.computeIfAbsent(className, this::computeAdditional).orElse(null); + } + return classInfo; + } + + @Override + public Collection getKnownDirectSubclasses(DotName className) { + if (additionalClasses.isEmpty()) { + return index.getKnownDirectSubclasses(className); + } + Set directSubclasses = new HashSet(index.getKnownDirectSubclasses(className)); + for (Optional additional : additionalClasses.values()) { + if (additional.isPresent() && className.equals(additional.get().superName())) { + directSubclasses.add(additional.get()); + } + } + return directSubclasses; + } + + @Override + public Collection getAllKnownSubclasses(DotName className) { + if (additionalClasses.isEmpty()) { + return index.getAllKnownSubclasses(className); + } + final Set allKnown = new HashSet(); + final Set processedClasses = new HashSet(); + getAllKnownSubClasses(className, allKnown, processedClasses); + return allKnown; + } + + @Override + public Collection getKnownDirectImplementors(DotName className) { + if (additionalClasses.isEmpty()) { + return index.getKnownDirectImplementors(className); + } + Set directImplementors = new HashSet(index.getKnownDirectImplementors(className)); + for (Optional additional : additionalClasses.values()) { + if (!additional.isPresent()) { + continue; + } + for (Type interfaceType : additional.get().interfaceTypes()) { + if (className.equals(interfaceType.name())) { + directImplementors.add(additional.get()); + break; + } + } + } + return directImplementors; + } + + @Override + public Collection getAllKnownImplementors(DotName interfaceName) { + if (additionalClasses.isEmpty()) { + return index.getAllKnownImplementors(interfaceName); + } + final Set allKnown = new HashSet(); + final Set subInterfacesToProcess = new HashSet(); + final Set processedClasses = new HashSet(); + subInterfacesToProcess.add(interfaceName); + while (!subInterfacesToProcess.isEmpty()) { + final Iterator toProcess = subInterfacesToProcess.iterator(); + DotName name = toProcess.next(); + toProcess.remove(); + processedClasses.add(name); + getKnownImplementors(name, allKnown, subInterfacesToProcess, processedClasses); + } + return allKnown; + } + + @Override + public Collection getAnnotations(DotName annotationName) { + return index.getAnnotations(annotationName); + } + + @Override + public Collection getAnnotationsWithRepeatable(DotName annotationName, IndexView index) { + return this.index.getAnnotationsWithRepeatable(annotationName, index); + } + + @Override + public Collection getKnownModules() { + return this.index.getKnownModules(); + } + + @Override + public ModuleInfo getModuleByName(DotName moduleName) { + return this.index.getModuleByName(moduleName); + } + + @Override + public Collection getKnownUsers(DotName className) { + return this.index.getKnownUsers(className); + } + + private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { + final Set subClassesToProcess = new HashSet(); + subClassesToProcess.add(className); + while (!subClassesToProcess.isEmpty()) { + final Iterator toProcess = subClassesToProcess.iterator(); + DotName name = toProcess.next(); + toProcess.remove(); + processedClasses.add(name); + getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); + } + } + + private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, + Set processedClasses) { + final Collection directSubclasses = getKnownDirectSubclasses(name); + if (directSubclasses != null) { + for (final ClassInfo clazz : directSubclasses) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + allKnown.add(clazz); + subClassesToProcess.add(className); + } + } + } + } + + private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, + Set processedClasses) { + final Collection list = getKnownDirectImplementors(name); + if (list != null) { + for (final ClassInfo clazz : list) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + if (Modifier.isInterface(clazz.flags())) { + subInterfacesToProcess.add(className); + } else { + if (!allKnown.contains(clazz)) { + allKnown.add(clazz); + processedClasses.add(className); + getAllKnownSubClasses(className, allKnown, processedClasses); + } + } + } + } + } + } + + private Optional computeAdditional(DotName className) { + LOGGER.debugf("Index: %s", className); + Indexer indexer = new Indexer(); + if (index(indexer, className.toString(), classLoader)) { + Index index = indexer.complete(); + return Optional.of(index.getClassByName(className)); + } else { + // Note that ConcurrentHashMap does not allow null to be used as a value + return Optional.empty(); + } + } + + static boolean index(Indexer indexer, String className, ClassLoader classLoader) { + boolean result = false; + try (InputStream stream = classLoader + .getResourceAsStream(className.replace('.', '/') + ".class")) { + if (stream != null) { + indexer.index(stream); + result = true; + } else { + LOGGER.warnf("Failed to index %s: Class does not exist in ClassLoader %s", className, classLoader); + } + } catch (IOException e) { + LOGGER.warnf(e, "Failed to index %s: %s", className, e.getMessage()); + } + return result; + } +} diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 01fc43010e54d..d8da4b951007f 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -67,6 +67,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -94,19 +95,19 @@ import org.jboss.resteasy.reactive.common.model.ParameterType; import org.jboss.resteasy.reactive.common.model.ResourceClass; import org.jboss.resteasy.reactive.common.model.ResourceMethod; +import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore; import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.common.util.URLUtils; import org.jboss.resteasy.reactive.spi.BeanFactory; -import org.objectweb.asm.Opcodes; public abstract class EndpointIndexer, PARAM extends IndexedParameter, METHOD extends ResourceMethod> { protected static final Map primitiveTypes; private static final Map> supportedReaderJavaTypes; // NOTE: sync with ContextProducer and ContextParamExtractor - private static final Set CONTEXT_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + private static final Set DEFAULT_CONTEXT_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( // spec ResteasyReactiveDotNames.URI_INFO, ResteasyReactiveDotNames.HTTP_HEADERS, @@ -184,6 +185,8 @@ public abstract class EndpointIndexer> factoryCreator; private final Consumer resourceMethodCallback; private final AnnotationStore annotationStore; + private final ApplicationScanningResult applicationScanningResult; + private final Set contextTypes; protected EndpointIndexer(Builder builder) { this.index = builder.index; @@ -200,9 +203,14 @@ protected EndpointIndexer(Builder builder) { this.factoryCreator = builder.factoryCreator; this.resourceMethodCallback = builder.resourceMethodCallback; this.annotationStore = new AnnotationStore(builder.annotationsTransformers); + this.applicationScanningResult = builder.applicationScanningResult; + this.contextTypes = builder.contextTypes; } - public ResourceClass createEndpoints(ClassInfo classInfo) { + public Optional createEndpoints(ClassInfo classInfo, boolean considerApplication) { + if (considerApplication && !applicationScanningResult.keepClass(classInfo.name().toString())) { + return Optional.empty(); + } try { String path = scannedResourcePaths.get(classInfo.name()); ResourceClass clazz = new ResourceClass(); @@ -226,7 +234,7 @@ public ResourceClass createEndpoints(ClassInfo classInfo) { clazz.setClassLevelExceptionMappers(classLevelExceptionMappers); } List methods = createEndpoints(classInfo, classInfo, new HashSet<>(), - clazz.getPathParameters(), clazz.getPath()); + clazz.getPathParameters(), clazz.getPath(), considerApplication); clazz.getMethods().addAll(methods); // get an InjectableBean view of our class @@ -246,13 +254,13 @@ public ResourceClass createEndpoints(ClassInfo classInfo) { clazz.setPerRequestResource(true); } - return clazz; + return Optional.of(clazz); } catch (Exception e) { if (Modifier.isInterface(classInfo.flags()) || Modifier.isAbstract(classInfo.flags())) { //kinda bogus, but we just ignore failed interfaces for now //they can have methods that are not valid until they are actually extended by a concrete type log.debug("Ignoring interface " + classInfo.name(), e); - return null; + return Optional.empty(); } throw new RuntimeException(e); } @@ -263,7 +271,11 @@ protected abstract METHOD createResourceMethod(MethodInfo info, ClassInfo actual protected List createEndpoints(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, Set seenMethods, - Set pathParameters, String resourceClassPath) { + Set pathParameters, String resourceClassPath, boolean considerApplication) { + if (considerApplication && applicationScanningResult != null + && !applicationScanningResult.keepClass(actualEndpointInfo.name().toString())) { + return Collections.emptyList(); + } // $$CDIWrapper suffix is used to generate CDI beans from interfaces, we don't want to create endpoints for them: if (currentClassInfo.name().toString().endsWith(CDI_WRAPPER_SUFFIX)) { @@ -348,7 +360,7 @@ protected List createEndpoints(ClassInfo currentClassInfo, ClassInfo superClass = index.getClassByName(superClassName); if (superClass != null) { ret.addAll(createEndpoints(superClass, actualEndpointInfo, seenMethods, - pathParameters, resourceClassPath)); + pathParameters, resourceClassPath, considerApplication)); } } List interfaces = currentClassInfo.interfaceNames(); @@ -356,7 +368,7 @@ protected List createEndpoints(ClassInfo currentClassInfo, ClassInfo superClass = index.getClassByName(i); if (superClass != null) { ret.addAll(createEndpoints(superClass, actualEndpointInfo, seenMethods, - pathParameters, resourceClassPath)); + pathParameters, resourceClassPath, considerApplication)); } } return ret; @@ -400,7 +412,7 @@ private boolean hasProperModifiers(MethodInfo info) { } private boolean isSynthetic(int mod) { - return (mod & Opcodes.ACC_SYNTHETIC) != 0; + return (mod & 0x1000) != 0; //0x1000 == SYNTHETIC } private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInfo actualEndpointInfo, @@ -700,7 +712,7 @@ private Type getNonAsyncReturnType(Type returnType) { } return returnType; // FIXME: should be an exception, but we have incomplete support for generics ATM, so we still - // have some unresolved type vars and they do pass _some_ tests + // have some unresolved type vars and they do pass _some_ tests // throw new UnsupportedOperationException("Endpoint return type not supported yet: " + returnType); } @@ -1145,8 +1157,8 @@ protected void handleListParam(Map existingConverters, String er PARAM builder, String elementType) { } - protected boolean isContextType(ClassType klass) { - return CONTEXT_TYPES.contains(klass.name()); + final boolean isContextType(ClassType klass) { + return contextTypes.contains(klass.name()); } private String valueOrDefault(AnnotationValue annotation, String defaultValue) { @@ -1173,7 +1185,7 @@ public static abstract class Builder, B private Function> factoryCreator = new ReflectionBeanFactoryCreator(); private BlockingDefault defaultBlocking = BlockingDefault.AUTOMATIC; private IndexView index; - private Map existingConverters; + private Map existingConverters = new HashMap<>(); private Map scannedResourcePaths; private ResteasyReactiveConfig config; private AdditionalReaders additionalReaders; @@ -1184,6 +1196,8 @@ public static abstract class Builder, B private Map> classLevelExceptionMappers; private Consumer resourceMethodCallback; private Collection annotationsTransformers; + private ApplicationScanningResult applicationScanningResult; + private Set contextTypes = new HashSet<>(DEFAULT_CONTEXT_TYPES); public B setDefaultBlocking(BlockingDefault defaultBlocking) { this.defaultBlocking = defaultBlocking; @@ -1200,6 +1214,16 @@ public B setIndex(IndexView index) { return (B) this; } + public B addContextType(DotName contextType) { + this.contextTypes.add(contextType); + return (B) this; + } + + public B addContextTypes(Collection contextTypes) { + this.contextTypes.addAll(contextTypes); + return (B) this; + } + public B setExistingConverters(Map existingConverters) { this.existingConverters = existingConverters; return (B) this; @@ -1255,6 +1279,11 @@ public B setAnnotationsTransformers(Collection annotatio return (B) this; } + public B setApplicationScanningResult(ApplicationScanningResult applicationScanningResult) { + this.applicationScanningResult = applicationScanningResult; + return (B) this; + } + public abstract T build(); } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java index 879bb8088b9c8..41fdabae253c2 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/JandexUtil.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.Stream; import org.jboss.jandex.AnnotationInstance; @@ -58,6 +59,11 @@ public void accept(Path path) { return indexer.complete(); } + public static IndexView createCalculatingIndex(Path path) { + Index index = createIndex(path); + return new CalculatingIndexView(index, Thread.currentThread().getContextClassLoader(), new ConcurrentHashMap<>()); + } + /** * Returns the captured generic types of an interface given a class that at some point in the class * hierarchy implements the interface. @@ -369,4 +375,5 @@ public static String getBoxedTypeName(Type type) { } return type.toString(); } + } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java index c9af2ce7968ac..8045a4752be6c 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveInterceptorScanner.java @@ -14,7 +14,7 @@ import org.jboss.resteasy.reactive.common.model.ResourceInterceptors; import org.jboss.resteasy.reactive.common.processor.NameBindingUtil; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.spi.BeanFactory; /** diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java index 0534765b532fa..3ea312024feb9 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ResteasyReactiveScanner.java @@ -5,12 +5,14 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HEAD; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.OPTIONS; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATCH; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PATH; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.POST; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.PUT; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -21,6 +23,8 @@ import java.util.Set; import java.util.stream.Collectors; import javax.enterprise.inject.spi.DeploymentException; +import javax.ws.rs.Priorities; +import javax.ws.rs.RuntimeType; import javax.ws.rs.core.Application; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; @@ -31,6 +35,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; import org.jboss.resteasy.reactive.common.processor.BlockingDefault; +import org.jboss.resteasy.reactive.common.processor.JandexUtil; import org.jboss.resteasy.reactive.common.processor.NameBindingUtil; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; @@ -123,6 +128,80 @@ public static ApplicationScanningResult scanForApplicationClass(IndexView index, selectedAppClass, blocking); } + public static SerializerScanningResult scanForSerializers(IndexView index, + ApplicationScanningResult applicationScanningResult) { + Collection readers = index + .getAllKnownImplementors(ResteasyReactiveDotNames.MESSAGE_BODY_READER); + List readerList = new ArrayList<>(); + + for (ClassInfo readerClass : readers) { + ApplicationScanningResult.KeepProviderResult keepProviderResult = applicationScanningResult + .keepProvider(readerClass); + if (keepProviderResult != ApplicationScanningResult.KeepProviderResult.DISCARD) { + List typeParameters = JandexUtil.resolveTypeParameters(readerClass.name(), + ResteasyReactiveDotNames.MESSAGE_BODY_READER, + index); + RuntimeType runtimeType = null; + if (keepProviderResult == ApplicationScanningResult.KeepProviderResult.SERVER_ONLY) { + runtimeType = RuntimeType.SERVER; + } + List mediaTypeStrings = Collections.emptyList(); + String readerClassName = readerClass.name().toString(); + AnnotationInstance consumesAnnotation = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSUMES); + if (consumesAnnotation != null) { + mediaTypeStrings = Arrays.asList(consumesAnnotation.value().asStringArray()); + } + AnnotationInstance constrainedToInstance = readerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); + if (constrainedToInstance != null) { + runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); + } + int priority = Priorities.USER; + AnnotationInstance priorityInstance = readerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + if (priorityInstance != null) { + priority = priorityInstance.value().asInt(); + } + readerList.add(new ScannedSerializer(readerClassName, + typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority)); + } + } + + List writerList = new ArrayList<>(); + Collection writers = index + .getAllKnownImplementors(ResteasyReactiveDotNames.MESSAGE_BODY_WRITER); + + for (ClassInfo writerClass : writers) { + ApplicationScanningResult.KeepProviderResult keepProviderResult = applicationScanningResult + .keepProvider(writerClass); + if (keepProviderResult != ApplicationScanningResult.KeepProviderResult.DISCARD) { + RuntimeType runtimeType = null; + if (keepProviderResult == ApplicationScanningResult.KeepProviderResult.SERVER_ONLY) { + runtimeType = RuntimeType.SERVER; + } + List mediaTypeStrings = Collections.emptyList(); + AnnotationInstance producesAnnotation = writerClass.classAnnotation(ResteasyReactiveDotNames.PRODUCES); + if (producesAnnotation != null) { + mediaTypeStrings = Arrays.asList(producesAnnotation.value().asStringArray()); + } + List typeParameters = JandexUtil.resolveTypeParameters(writerClass.name(), + ResteasyReactiveDotNames.MESSAGE_BODY_WRITER, + index); + String writerClassName = writerClass.name().toString(); + AnnotationInstance constrainedToInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); + if (constrainedToInstance != null) { + runtimeType = RuntimeType.valueOf(constrainedToInstance.value().asEnum()); + } + int priority = Priorities.USER; + AnnotationInstance priorityInstance = writerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + if (priorityInstance != null) { + priority = priorityInstance.value().asInt(); + } + writerList.add(new ScannedSerializer(writerClassName, + typeParameters.get(0).name().toString(), mediaTypeStrings, runtimeType, false, priority)); + } + } + return new SerializerScanningResult(readerList, writerList); + } + private static boolean appClassHasInject(ClassInfo appClass) { if (appClass.annotations() == null) { return false; @@ -225,6 +304,8 @@ public static ResourceScanningResult scanResources( && Modifier.isAbstract(classWithJaxrsMethod.flags()) && !clientInterfaces.containsKey(classWithJaxrsMethod.name())) { clientInterfaces.put(classWithJaxrsMethod.name(), ""); + } else if (classWithJaxrsMethod.classAnnotation(PATH) == null) { + possibleSubResources.put(classWithJaxrsMethod.name(), classWithJaxrsMethod); } } } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java new file mode 100644 index 0000000000000..d607e21efb7b7 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/ScannedSerializer.java @@ -0,0 +1,53 @@ +package org.jboss.resteasy.reactive.common.processor.scanning; + +import java.util.List; +import javax.ws.rs.Priorities; +import javax.ws.rs.RuntimeType; + +public class ScannedSerializer { + + private final String className; + private final String handledClassName; + private final List mediaTypeStrings; + private final RuntimeType runtimeType; + private final boolean builtin; + private final Integer priority; + + public ScannedSerializer(String className, String handledClassName, List mediaTypeStrings) { + this(className, handledClassName, mediaTypeStrings, null, true, Priorities.USER); + } + + public ScannedSerializer(String className, String handledClassName, List mediaTypeStrings, + RuntimeType runtimeType, boolean builtin, Integer priority) { + this.className = className; + this.handledClassName = handledClassName; + this.mediaTypeStrings = mediaTypeStrings; + this.runtimeType = runtimeType; + this.builtin = builtin; + this.priority = priority; + } + + public String getClassName() { + return className; + } + + public String getHandledClassName() { + return handledClassName; + } + + public List getMediaTypeStrings() { + return mediaTypeStrings; + } + + public RuntimeType getRuntimeType() { + return runtimeType; + } + + public boolean isBuiltin() { + return builtin; + } + + public Integer getPriority() { + return priority; + } +} diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/SerializerScanningResult.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/SerializerScanningResult.java new file mode 100644 index 0000000000000..82828d881f14e --- /dev/null +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/scanning/SerializerScanningResult.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.common.processor.scanning; + +import java.util.List; + +public class SerializerScanningResult { + + private final List readers; + private final List writers; + + public SerializerScanningResult(List readers, List writers) { + this.readers = List.copyOf(readers); + this.writers = List.copyOf(writers); + } + + public List getReaders() { + return readers; + } + + public List getWriters() { + return writers; + } +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceReader.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceReader.java index f0caa580ad710..69ad09b953e4a 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceReader.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceReader.java @@ -52,16 +52,18 @@ public boolean isBuiltin() { return builtin; } - public void setBuiltin(boolean builtin) { + public ResourceReader setBuiltin(boolean builtin) { this.builtin = builtin; + return this; } public Integer getPriority() { return priority; } - public void setPriority(Integer priority) { + public ResourceReader setPriority(Integer priority) { this.priority = priority; + return this; } public MessageBodyReader instance() { diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceWriter.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceWriter.java index a547ef6b956e8..d04c4420c801d 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceWriter.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceWriter.java @@ -55,16 +55,18 @@ public boolean isBuiltin() { return builtin; } - public void setBuiltin(boolean builtin) { + public ResourceWriter setBuiltin(boolean builtin) { this.builtin = builtin; + return this; } public Integer getPriority() { return priority; } - public void setPriority(Integer priority) { + public ResourceWriter setPriority(Integer priority) { this.priority = priority; + return this; } public MessageBodyWriter instance() { diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/ReflectionBeanFactory.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/reflection/ReflectionBeanFactory.java similarity index 94% rename from independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/ReflectionBeanFactory.java rename to independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/reflection/ReflectionBeanFactory.java index f9998a0393c55..02bb74b2970f0 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/ReflectionBeanFactory.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/reflection/ReflectionBeanFactory.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.reactive.common.util; +package org.jboss.resteasy.reactive.common.reflection; import org.jboss.resteasy.reactive.spi.BeanFactory; diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/ReflectionBeanFactoryCreator.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/reflection/ReflectionBeanFactoryCreator.java similarity index 85% rename from independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/ReflectionBeanFactoryCreator.java rename to independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/reflection/ReflectionBeanFactoryCreator.java index 236ca4bf95e27..0382424a116f6 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/ReflectionBeanFactoryCreator.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/reflection/ReflectionBeanFactoryCreator.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.reactive.common.util; +package org.jboss.resteasy.reactive.common.reflection; import java.util.function.Function; import org.jboss.resteasy.reactive.spi.BeanFactory; diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 091d6192153b9..a64d4abe241c2 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -58,6 +58,7 @@ 2.0.0.Final 2.12.5 1.0.0.Alpha7 + 2.0.2 @@ -280,6 +281,11 @@ ${jboss-jaxb-api_2.3_spec.version} test
+ + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} + diff --git a/independent-projects/resteasy-reactive/server/processor/pom.xml b/independent-projects/resteasy-reactive/server/processor/pom.xml index 17ccebbe69c01..799fa3f9f967e 100644 --- a/independent-projects/resteasy-reactive/server/processor/pom.xml +++ b/independent-projects/resteasy-reactive/server/processor/pom.xml @@ -19,10 +19,6 @@ jandex - - io.quarkus.gizmo - gizmo - io.quarkus.resteasy.reactive resteasy-reactive diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ReflectionEndpointInvokerFactory.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ReflectionEndpointInvokerFactory.java index 3d2b37d740626..ae1b3ee2bbc56 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ReflectionEndpointInvokerFactory.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ReflectionEndpointInvokerFactory.java @@ -1,5 +1,6 @@ package org.jboss.resteasy.reactive.server.processor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.function.Supplier; @@ -26,7 +27,14 @@ public EndpointInvoker get() { return new EndpointInvoker() { @Override public Object invoke(Object instance, Object[] parameters) throws Exception { - return meth.invoke(instance, parameters); + try { + return meth.invoke(instance, parameters); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof Exception) { + throw (Exception) e.getCause(); + } + throw e; + } } }; } catch (Exception e) { diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java index 39ec7b4c5b494..92c612b1125e6 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java @@ -9,8 +9,9 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_STRUCTURE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JSONP_JSON_VALUE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.MULTI_VALUED_MAP; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.STRING; -import io.quarkus.gizmo.MethodCreator; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -23,6 +24,7 @@ import java.util.function.Supplier; import java.util.regex.PatternSyntaxException; import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.PathSegment; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -41,12 +43,17 @@ import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore; import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; import org.jboss.resteasy.reactive.server.core.parameters.converters.ListConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.LoadedParameterConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.LocalDateParamConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.NoopParameterConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.OptionalConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverterSupplier; import org.jboss.resteasy.reactive.server.core.parameters.converters.PathSegmentParamConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.RuntimeResolvedConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.SetConverter; import org.jboss.resteasy.reactive.server.core.parameters.converters.SortedSetConverter; +import org.jboss.resteasy.reactive.server.core.reflection.ReflectionConstructorParameterConverterSupplier; +import org.jboss.resteasy.reactive.server.core.reflection.ReflectionValueOfParameterConverterSupplier; import org.jboss.resteasy.reactive.server.mapping.URITemplate; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; import org.jboss.resteasy.reactive.server.model.ServerMethodParameter; @@ -61,13 +68,11 @@ public class ServerEndpointIndexer extends EndpointIndexer { - private final MethodCreator initConverters; protected final EndpointInvokerFactory endpointInvokerFactory; protected final List methodScanners; protected ServerEndpointIndexer(AbstractBuilder builder) { super(builder); - this.initConverters = builder.initConverters; this.endpointInvokerFactory = builder.endpointInvokerFactory; this.methodScanners = new ArrayList<>(builder.methodScanners); } @@ -291,7 +296,7 @@ protected void handleOtherParam(Map existingConverters, String e existingConverters, errorLocation, hasRuntimeConverters)); } catch (Throwable throwable) { throw new RuntimeException("Could not create converter for " + elementType + " for " + builder.getErrorLocation() - + " of type " + builder.getType()); + + " of type " + builder.getType(), throwable); } } @@ -331,16 +336,86 @@ protected void handleLocalDateParam(ServerIndexedParameter builder) { builder.setConverter(new LocalDateParamConverter.Supplier()); } - protected ParameterConverterSupplier extractConverter(String elementType, IndexView indexView, + private ParameterConverterSupplier extractConverter(String elementType, IndexView indexView, Map existingConverters, String errorLocation, boolean hasRuntimeConverters) { - return null; + if (elementType.equals(String.class.getName())) { + if (hasRuntimeConverters) + return new RuntimeResolvedConverter.Supplier().setDelegate(new NoopParameterConverter.Supplier()); + // String needs no conversion + return null; + } else if (existingConverters.containsKey(elementType)) { + String className = existingConverters.get(elementType); + ParameterConverterSupplier delegate; + if (className == null) + delegate = null; + else + delegate = new LoadedParameterConverter().setClassName(className); + if (hasRuntimeConverters) + return new RuntimeResolvedConverter.Supplier().setDelegate(delegate); + if (delegate == null) + throw new RuntimeException("Failed to find converter for " + elementType); + return delegate; + } else if (elementType.equals(PathSegment.class.getName())) { + return new PathSegmentParamConverter.Supplier(); + } + return extractConverterImpl(elementType, indexView, existingConverters, errorLocation, hasRuntimeConverters); + } + + protected ParameterConverterSupplier extractConverterImpl(String elementType, IndexView indexView, + Map existingConverters, String errorLocation, boolean hasRuntimeConverters) { + MethodInfo fromString = null; + MethodInfo valueOf = null; + MethodInfo stringCtor = null; + String primitiveWrapperType = primitiveTypes.get(elementType); + String prefix = ""; + if (primitiveWrapperType != null) { + return new ReflectionValueOfParameterConverterSupplier(primitiveWrapperType); + } else { + ClassInfo type = indexView.getClassByName(DotName.createSimple(elementType)); + if (type != null) { + for (MethodInfo i : type.methods()) { + boolean isStatic = ((i.flags() & Modifier.STATIC) != 0); + boolean isNotPrivate = (i.flags() & Modifier.PRIVATE) == 0; + if ((i.parameters().size() == 1) && isNotPrivate) { + if (i.parameters().get(0).name().equals(STRING)) { + if (i.name().equals("")) { + stringCtor = i; + } else if (i.name().equals("valueOf") && isStatic) { + valueOf = i; + } else if (i.name().equals("fromString") && isStatic) { + fromString = i; + } + } + } + } + if (type.isEnum()) { + //spec weirdness, enums order is different + if (fromString != null) { + valueOf = null; + } + } + } + } + ParameterConverterSupplier delegate = null; + if (stringCtor != null) { + delegate = new ReflectionConstructorParameterConverterSupplier(stringCtor.declaringClass().name().toString()); + } else if (valueOf != null) { + delegate = new ReflectionValueOfParameterConverterSupplier(valueOf.declaringClass().name().toString()); + } else if (fromString != null) { + delegate = new ReflectionValueOfParameterConverterSupplier(fromString.declaringClass().name().toString(), + "fromString"); + } + if (hasRuntimeConverters) + return new RuntimeResolvedConverter.Supplier().setDelegate(delegate); + if (delegate == null) + throw new RuntimeException("Failed to find converter for " + elementType); + return delegate; } @SuppressWarnings("unchecked") public static class AbstractBuilder> extends EndpointIndexer.Builder { - private MethodCreator initConverters; private EndpointInvokerFactory endpointInvokerFactory = new ReflectionEndpointInvokerFactory(); private List methodScanners = new ArrayList<>(); @@ -353,15 +428,6 @@ public B setEndpointInvokerFactory(EndpointInvokerFactory endpointInvokerFactory return (B) this; } - public MethodCreator getInitConverters() { - return initConverters; - } - - public B setInitConverters(MethodCreator initConverters) { - this.initConverters = initConverters; - return (B) this; - } - public B addMethodScanner(MethodScanner methodScanner) { this.methodScanners.add(methodScanner); return (B) this; diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java index 5441090aafabc..9f9f925eb24c1 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveContextResolverScanner.java @@ -14,7 +14,7 @@ import org.jboss.resteasy.reactive.common.processor.JandexUtil; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.server.model.ContextResolvers; import org.jboss.resteasy.reactive.spi.BeanFactory; diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java index 7e5338fe3cf01..e0e4d7b7bbd8e 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveExceptionMappingScanner.java @@ -13,7 +13,7 @@ import org.jboss.resteasy.reactive.common.processor.JandexUtil; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.server.core.ExceptionMapping; import org.jboss.resteasy.reactive.spi.BeanFactory; diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveFeatureScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveFeatureScanner.java index 722b8f6055ba9..00cb508e94b7a 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveFeatureScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveFeatureScanner.java @@ -12,7 +12,7 @@ import org.jboss.resteasy.reactive.common.model.ResourceFeature; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.server.model.DynamicFeatures; import org.jboss.resteasy.reactive.server.model.Features; import org.jboss.resteasy.reactive.spi.BeanFactory; diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java index 9b28b0dafde31..d569ea08c949a 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ResteasyReactiveParamConverterScanner.java @@ -9,7 +9,7 @@ import org.jboss.resteasy.reactive.common.model.ResourceParamConverterProvider; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; import org.jboss.resteasy.reactive.spi.BeanFactory; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/BlockingOperationSupport.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/BlockingOperationSupport.java index 69e44c27af743..792e18ad1dc75 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/BlockingOperationSupport.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/BlockingOperationSupport.java @@ -4,6 +4,7 @@ public class BlockingOperationSupport { private static volatile IOThreadDetector ioThreadDetector; + //TODO: move away from a static public static void setIoThreadDetector(IOThreadDetector ioThreadDetector) { BlockingOperationSupport.ioThreadDetector = ioThreadDetector; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java index 9d2c7354101b2..fc72e1932ba54 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java @@ -1,6 +1,9 @@ package org.jboss.resteasy.reactive.server.core; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; import java.lang.reflect.Type; import java.util.ArrayDeque; import java.util.ArrayList; @@ -26,6 +29,8 @@ import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.WriterInterceptor; +import org.jboss.resteasy.reactive.FilePart; +import org.jboss.resteasy.reactive.PathPart; import org.jboss.resteasy.reactive.common.PreserveTargetException; import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.headers.HeaderUtil; @@ -39,6 +44,20 @@ import org.jboss.resteasy.reactive.server.core.serialization.FixedEntityWriterArray; import org.jboss.resteasy.reactive.server.jaxrs.WriterInterceptorContextImpl; import org.jboss.resteasy.reactive.server.mapping.RuntimeResource; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerBooleanMessageBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerByteArrayMessageBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerCharArrayMessageBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerCharacterMessageBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerDefaultTextPlainBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFileBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFilePartBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerFormUrlEncodedProvider; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerInputStreamMessageBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerNumberMessageBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerPathBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerPathPartBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerReaderBodyHandler; +import org.jboss.resteasy.reactive.server.providers.serialisers.ServerStringMessageBodyHandler; import org.jboss.resteasy.reactive.server.spi.ServerHttpRequest; import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; @@ -65,6 +84,52 @@ public void accept(ResteasyReactiveRequestContext context) { primitivesToWrappers.put(double.class, Double.class); } + public final static List BUILTIN_READERS = List.of( + new Serialisers.BuiltinReader(String.class, ServerStringMessageBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinReader(Boolean.class, ServerBooleanMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinReader(Character.class, ServerCharacterMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinReader(Number.class, ServerNumberMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinReader(InputStream.class, ServerInputStreamMessageBodyHandler.class, MediaType.WILDCARD), + new Serialisers.BuiltinReader(Reader.class, ServerReaderBodyHandler.class, MediaType.WILDCARD), + new Serialisers.BuiltinReader(File.class, ServerFileBodyHandler.class, MediaType.WILDCARD), + + new Serialisers.BuiltinReader(byte[].class, ServerByteArrayMessageBodyHandler.class, MediaType.WILDCARD), + new Serialisers.BuiltinReader(Object.class, ServerDefaultTextPlainBodyHandler.class, MediaType.TEXT_PLAIN, + RuntimeType.SERVER)); + public final static List BUILTIN_WRITERS = List.of( + new Serialisers.BuiltinWriter(String.class, ServerStringMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinWriter(Number.class, ServerStringMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinWriter(Boolean.class, ServerStringMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinWriter(Character.class, ServerStringMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinWriter(Object.class, ServerStringMessageBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(char[].class, ServerCharArrayMessageBodyHandler.class, + MediaType.TEXT_PLAIN), + new Serialisers.BuiltinWriter(byte[].class, ServerByteArrayMessageBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(MultivaluedMap.class, ServerFormUrlEncodedProvider.class, + MediaType.APPLICATION_FORM_URLENCODED), + new Serialisers.BuiltinWriter(InputStream.class, ServerInputStreamMessageBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(Reader.class, ServerReaderBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(File.class, ServerFileBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(FilePart.class, ServerFilePartBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(java.nio.file.Path.class, ServerPathBodyHandler.class, + MediaType.WILDCARD), + new Serialisers.BuiltinWriter(PathPart.class, ServerPathPartBodyHandler.class, + MediaType.WILDCARD)); + public static final MessageBodyWriter[] NO_WRITER = new MessageBodyWriter[0]; public static final MessageBodyReader[] NO_READER = new MessageBodyReader[0]; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/ContextParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/ContextParamExtractor.java index 085850388056e..ae9e7669f58bf 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/ContextParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/ContextParamExtractor.java @@ -33,6 +33,10 @@ public ContextParamExtractor(String type) { } } + public ContextParamExtractor(Class type) { + this.type = type; + } + @Override public Object extractParameter(ResteasyReactiveRequestContext context) { // NOTE: Same list for CDI at ContextProducers and in EndpointIndexer.CONTEXT_TYPES diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/LoadedParameterConverter.java similarity index 80% rename from independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java rename to independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/LoadedParameterConverter.java index 9bded4a0ff5aa..3d1036fc8cbf1 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/LoadedParameterConverter.java @@ -2,7 +2,7 @@ import java.util.Objects; -public class GeneratedParameterConverter implements ParameterConverterSupplier { +public class LoadedParameterConverter implements ParameterConverterSupplier { private String className; @@ -22,7 +22,7 @@ public String getClassName() { return className; } - public GeneratedParameterConverter setClassName(String className) { + public LoadedParameterConverter setClassName(String className) { this.className = className; return this; } @@ -33,7 +33,7 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - GeneratedParameterConverter that = (GeneratedParameterConverter) o; + LoadedParameterConverter that = (LoadedParameterConverter) o; return Objects.equals(className, that.className); } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java index 19698b2664c9e..1c5dffe4dcbd5 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/OptionalConverter.java @@ -26,7 +26,9 @@ public Object convert(Object parameter) { @Override public void init(ParamConverterProviders deployment, Class rawType, Type genericType, Annotation[] annotations) { - delegate.init(deployment, rawType, genericType, annotations); + if (delegate != null) { + delegate.init(deployment, rawType, genericType, annotations); + } } public static class OptionalSupplier implements DelegatingParameterConverterSupplier { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionConstructorParameterConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionConstructorParameterConverter.java new file mode 100644 index 0000000000000..1ba691f95ab90 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionConstructorParameterConverter.java @@ -0,0 +1,31 @@ +package org.jboss.resteasy.reactive.server.core.reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverter; +import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; + +public class ReflectionConstructorParameterConverter implements ParameterConverter { + + final Constructor constructor; + + public ReflectionConstructorParameterConverter(Constructor constructor) { + this.constructor = constructor; + } + + @Override + public Object convert(Object parameter) { + try { + return constructor.newInstance(parameter); + } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { + throw new RuntimeException(e); + } + } + + @Override + public void init(ParamConverterProviders deployment, Class rawType, Type genericType, Annotation[] annotations) { + + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionConstructorParameterConverterSupplier.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionConstructorParameterConverterSupplier.java new file mode 100644 index 0000000000000..c24051e0546a1 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionConstructorParameterConverterSupplier.java @@ -0,0 +1,41 @@ +package org.jboss.resteasy.reactive.server.core.reflection; + +import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverterSupplier; + +public class ReflectionConstructorParameterConverterSupplier implements ParameterConverterSupplier { + + private String targetTypeName; + + public ReflectionConstructorParameterConverterSupplier() { + } + + public ReflectionConstructorParameterConverterSupplier(String targetTypeName) { + this.targetTypeName = targetTypeName; + } + + @Override + public ParameterConverter get() { + try { + return new ReflectionConstructorParameterConverter(Thread.currentThread().getContextClassLoader() + .loadClass(targetTypeName).getDeclaredConstructor(String.class)); + } catch (NoSuchMethodException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getClassName() { + return ReflectionValueOfParameterConverter.class.getName(); + } + + public String getTargetTypeName() { + return targetTypeName; + } + + public ReflectionConstructorParameterConverterSupplier setTargetTypeName(String targetTypeName) { + this.targetTypeName = targetTypeName; + return this; + } + +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionValueOfParameterConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionValueOfParameterConverter.java new file mode 100644 index 0000000000000..10a518432b0cc --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionValueOfParameterConverter.java @@ -0,0 +1,31 @@ +package org.jboss.resteasy.reactive.server.core.reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverter; +import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; + +public class ReflectionValueOfParameterConverter implements ParameterConverter { + + public ReflectionValueOfParameterConverter(Method valueOf) { + this.valueOf = valueOf; + } + + final Method valueOf; + + @Override + public Object convert(Object parameter) { + try { + return valueOf.invoke(null, parameter); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + @Override + public void init(ParamConverterProviders deployment, Class rawType, Type genericType, Annotation[] annotations) { + + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionValueOfParameterConverterSupplier.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionValueOfParameterConverterSupplier.java new file mode 100644 index 0000000000000..184a767797b66 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectionValueOfParameterConverterSupplier.java @@ -0,0 +1,56 @@ +package org.jboss.resteasy.reactive.server.core.reflection; + +import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverter; +import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverterSupplier; + +public class ReflectionValueOfParameterConverterSupplier implements ParameterConverterSupplier { + + private String targetTypeName; + private String methodName; + + public ReflectionValueOfParameterConverterSupplier() { + } + + public ReflectionValueOfParameterConverterSupplier(String targetTypeName) { + this.targetTypeName = targetTypeName; + this.methodName = "valueOf"; + } + + public ReflectionValueOfParameterConverterSupplier(String targetTypeName, String methodName) { + this.targetTypeName = targetTypeName; + this.methodName = methodName; + } + + @Override + public ParameterConverter get() { + try { + return new ReflectionValueOfParameterConverter(Thread.currentThread().getContextClassLoader() + .loadClass(targetTypeName).getDeclaredMethod(methodName, String.class)); + } catch (NoSuchMethodException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getClassName() { + return ReflectionValueOfParameterConverter.class.getName(); + } + + public String getTargetTypeName() { + return targetTypeName; + } + + public ReflectionValueOfParameterConverterSupplier setTargetTypeName(String targetTypeName) { + this.targetTypeName = targetTypeName; + return this; + } + + public String getMethodName() { + return methodName; + } + + public ReflectionValueOfParameterConverterSupplier setMethodName(String methodName) { + this.methodName = methodName; + return this; + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectiveContextInjectedBeanFactory.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectiveContextInjectedBeanFactory.java new file mode 100644 index 0000000000000..10a0e9672bcd7 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/reflection/ReflectiveContextInjectedBeanFactory.java @@ -0,0 +1,94 @@ +package org.jboss.resteasy.reactive.server.core.reflection; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import javax.ws.rs.core.Context; +import org.jboss.resteasy.reactive.server.core.CurrentRequestManager; +import org.jboss.resteasy.reactive.server.core.parameters.ContextParamExtractor; +import org.jboss.resteasy.reactive.spi.BeanFactory; + +public class ReflectiveContextInjectedBeanFactory implements BeanFactory { + public static final Function, BeanFactory> FACTORY = new Function, BeanFactory>() { + @Override + public BeanFactory apply(Class aClass) { + return new ReflectiveContextInjectedBeanFactory<>(aClass); + } + }; + public static final Function> STRING_FACTORY = new Function>() { + @Override + public BeanFactory apply(String name) { + try { + return new ReflectiveContextInjectedBeanFactory<>( + Thread.currentThread().getContextClassLoader().loadClass(name)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + }; + + private final Constructor constructor; + private final Map proxiesToInject; + + public ReflectiveContextInjectedBeanFactory(Class clazz) { + try { + constructor = clazz.getConstructor(); + constructor.setAccessible(true); + Class c = clazz; + proxiesToInject = new HashMap<>(); + while (c != Object.class) { + for (Field f : c.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers()) || Modifier.isFinal(f.getModifiers())) { + continue; + } + ContextParamExtractor contextParamExtractor = new ContextParamExtractor(f.getType()); + if (f.isAnnotationPresent(Context.class)) { + f.setAccessible(true); + proxiesToInject.put(f, Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), + new Class[] { f.getType() }, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object delegate = contextParamExtractor.extractParameter(CurrentRequestManager.get()); + return method.invoke(delegate, args); + } + })); + } + } + c = c.getSuperclass(); + } + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Override + public BeanInstance createInstance() { + try { + T instance = constructor.newInstance(); + for (var i : proxiesToInject.entrySet()) { + i.getKey().set(instance, i.getValue()); + } + return new BeanInstance() { + @Override + public T getInstance() { + return instance; + } + + @Override + public void close() { + + } + }; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index 51712d5cd9e07..cdf388d0c99c1 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -32,9 +32,9 @@ import org.jboss.resteasy.reactive.common.model.MethodParameter; import org.jboss.resteasy.reactive.common.model.ParameterType; import org.jboss.resteasy.reactive.common.model.ResourceClass; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.common.util.MediaTypeHelper; import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap; -import org.jboss.resteasy.reactive.common.util.ReflectionBeanFactoryCreator; import org.jboss.resteasy.reactive.common.util.ServerMediaType; import org.jboss.resteasy.reactive.common.util.types.TypeSignatureParser; import org.jboss.resteasy.reactive.server.core.DeploymentInfo; diff --git a/independent-projects/resteasy-reactive/server/vertx/pom.xml b/independent-projects/resteasy-reactive/server/vertx/pom.xml index c65cce73ecb44..6b6c920a956fd 100644 --- a/independent-projects/resteasy-reactive/server/vertx/pom.xml +++ b/independent-projects/resteasy-reactive/server/vertx/pom.xml @@ -52,6 +52,11 @@ junit-jupiter test + + org.assertj + assertj-core + test + io.rest-assured rest-assured @@ -64,6 +69,11 @@ org.jboss.spec.javax.xml.bind jboss-jaxb-api_2.3_spec + + jakarta.validation + jakarta.validation-api + test + org.jboss.logging diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/BlockingInputHandler.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/BlockingInputHandler.java similarity index 85% rename from extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/BlockingInputHandler.java rename to independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/BlockingInputHandler.java index aa933e600bf06..71c514582c583 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/BlockingInputHandler.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/BlockingInputHandler.java @@ -1,17 +1,12 @@ -package io.quarkus.resteasy.reactive.server.runtime; +package org.jboss.resteasy.reactive.server.vertx; +import io.vertx.ext.web.RoutingContext; import java.time.Duration; - import javax.ws.rs.HttpMethod; - import org.jboss.resteasy.reactive.common.util.EmptyInputStream; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.spi.RuntimeConfigurableServerRestHandler; import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; -import org.jboss.resteasy.reactive.server.vertx.VertxResteasyReactiveRequestContext; - -import io.quarkus.vertx.http.runtime.VertxInputStream; -import io.vertx.ext.web.RoutingContext; /** * Handler that reads data and sets up the input stream and blocks until the data has been read. @@ -42,7 +37,8 @@ public void handle(ResteasyReactiveRequestContext context) throws Exception { //TODO: this should not be installed for servlet VertxResteasyReactiveRequestContext vertxContext = (VertxResteasyReactiveRequestContext) context; RoutingContext routingContext = vertxContext.getContext(); - vertxContext.setInputStream(new VertxInputStream(routingContext, timeout.toMillis())); + vertxContext.setInputStream( + new VertxInputStream(routingContext, timeout.toMillis(), (VertxResteasyReactiveRequestContext) context)); } } } diff --git a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java index ddb9950bbaaff..055795164fba9 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/main/java/org/jboss/resteasy/reactive/server/vertx/VertxResteasyReactiveRequestContext.java @@ -74,6 +74,7 @@ public void handle(Void unused) { }); } }; + request.pause(); } @Override diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/ResteasyReactiveUnitTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/ResteasyReactiveUnitTest.java index 6a256f762278a..9864319e13fe9 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/ResteasyReactiveUnitTest.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/framework/ResteasyReactiveUnitTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; +import io.vertx.core.Context; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServer; import io.vertx.ext.web.Route; @@ -9,17 +10,22 @@ import java.io.Closeable; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Executor; @@ -36,29 +42,41 @@ import javax.ws.rs.ext.MessageBodyWriter; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; import org.jboss.resteasy.reactive.common.ResteasyReactiveConfig; +import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.model.ResourceClass; import org.jboss.resteasy.reactive.common.model.ResourceReader; import org.jboss.resteasy.reactive.common.model.ResourceWriter; +import org.jboss.resteasy.reactive.common.processor.AdditionalReaders; +import org.jboss.resteasy.reactive.common.processor.AdditionalWriters; import org.jboss.resteasy.reactive.common.processor.JandexUtil; +import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ApplicationScanningResult; import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult; import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveInterceptorScanner; import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner; +import org.jboss.resteasy.reactive.common.processor.scanning.SerializerScanningResult; +import org.jboss.resteasy.reactive.common.reflection.ReflectionBeanFactory; +import org.jboss.resteasy.reactive.server.core.BlockingOperationSupport; import org.jboss.resteasy.reactive.server.core.Deployment; import org.jboss.resteasy.reactive.server.core.DeploymentInfo; import org.jboss.resteasy.reactive.server.core.ServerSerialisers; +import org.jboss.resteasy.reactive.server.core.reflection.ReflectiveContextInjectedBeanFactory; import org.jboss.resteasy.reactive.server.core.startup.CustomServerRestHandlers; import org.jboss.resteasy.reactive.server.core.startup.RuntimeDeploymentManager; import org.jboss.resteasy.reactive.server.handlers.RestInitialHandler; import org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer; +import org.jboss.resteasy.reactive.server.processor.scanning.AsyncReturnTypeScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResteasyReactiveContextResolverScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResteasyReactiveExceptionMappingScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResteasyReactiveFeatureScanner; import org.jboss.resteasy.reactive.server.processor.scanning.ResteasyReactiveParamConverterScanner; import org.jboss.resteasy.reactive.server.providers.serialisers.ServerByteArrayMessageBodyHandler; import org.jboss.resteasy.reactive.server.providers.serialisers.ServerStringMessageBodyHandler; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfigurableServerRestHandler; +import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; +import org.jboss.resteasy.reactive.server.vertx.BlockingInputHandler; import org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler; import org.jboss.resteasy.reactive.server.vertx.VertxRequestContextFactory; import org.jboss.resteasy.reactive.spi.BeanFactory; @@ -78,6 +96,12 @@ public class ResteasyReactiveUnitTest implements BeforeAllCallback, AfterAllCall static { System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager"); rootLogger = LogManager.getLogManager().getLogger(""); + BlockingOperationSupport.setIoThreadDetector(new BlockingOperationSupport.IOThreadDetector() { + @Override + public boolean isBlockingAllowed() { + return !Context.isOnEventLoopThread(); + } + }); } private Path deploymentDir; @@ -211,35 +235,65 @@ public void close() throws Throwable { }); } - Index index = JandexUtil.createIndex(deploymentDir); + IndexView index = JandexUtil.createCalculatingIndex(deploymentDir); ApplicationScanningResult applicationScanningResult = ResteasyReactiveScanner.scanForApplicationClass(index, Collections.emptySet()); ResourceScanningResult resources = ResteasyReactiveScanner.scanResources(index, Collections.emptyMap(), Collections.emptyMap()); + SerializerScanningResult serializerScanningResult = ResteasyReactiveScanner.scanForSerializers(index, + applicationScanningResult); if (resources == null) { throw new RuntimeException("no JAX-RS resources found"); } + AdditionalReaders readers = new AdditionalReaders(); + AdditionalWriters writers = new AdditionalWriters(); ServerEndpointIndexer serverEndpointIndexer = new ServerEndpointIndexer.Builder() .setIndex(index) .setScannedResourcePaths(resources.getScannedResourcePaths()) .setClassLevelExceptionMappers(new HashMap<>()) + .setAdditionalReaders(readers) + .addMethodScanner(new AsyncReturnTypeScanner()) + .setAdditionalWriters(writers) .setInjectableBeans(new HashMap<>()) .setConfig(new ResteasyReactiveConfig(10000, true, true)) .setHttpAnnotationToMethod(resources.getHttpAnnotationToMethod()) + .setApplicationScanningResult(applicationScanningResult) .build(); List resourceClasses = new ArrayList<>(); List subResourceClasses = new ArrayList<>(); for (Map.Entry i : resources.getScannedResources().entrySet()) { - ResourceClass res = serverEndpointIndexer.createEndpoints(i.getValue()); - resourceClasses.add(res); + Optional res = serverEndpointIndexer.createEndpoints(i.getValue(), true); + if (res.isPresent()) { + resourceClasses.add(res.get()); + } } for (Map.Entry i : resources.getPossibleSubResources().entrySet()) { - ResourceClass res = serverEndpointIndexer.createEndpoints(i.getValue()); - subResourceClasses.add(res); + Optional res = serverEndpointIndexer.createEndpoints(i.getValue(), false); + if (res.isPresent()) { + subResourceClasses.add(res.get()); + } } ServerSerialisers serialisers = new ServerSerialisers(); + for (var i : serializerScanningResult.getWriters()) { + serialisers.addWriter(Thread.currentThread().getContextClassLoader().loadClass(i.getHandledClassName()), + new ResourceWriter() + .setMediaTypeStrings(i.getMediaTypeStrings()) + .setConstraint(i.getRuntimeType()) + .setBuiltin(i.isBuiltin()) + .setPriority(i.getPriority()) + .setFactory(new ReflectionBeanFactory<>(i.getClassName()))); + } + for (var i : serializerScanningResult.getReaders()) { + serialisers.addReader(Thread.currentThread().getContextClassLoader().loadClass(i.getHandledClassName()), + new ResourceReader() + .setMediaTypeStrings(i.getMediaTypeStrings()) + .setConstraint(i.getRuntimeType()) + .setBuiltin(i.isBuiltin()) + .setPriority(i.getPriority()) + .setFactory(new ReflectionBeanFactory<>(i.getClassName()))); + } serialisers.addWriter(String.class, new ResourceWriter() .setMediaTypeStrings(Collections.singletonList(MediaType.WILDCARD)) .setFactory(new BeanFactory>() { @@ -279,19 +333,26 @@ public void close() { DeploymentInfo info = new DeploymentInfo() .setApplicationPath("/") .setConfig(new ResteasyReactiveConfig()) - .setFeatures(ResteasyReactiveFeatureScanner.createFeatures(index, applicationScanningResult)) + .setFeatures(ResteasyReactiveFeatureScanner.createFeatures(index, applicationScanningResult, + ReflectiveContextInjectedBeanFactory.STRING_FACTORY)) .setInterceptors( - ResteasyReactiveInterceptorScanner.createResourceInterceptors(index, applicationScanningResult)) - .setDynamicFeatures(ResteasyReactiveFeatureScanner.createDynamicFeatures(index, applicationScanningResult)) + ResteasyReactiveInterceptorScanner.createResourceInterceptors(index, applicationScanningResult, + ReflectiveContextInjectedBeanFactory.STRING_FACTORY)) + .setDynamicFeatures(ResteasyReactiveFeatureScanner.createDynamicFeatures(index, applicationScanningResult, + ReflectiveContextInjectedBeanFactory.STRING_FACTORY)) .setParamConverterProviders( - ResteasyReactiveParamConverterScanner.createParamConverters(index, applicationScanningResult)) + ResteasyReactiveParamConverterScanner.createParamConverters(index, applicationScanningResult, + ReflectiveContextInjectedBeanFactory.STRING_FACTORY)) .setSerialisers(serialisers) .setExceptionMapping( - ResteasyReactiveExceptionMappingScanner.createExceptionMappers(index, applicationScanningResult)) + ResteasyReactiveExceptionMappingScanner.createExceptionMappers(index, applicationScanningResult, + ReflectiveContextInjectedBeanFactory.STRING_FACTORY)) .setResourceClasses(resourceClasses) .setCtxResolvers( - ResteasyReactiveContextResolverScanner.createContextResolvers(index, applicationScanningResult)) + ResteasyReactiveContextResolverScanner.createContextResolvers(index, applicationScanningResult, + ReflectiveContextInjectedBeanFactory.STRING_FACTORY)) .setLocatableResourceClasses(subResourceClasses) + .setFactoryCreator(ReflectiveContextInjectedBeanFactory.FACTORY) .setApplicationSupplier(new Supplier() { @Override public Application get() { @@ -311,13 +372,103 @@ public Application get() { } } }); + setupSerializers(readers, writers, serialisers); + String path = "/"; + if (applicationScanningResult.getSelectedAppClass() != null) { + var pathAn = applicationScanningResult.getSelectedAppClass() + .classAnnotation(ResteasyReactiveDotNames.APPLICATION_PATH); + if (pathAn != null) { + path = pathAn.value().asString(); + if (!path.startsWith("/")) { + path = "/" + path; + } + } + } RuntimeDeploymentManager runtimeDeploymentManager = new RuntimeDeploymentManager(info, () -> executor, - new CustomServerRestHandlers(null), - closeable -> closeTasks.add(closeable), new VertxRequestContextFactory(), ThreadSetupAction.NOOP, "/"); + new CustomServerRestHandlers(BlockingInputHandler::new), + closeable -> closeTasks.add(closeable), new VertxRequestContextFactory(), ThreadSetupAction.NOOP, path); Deployment deployment = runtimeDeploymentManager.deploy(); RestInitialHandler initialHandler = new RestInitialHandler(deployment); - router.route().handler(new ResteasyReactiveVertxHandler(initialHandler)); + List runtimeConfigurableServerRestHandlers = deployment + .getRuntimeConfigurableServerRestHandlers(); + for (RuntimeConfigurableServerRestHandler handler : runtimeConfigurableServerRestHandlers) { + handler.configure(new RuntimeConfiguration() { + @Override + public Duration readTimeout() { + return Duration.of(1, ChronoUnit.MINUTES); + } + + @Override + public Body body() { + return new Body() { + @Override + public boolean deleteUploadedFilesOnEnd() { + return true; + } + + @Override + public String uploadsDirectory() { + return System.getProperty("java.io.tmpdir"); + } + + @Override + public Charset defaultCharset() { + return StandardCharsets.UTF_8; + } + }; + } + + @Override + public Limits limits() { + return new Limits() { + @Override + public Optional maxBodySize() { + return Optional.empty(); + } + + @Override + public long maxFormAttributeSize() { + return Long.MAX_VALUE; + } + }; + } + }); + } + + ResteasyReactiveVertxHandler handler = new ResteasyReactiveVertxHandler(initialHandler); + router.route(path).handler(handler); + if (path.endsWith("/")) { + router.route(path + "*").handler(handler); + } else { + router.route(path + "/*").handler(handler); + } + + } + + private void setupSerializers(AdditionalReaders readers, AdditionalWriters writers, ServerSerialisers serialisers) { + for (var i : writers.get()) { + serialisers.addWriter(i.getEntityClass(), + new ResourceWriter().setFactory(new ReflectiveContextInjectedBeanFactory(i.getHandlerClass())) + .setConstraint(i.getConstraint()).setMediaTypeStrings(Collections.singletonList(i.getMediaType()))); + } + for (var i : readers.get()) { + serialisers.addReader(i.getEntityClass(), + new ResourceReader().setFactory(new ReflectiveContextInjectedBeanFactory(i.getHandlerClass())) + .setConstraint(i.getConstraint()).setMediaTypeStrings(Collections.singletonList(i.getMediaType()))); + } + for (Serialisers.BuiltinReader builtinReader : ServerSerialisers.BUILTIN_READERS) { + serialisers.addReader(builtinReader.entityClass, + new ResourceReader().setFactory(new ReflectiveContextInjectedBeanFactory(builtinReader.readerClass)) + .setConstraint(builtinReader.constraint) + .setMediaTypeStrings(Collections.singletonList(builtinReader.mediaType)).setBuiltin(true)); + } + for (Serialisers.BuiltinWriter builtinReader : ServerSerialisers.BUILTIN_WRITERS) { + serialisers.addWriter(builtinReader.entityClass, + new ResourceWriter().setFactory(new ReflectiveContextInjectedBeanFactory(builtinReader.writerClass)) + .setConstraint(builtinReader.constraint) + .setMediaTypeStrings(Collections.singletonList(builtinReader.mediaType)).setBuiltin(true)); + } } @Override diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationPathTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationPathTest.java new file mode 100644 index 0000000000000..b00f0f6297752 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationPathTest.java @@ -0,0 +1,48 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.restassured.RestAssured; +import java.util.function.Supplier; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ApplicationPathTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TestApplication.class, HelloResource.class); + } + }); + + @Test + public void helloTest() { + RestAssured.get("/app/hello") + .then().body(Matchers.equalTo("hello")); + } + + @ApplicationPath("app") + public static class TestApplication extends Application { + + } + + @Path("hello") + public static class HelloResource { + + @GET + public String hello() { + return "hello"; + } + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationTest.java new file mode 100644 index 0000000000000..1b080409a1228 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationTest.java @@ -0,0 +1,238 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.container.DynamicFeature; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * The integration test allowing to ensure that we can rely on {@link Application#getClasses()} to specify explicitly + * the classes to use for the application. + */ +class ApplicationTest { + + @RegisterExtension + static ResteasyReactiveUnitTest runner = new ResteasyReactiveUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses( + ResourceTest1.class, ResourceTest2.class, ResponseFilter1.class, ResponseFilter2.class, + ResponseFilter3.class, ResponseFilter4.class, ResponseFilter5.class, ResponseFilter6.class, + Feature1.class, Feature2.class, DynamicFeature1.class, DynamicFeature2.class, + ExceptionMapper1.class, ExceptionMapper2.class, AppTest.class)); + + @DisplayName("Should access to ok of resource 1 and provide a response with the expected headers") + @Test + void should_call_ok_of_resource_1() { + when() + .get("/rt-1/ok") + .then() + .header("X-RF-1", notNullValue()) + .header("X-RF-2", nullValue()) + .header("X-RF-3", notNullValue()) + .header("X-RF-4", nullValue()) + .header("X-RF-5", notNullValue()) + .header("X-RF-6", nullValue()) + .body(Matchers.is("ok1")); + } + + @DisplayName("Should access to ko of resource 1 and call the expected exception mapper") + @Test + void should_call_ko_of_resource_1() { + when() + .get("/rt-1/ko") + .then() + .statusCode(Response.Status.SERVICE_UNAVAILABLE.getStatusCode()); + } + + @DisplayName("Should access to ok of resource 1 and provide a response with the expected headers") + @Test + void should_not_call_ok_of_resource_2() { + when() + .get("/rt-2/ok") + .then() + .statusCode(Response.Status.SERVICE_UNAVAILABLE.getStatusCode()); + } + + @Path("rt-1") + public static class ResourceTest1 { + + @GET + @Path("ok") + public String ok() { + return "ok1"; + } + + @GET + @Path("ko") + public String ko() { + throw new UnsupportedOperationException(); + } + } + + @Path("rt-2") + public static class ResourceTest2 { + + @GET + @Path("ok") + public String ok() { + return "ok2"; + } + } + + @Provider + public static class ResponseFilter1 implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("X-RF-1", "Value"); + } + } + + @Provider + public static class ResponseFilter2 implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("X-RF-2", "Value"); + } + } + + @Provider + public static class ResponseFilter3 implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("X-RF-3", "Value"); + } + } + + @Provider + public static class ResponseFilter4 implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("X-RF-4", "Value"); + } + } + + @Provider + public static class ResponseFilter5 implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("X-RF-5", "Value"); + } + } + + @Provider + public static class ResponseFilter6 implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("X-RF-6", "Value"); + } + } + + @Provider + public static class Feature1 implements Feature { + + @Override + public boolean configure(FeatureContext context) { + context.register(ResponseFilter3.class); + return true; + } + } + + @Provider + public static class Feature2 implements Feature { + + @Override + public boolean configure(FeatureContext context) { + context.register(ResponseFilter4.class); + return true; + } + } + + @Provider + public static class ExceptionMapper1 implements ExceptionMapper { + + @Override + public Response toResponse(RuntimeException exception) { + return Response.status(Response.Status.SERVICE_UNAVAILABLE.getStatusCode()).build(); + } + } + + @Provider + public static class ExceptionMapper2 implements ExceptionMapper { + + @Override + public Response toResponse(UnsupportedOperationException exception) { + return Response.status(Response.Status.NOT_IMPLEMENTED.getStatusCode()).build(); + } + } + + @Provider + public static class DynamicFeature1 implements DynamicFeature { + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + context.register(ResponseFilter5.class); + } + } + + @Provider + public static class DynamicFeature2 implements DynamicFeature { + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + context.register(ResponseFilter6.class); + } + } + + public static class AppTest extends Application { + + @Override + public Set> getClasses() { + return new HashSet<>( + Arrays.asList( + ResourceTest1.class, Feature1.class, ExceptionMapper1.class)); + } + + @Override + public Set getSingletons() { + return new HashSet<>( + Arrays.asList( + new ResponseFilter1(), new DynamicFeature1())); + } + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationWithBlockingTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationWithBlockingTest.java new file mode 100644 index 0000000000000..e3f6311f19fb0 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ApplicationWithBlockingTest.java @@ -0,0 +1,84 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.restassured.RestAssured; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.NonBlocking; +import java.util.function.Supplier; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Application; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ApplicationWithBlockingTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(BlockingApplication.class, ThreadNameResource.class); + } + }); + + @Test + public void test() { + RestAssured.get("/tname/blocking") + .then().body(Matchers.containsString("pool"), Matchers.not(Matchers.containsString("loop"))); + + RestAssured.get("/tname/nonblocking") + .then().body(Matchers.containsString("loop"), Matchers.not(Matchers.containsString("pool"))); + + RestAssured.get("/tname2/blocking") + .then().body(Matchers.containsString("pool"), Matchers.not(Matchers.containsString("loop"))); + + RestAssured.get("/tname2/nonblocking") + .then().body(Matchers.containsString("loop"), Matchers.not(Matchers.containsString("pool"))); + } + + @Blocking + public static class BlockingApplication extends Application { + + } + + @Path("tname") + public static class ThreadNameResource { + + @Path("blocking") + @GET + public String threadName() { + return Thread.currentThread().getName(); + } + + @NonBlocking + @Path("nonblocking") + @GET + public String nonBlocking() { + return Thread.currentThread().getName(); + } + } + + @Blocking // this should have no effect + @Path("tname2") + public static class ThreadNameResourceWithBlocking { + + @Path("blocking") + @GET + public String threadName() { + return Thread.currentThread().getName(); + } + + @NonBlocking + @Path("nonblocking") + @GET + public String nonBlocking() { + return Thread.currentThread().getName(); + } + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Bar.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Bar.java new file mode 100644 index 0000000000000..977fab5136a73 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Bar.java @@ -0,0 +1,13 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.ws.rs.NameBinding; + +@NameBinding +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface Bar { +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BeanParamSubClass.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BeanParamSubClass.java new file mode 100644 index 0000000000000..48922b257e5bc --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BeanParamSubClass.java @@ -0,0 +1,14 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.QueryParam; +import org.junit.jupiter.api.Assertions; + +public class BeanParamSubClass extends BeanParamSuperClass { + @QueryParam("query") + String queryInSubClass; + + public void check(String path) { + super.check(path); + Assertions.assertEquals("one-query", queryInSubClass); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BeanParamSuperClass.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BeanParamSuperClass.java new file mode 100644 index 0000000000000..46ef55aa67102 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BeanParamSuperClass.java @@ -0,0 +1,87 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.util.List; +import javax.ws.rs.BeanParam; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Assertions; + +public class BeanParamSuperClass { + @QueryParam("query") + String query; + + @QueryParam("query") + private String privateQuery; + + @QueryParam("query") + protected String protectedQuery; + + @QueryParam("query") + public String publicQuery; + + @HeaderParam("header") + String header; + + @Context + UriInfo uriInfo; + + @BeanParam + OtherBeanParam otherBeanParam; + + @QueryParam("queryList") + List queryList; + + @QueryParam("query") + ParameterWithFromString parameterWithFromString; + + @QueryParam("missing") + String missing; + + @DefaultValue("there") + @QueryParam("missing") + String missingWithDefaultValue; + + @QueryParam("missing") + ParameterWithFromString missingParameterWithFromString; + + @DefaultValue("there") + @QueryParam("missing") + ParameterWithFromString missingParameterWithFromStringAndDefaultValue; + + @QueryParam("int") + int primitiveParam; + + @QueryParam("missing") + int missingPrimitiveParam; + + @DefaultValue("42") + @QueryParam("missing") + int missingPrimitiveParamWithDefaultValue; + + public void check(String path) { + Assertions.assertEquals("one-query", query); + Assertions.assertEquals("one-query", privateQuery); + Assertions.assertEquals("one-query", protectedQuery); + Assertions.assertEquals("one-query", publicQuery); + Assertions.assertEquals("one-header", header); + Assertions.assertNotNull(uriInfo); + Assertions.assertEquals(path, uriInfo.getPath()); + Assertions.assertNotNull(otherBeanParam); + otherBeanParam.check(path); + Assertions.assertNotNull(queryList); + Assertions.assertEquals("one", queryList.get(0)); + Assertions.assertEquals("two", queryList.get(1)); + Assertions.assertNotNull(parameterWithFromString); + Assertions.assertEquals("ParameterWithFromString[val=one-query]", parameterWithFromString.toString()); + Assertions.assertNull(missing); + Assertions.assertEquals("there", missingWithDefaultValue); + Assertions.assertNull(missingParameterWithFromString); + Assertions.assertEquals("ParameterWithFromString[val=there]", missingParameterWithFromStringAndDefaultValue.toString()); + Assertions.assertEquals(666, primitiveParam); + Assertions.assertEquals(0, missingPrimitiveParam); + Assertions.assertEquals(42, missingPrimitiveParamWithDefaultValue); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BlockingWithFilterTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BlockingWithFilterTest.java new file mode 100644 index 0000000000000..098e070579ed5 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/BlockingWithFilterTest.java @@ -0,0 +1,66 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.restassured.RestAssured; +import io.smallrye.common.annotation.Blocking; +import java.util.function.Supplier; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import org.jboss.resteasy.reactive.server.ServerRequestFilter; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +@Disabled("filters") +public class BlockingWithFilterTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TestFilter.class, TestResource.class); + } + }); + + @Test + public void requestFilterTest() { + String response = RestAssured.get("/test/request") + .then().statusCode(200).contentType("text/plain").extract().body().asString(); + String[] parts = response.split("/"); + assertEquals(2, parts.length); + assertEquals(parts[0], parts[1]); + assertFalse(parts[0].contains("eventloop")); + assertTrue(parts[0].contains("executor")); + } + + public static class TestFilter { + + @ServerRequestFilter + public void filter(ContainerRequestContext requestContext) { + requestContext.getHeaders().add("filter-thread", Thread.currentThread().getName()); + } + + } + + @Path("/test") + public static class TestResource { + + @Blocking + @Path("/request") + @GET + public String get(@Context HttpHeaders headers) { + return headers.getHeaderString("filter-thread") + "/" + Thread.currentThread().getName(); + } + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/DynamicFeatureRequestFilterWithLowPriority.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/DynamicFeatureRequestFilterWithLowPriority.java new file mode 100644 index 0000000000000..ccd08d1c5429d --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/DynamicFeatureRequestFilterWithLowPriority.java @@ -0,0 +1,17 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; + +@Priority(Priorities.USER + 1000) +public class DynamicFeatureRequestFilterWithLowPriority implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("feature-filter-request"); + requestContext.getHeaders().putSingle("feature-filter-request", previousFilterHeaderValue + "-low"); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureMappedException.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureMappedException.java new file mode 100644 index 0000000000000..a06d0e8aca619 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureMappedException.java @@ -0,0 +1,4 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +public class FeatureMappedException extends RuntimeException { +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureMappedExceptionMapper.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureMappedExceptionMapper.java new file mode 100644 index 0000000000000..48335027382d7 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureMappedExceptionMapper.java @@ -0,0 +1,13 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.Serializable; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +public class FeatureMappedExceptionMapper implements Cloneable, ExceptionMapper, Serializable { + + @Override + public Response toResponse(FeatureMappedException exception) { + return Response.status(667).build(); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureRequestFilterWithHighestPriority.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureRequestFilterWithHighestPriority.java new file mode 100644 index 0000000000000..4a1feef73d52d --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureRequestFilterWithHighestPriority.java @@ -0,0 +1,16 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; + +@Priority(Priorities.AUTHENTICATION) +public class FeatureRequestFilterWithHighestPriority implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + requestContext.getHeaders().add("feature-filter-request", "authentication"); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureRequestFilterWithNormalPriority.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureRequestFilterWithNormalPriority.java new file mode 100644 index 0000000000000..4a7457b5804ec --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureRequestFilterWithNormalPriority.java @@ -0,0 +1,14 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; + +public class FeatureRequestFilterWithNormalPriority implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("feature-filter-request"); + requestContext.getHeaders().putSingle("feature-filter-request", previousFilterHeaderValue + "-default"); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureResponseFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureResponseFilter.java new file mode 100644 index 0000000000000..2a1f0f50a3300 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FeatureResponseFilter.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; + +public class FeatureResponseFilter implements ContainerResponseFilter { + + private final String headerName; + private final String headerValue; + + public FeatureResponseFilter(String headerName, String headerValue) { + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + responseContext.getHeaders().add(headerName, headerValue); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FieldInjectedResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FieldInjectedResource.java new file mode 100644 index 0000000000000..8348c36369bc8 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FieldInjectedResource.java @@ -0,0 +1,52 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Assertions; + +@Path("injection") +public class FieldInjectedResource { + + @QueryParam("query") + String query; + + @HeaderParam("header") + String header; + + @Context + UriInfo uriInfo; + + @BeanParam + SimpleBeanParam beanParam; + + @Path("field") + @GET + public String field() { + checkInjections("/injection/field", query, header, uriInfo, beanParam); + return "OK"; + } + + protected void checkInjections(String path, String query, String header, UriInfo uriInfo, SimpleBeanParam beanParam) { + Assertions.assertEquals("one-query", query); + Assertions.assertEquals("one-header", header); + Assertions.assertNotNull(uriInfo); + Assertions.assertEquals(path, uriInfo.getPath()); + Assertions.assertNotNull(beanParam); + beanParam.check(path); + } + + @Path("param") + @GET + public String param(@QueryParam("query") String query, + @HeaderParam("header") String header, + @Context UriInfo uriInfo, + @BeanParam SimpleBeanParam beanParam) { + checkInjections("/injection/param", query, header, uriInfo, beanParam); + return "OK"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FieldInjectedSubClassResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FieldInjectedSubClassResource.java new file mode 100644 index 0000000000000..dacc5a575c770 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/FieldInjectedSubClassResource.java @@ -0,0 +1,52 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.validation.constraints.NotNull; +import javax.ws.rs.BeanParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Assertions; + +@Path("injection-subclass") +public class FieldInjectedSubClassResource extends FieldInjectedResource { + + @QueryParam("query") + String queryInSubClass; + + @Context + UriInfo uriInfoInSubClass; + + @BeanParam + BeanParamSubClass beanParamSubClass; + + @Path("field2") + @GET + public String field() { + checkInjections("/injection-subclass/field2", query, header, uriInfo, beanParam, queryInSubClass, uriInfoInSubClass, + beanParamSubClass); + return "OK"; + } + + private void checkInjections(String path, String query, String header, UriInfo uriInfo, SimpleBeanParam beanParam, + String queryInSubClass, UriInfo uriInfoInSubClass, BeanParamSubClass beanParamSubClass) { + super.checkInjections(path, query, header, uriInfo, beanParam); + Assertions.assertEquals("one-query", queryInSubClass); + Assertions.assertNotNull(uriInfoInSubClass); + Assertions.assertEquals(path, uriInfoInSubClass.getPath()); + Assertions.assertNotNull(beanParamSubClass); + beanParamSubClass.check(path); + } + + @Path("param2") + @GET + public String param(@QueryParam("query") String query, + @HeaderParam("header") String header, + @Context UriInfo uriInfo, + @BeanParam @NotNull BeanParamSubClass beanParam) { + checkInjections("/injection-subclass/param2", query, header, uriInfo, this.beanParam, query, uriInfo, beanParam); + return "OK"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Foo.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Foo.java new file mode 100644 index 0000000000000..2edc877ff8df4 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Foo.java @@ -0,0 +1,13 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.ws.rs.NameBinding; + +@NameBinding +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface Foo { +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/HeaderParamResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/HeaderParamResource.java new file mode 100644 index 0000000000000..0545e4a35fd88 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/HeaderParamResource.java @@ -0,0 +1,20 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; + +@Path("/ctor-header") +public class HeaderParamResource { + + private final String headerParamValue; + + public HeaderParamResource(@HeaderParam("h1") String headerParamValue) { + this.headerParamValue = headerParamValue; + } + + @GET + public String get() { + return headerParamValue; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/HelloService.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/HelloService.java new file mode 100644 index 0000000000000..3a59559cb8a6d --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/HelloService.java @@ -0,0 +1,18 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.vertx.ext.web.RoutingContext; +import java.util.Objects; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +@RequestScoped +public class HelloService { + + @Inject + RoutingContext context; + + public String sayHello() { + Objects.requireNonNull(context); + return "Hello"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceResource.java new file mode 100644 index 0000000000000..b2f0a2afcdffc --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceResource.java @@ -0,0 +1,14 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("iface") +public interface InterfaceResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + String hello(); +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceResourceImpl.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceResourceImpl.java new file mode 100644 index 0000000000000..315656fa97bd3 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceResourceImpl.java @@ -0,0 +1,9 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +public class InterfaceResourceImpl implements InterfaceResource { + + @Override + public String hello() { + return "Hello"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceWithImplTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceWithImplTest.java new file mode 100644 index 0000000000000..855fcb4172ea4 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/InterfaceWithImplTest.java @@ -0,0 +1,67 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.restassured.RestAssured; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.NonBlocking; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.core.BlockingOperationSupport; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class InterfaceWithImplTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Greeting.class, GreetingImpl.class)); + + @Test + public void test() { + RestAssured.get("/hello/greeting/universe") + .then().body(Matchers.equalTo("name: universe / blocking: true")); + + RestAssured.get("/hello/greeting2/universe") + .then().body(Matchers.equalTo("name: universe / blocking: false")); + } + + @Path("/hello") + @NonBlocking + public interface Greeting { + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/greeting/{name}") + String greeting(String name); + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/greeting2/{name}") + String greeting2(String name); + } + + public static class GreetingImpl implements Greeting { + + @Override + @Blocking + public String greeting(String name) { + return resultString(name); + } + + @Override + public String greeting2(String name) { + return resultString(name); + } + + private String resultString(String name) { + return "name: " + name + " / blocking: " + BlockingOperationSupport.isBlockingAllowed(); + } + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateCustomParamConverterProviderTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateCustomParamConverterProviderTest.java new file mode 100644 index 0000000000000..d08b8bd95d578 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateCustomParamConverterProviderTest.java @@ -0,0 +1,82 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.restassured.RestAssured; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; +import javax.ws.rs.ext.Provider; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class LocalDateCustomParamConverterProviderTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class, CustomLocalDateParamConverterProvider.class, + CustomLocalDateParamConverter.class)); + + @Test + public void localDateAsQueryParam() { + RestAssured.get("/hello?date=1981-W38-6") + .then().body(Matchers.equalTo("hello#1981-09-19")); + } + + @Test + public void localDateAsPathParam() { + RestAssured.get("/hello/1995-W38-4") + .then().body(Matchers.equalTo("hello@1995-09-21")); + } + + @Path("hello") + public static class HelloResource { + + @GET + public String helloQuery(@QueryParam("date") LocalDate date) { + return "hello#" + date; + } + + @GET + @Path("{date}") + public String helloPath(@PathParam("date") LocalDate date) { + return "hello@" + date; + } + } + + public static class CustomLocalDateParamConverter implements ParamConverter { + + @Override + public LocalDate fromString(String value) { + return LocalDate.parse(value, DateTimeFormatter.ISO_WEEK_DATE); + } + + @Override + public String toString(LocalDate value) { + return value.format(DateTimeFormatter.ISO_WEEK_DATE); + } + } + + @Provider + public static class CustomLocalDateParamConverterProvider implements ParamConverterProvider { + + @SuppressWarnings("unchecked") + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (rawType == LocalDate.class) { + return (ParamConverter) new CustomLocalDateParamConverter(); + } + return null; + } + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java new file mode 100644 index 0000000000000..96b16554e76a0 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java @@ -0,0 +1,49 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.restassured.RestAssured; +import java.time.LocalDate; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class LocalDateParamTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class)); + + @Test + public void localDateAsQueryParam() { + RestAssured.get("/hello?date=1984-08-08") + .then().body(Matchers.equalTo("hello#1984-08-08")); + } + + @Test + public void localDateAsPathParam() { + RestAssured.get("/hello/1995-09-21") + .then().body(Matchers.equalTo("hello@1995-09-21")); + } + + @Path("hello") + public static class HelloResource { + + @GET + public String helloQuery(@QueryParam("date") LocalDate date) { + return "hello#" + date; + } + + @GET + @Path("{date}") + public String helloPath(@PathParam("date") LocalDate date) { + return "hello@" + date; + } + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameter.java new file mode 100644 index 0000000000000..da5c3b201b327 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameter.java @@ -0,0 +1,18 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +public class MyParameter { + private String value; + + public MyParameter(String str) { + this.value = "WRONG CONSTRUCTOR"; + } + + public MyParameter(String str, String str2) { + this.value = str + str2; + } + + @Override + public String toString() { + return value; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameterConverter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameterConverter.java new file mode 100644 index 0000000000000..ca395b50dbfac --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameterConverter.java @@ -0,0 +1,17 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.ext.ParamConverter; + +public class MyParameterConverter implements ParamConverter { + + @Override + public MyParameter fromString(String value) { + return new MyParameter(value, value); + } + + @Override + public String toString(MyParameter value) { + return value.toString(); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameterProvider.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameterProvider.java new file mode 100644 index 0000000000000..a45687f698aae --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/MyParameterProvider.java @@ -0,0 +1,20 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; +import javax.ws.rs.ext.Provider; + +@Provider +public class MyParameterProvider implements ParamConverterProvider { + + @SuppressWarnings("unchecked") + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (rawType == MyParameter.class) + return (ParamConverter) new MyParameterConverter(); + return null; + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/NewParamsRestResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/NewParamsRestResource.java new file mode 100644 index 0000000000000..2d0db9c691d48 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/NewParamsRestResource.java @@ -0,0 +1,108 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.smallrye.common.annotation.Blocking; +import java.util.Optional; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.container.ResourceContext; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Configuration; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.Providers; +import javax.ws.rs.sse.Sse; +import javax.ws.rs.sse.SseEventSink; +import org.jboss.resteasy.reactive.RestCookie; +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.resteasy.reactive.RestHeader; +import org.jboss.resteasy.reactive.RestMatrix; +import org.jboss.resteasy.reactive.RestPath; +import org.jboss.resteasy.reactive.RestQuery; +import org.jboss.resteasy.reactive.server.SimpleResourceInfo; +import org.jboss.resteasy.reactive.server.core.BlockingOperationSupport; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +import org.junit.jupiter.api.Assertions; + +@Path("/new-params/{klass}/{regex:[^/]+}") +public class NewParamsRestResource { + + @GET + @Path("{id}") + public String get(String klass, String regex, String id) { + return "GET:" + klass + ":" + regex + ":" + id; + } + + @POST + @Path("params/{p}") + public String params(@RestPath String p, + @RestQuery String q, + @RestQuery Optional q2, + @RestQuery Optional q3, + @RestHeader int h, + @RestHeader String xMyHeader, + @RestHeader("Test-Header-Param") String testHeaderParam, + @RestHeader("") String paramEmpty, + @RestForm String f, + @RestMatrix String m, + @RestCookie String c) { + return "params: p: " + p + ", q: " + q + ", h: " + h + ", xMyHeader: " + xMyHeader + ", testHeaderParam: " + + testHeaderParam + ", paramEmpty: " + + paramEmpty + ", f: " + f + ", m: " + m + ", c: " + + c + ", q2: " + + q2.orElse("empty") + ", q3: " + q3.orElse(-1); + } + + @Blocking + @POST + @Path("form-blocking") + public String formBlocking(@RestForm String f) { + if (!BlockingOperationSupport.isBlockingAllowed()) { + throw new RuntimeException("should not have dispatched"); + } + return f; + } + + @GET + @Path("context") + public String context(// Spec: + UriInfo uriInfo, + HttpHeaders headers, + Request request, + SecurityContext securityContext, + Providers providers, + ResourceContext resourceContext, + Configuration configuration, + // Extras + ResourceInfo resourceInfo, + SimpleResourceInfo simplifiedResourceInfo, + ServerRequestContext restContext) { + Assertions.assertNotNull(uriInfo); + Assertions.assertNotNull(headers); + Assertions.assertNotNull(request); + Assertions.assertNotNull(securityContext); + Assertions.assertNotNull(providers); + Assertions.assertNotNull(resourceContext); + Assertions.assertNotNull(configuration); + Assertions.assertNotNull(resourceInfo); + Assertions.assertNotNull(simplifiedResourceInfo); + Assertions.assertNotNull(restContext); + return "OK"; + } + + @GET + @Path("sse") + @Produces(MediaType.SERVER_SENT_EVENTS) + public void eventStream(SseEventSink eventSink, + Sse sse) { + Assertions.assertNotNull(eventSink); + Assertions.assertNotNull(sse); + try (SseEventSink sink = eventSink) { + eventSink.send(sse.newEvent("OK")); + } + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/OtherBeanParam.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/OtherBeanParam.java new file mode 100644 index 0000000000000..54f8a8a9d5945 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/OtherBeanParam.java @@ -0,0 +1,25 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.HeaderParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Assertions; + +public class OtherBeanParam { + @QueryParam("query") + String query; + + @HeaderParam("header") + String header; + + @Context + UriInfo uriInfo; + + public void check(String path) { + Assertions.assertEquals("one-query", query); + Assertions.assertEquals("one-header", header); + Assertions.assertNotNull(uriInfo); + Assertions.assertEquals(path, uriInfo.getPath()); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ParameterWithFromString.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ParameterWithFromString.java new file mode 100644 index 0000000000000..b6248cdce6111 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ParameterWithFromString.java @@ -0,0 +1,19 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +public class ParameterWithFromString { + + private String val; + + public ParameterWithFromString(String val) { + this.val = val; + } + + public static ParameterWithFromString fromString(String val) { + return new ParameterWithFromString(val); + } + + @Override + public String toString() { + return "ParameterWithFromString[val=" + val + "]"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Person.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Person.java new file mode 100644 index 0000000000000..21a167d2a6df0 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/Person.java @@ -0,0 +1,27 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.validation.constraints.NotBlank; + +public class Person { + + private String first; + + @NotBlank(message = "Title cannot be blank") + private String last; + + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/PortProviderUtil.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/PortProviderUtil.java new file mode 100644 index 0000000000000..2796f6aef3d5f --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/PortProviderUtil.java @@ -0,0 +1,60 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.net.URI; + +/** + * Utility class that provides a port number for the Resteasy embedded container. + */ +public class PortProviderUtil { + + /** + * Create a URI for the provided path, using the configured port + * + * @param path the request path + * @return a full URI + */ + public static URI createURI(String path) { + return URI.create(generateURL(path)); + } + + /** + * Generate a base URL incorporating the configured port. + * + * @return a full URL + */ + public static String generateBaseUrl() { + return generateURL(""); + } + + /** + * Generate a URL with port, hostname + * + * @param path the path + * @return a full URL + */ + public static String generateURL(String path) { + return "http://localhost:8080" + path; + } + + public static String generateURL(String path, String ignore) { + return generateURL(path); + } + + /** + * Get port. + * + * @return The port number + */ + public static int getPort() { + return 8080; + } + + /** + * Get host IP. + * + * @return The host IP + */ + public static String getHost() { + return "localhost"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/QueryParamResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/QueryParamResource.java new file mode 100644 index 0000000000000..b92e76bfa1885 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/QueryParamResource.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; + +@Path("/ctor-query") +public class QueryParamResource { + + private final String queryParamValue; + + public QueryParamResource(HelloService helloService, @QueryParam("q1") String queryParamValue, @Context UriInfo uriInfo) { + this.queryParamValue = queryParamValue; + } + + @GET + public String get() { + return queryParamValue; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RawListQueryParamTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RawListQueryParamTest.java new file mode 100644 index 0000000000000..df2a830a0c79b --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RawListQueryParamTest.java @@ -0,0 +1,53 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.restassured.RestAssured; +import java.util.List; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.RestQuery; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class RawListQueryParamTest { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(HelloResource.class)); + + @Test + public void noQueryParams() { + RestAssured.get("/hello") + .then().statusCode(200).body(Matchers.equalTo("hello world")); + } + + @Test + public void singleQueryParam() { + RestAssured.get("/hello?name=foo") + .then().statusCode(200).body(Matchers.equalTo("hello foo")); + } + + @Test + public void multipleQueryParams() { + RestAssured.get("/hello?name=foo&name=bar") + .then().statusCode(200).body(Matchers.equalTo("hello foo,bar")); + } + + @Path("hello") + public static class HelloResource { + + @GET + @SuppressWarnings({ "rawtypes", "unchecked" }) + public String hello(@RestQuery("name") List names) { + if (names.isEmpty()) { + return "hello world"; + } + return "hello " + String.join(",", names); + } + + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ResourceInfoInjectingFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ResourceInfoInjectingFilter.java new file mode 100644 index 0000000000000..4f2a05cb5dbee --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/ResourceInfoInjectingFilter.java @@ -0,0 +1,20 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; + +@Provider +public class ResourceInfoInjectingFilter implements ContainerRequestFilter { + + @Context + ResourceInfo resourceInfo; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + requestContext.getHeaders().add("method-name", resourceInfo.getResourceMethod().getName()); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RootAResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RootAResource.java new file mode 100644 index 0000000000000..fccba53949602 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RootAResource.java @@ -0,0 +1,15 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/") +public class RootAResource { + + @GET + @Path("a") + public String a() { + return "a"; + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RootBResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RootBResource.java new file mode 100644 index 0000000000000..7017b1d2ebd51 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/RootBResource.java @@ -0,0 +1,15 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/") +public class RootBResource { + + @GET + @Path("b") + public String b() { + return "b"; + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleBeanParam.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleBeanParam.java new file mode 100644 index 0000000000000..31ab8ffaf74fa --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleBeanParam.java @@ -0,0 +1,95 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.util.List; +import javax.ws.rs.BeanParam; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import org.junit.jupiter.api.Assertions; + +public class SimpleBeanParam { + @QueryParam("query") + String query; + + @QueryParam("query") + private String privateQuery; + + @QueryParam("query") + protected String protectedQuery; + + @QueryParam("query") + public String publicQuery; + + @HeaderParam("header") + String header; + + @Context + UriInfo uriInfo; + + @BeanParam + OtherBeanParam otherBeanParam; + + @QueryParam("queryList") + List queryList; + + @QueryParam("query") + ParameterWithFromString parameterWithFromString; + + @QueryParam("missing") + String missing; + + @DefaultValue("there") + @QueryParam("missing") + String missingWithDefaultValue; + + @QueryParam("missing") + ParameterWithFromString missingParameterWithFromString; + + @DefaultValue("there") + @QueryParam("missing") + ParameterWithFromString missingParameterWithFromStringAndDefaultValue; + + @QueryParam("int") + int primitiveParam; + + @QueryParam("missing") + int missingPrimitiveParam; + + @DefaultValue("42") + @QueryParam("missing") + int missingPrimitiveParamWithDefaultValue; + + @QueryParam("query") + MyParameter myParameter; + + @QueryParam("query") + List myParameterList; + + public void check(String path) { + Assertions.assertEquals("one-query", query); + Assertions.assertEquals("one-query", privateQuery); + Assertions.assertEquals("one-query", protectedQuery); + Assertions.assertEquals("one-query", publicQuery); + Assertions.assertEquals("one-header", header); + Assertions.assertNotNull(uriInfo); + Assertions.assertEquals(path, uriInfo.getPath()); + Assertions.assertNotNull(otherBeanParam); + otherBeanParam.check(path); + Assertions.assertNotNull(queryList); + Assertions.assertEquals("one", queryList.get(0)); + Assertions.assertEquals("two", queryList.get(1)); + Assertions.assertNotNull(parameterWithFromString); + Assertions.assertEquals("ParameterWithFromString[val=one-query]", parameterWithFromString.toString()); + Assertions.assertNull(missing); + Assertions.assertEquals("there", missingWithDefaultValue); + Assertions.assertNull(missingParameterWithFromString); + Assertions.assertEquals("ParameterWithFromString[val=there]", missingParameterWithFromStringAndDefaultValue.toString()); + Assertions.assertEquals(666, primitiveParam); + Assertions.assertEquals(0, missingPrimitiveParam); + Assertions.assertEquals(42, missingPrimitiveParamWithDefaultValue); + Assertions.assertEquals("one-queryone-query", myParameter.toString()); + Assertions.assertEquals("one-queryone-query", myParameterList.get(0).toString()); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleQuarkusRestResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleQuarkusRestResource.java new file mode 100644 index 0000000000000..d9bade4c50307 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleQuarkusRestResource.java @@ -0,0 +1,367 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import javax.inject.Inject; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.OPTIONS; +import javax.ws.rs.PATCH; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Providers; +import org.jboss.resteasy.reactive.RestHeader; +import org.jboss.resteasy.reactive.server.SimpleResourceInfo; +import org.jboss.resteasy.reactive.server.core.BlockingOperationSupport; + +@Path("/simple") +public class SimpleQuarkusRestResource { + + private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; + + @Inject + HelloService service; + + @GET + public String get() { + return "GET"; + } + + @Path("sub") + public Object subResource() { + return new SubResource(); + } + + @GET + @Path("/hello") + public String hello() { + return service.sayHello(); + } + + @GET + @Path("{id}") + public String get(@PathParam("id") String id) { + return "GET:" + id; + } + + @POST + @Path("params/{p}") + public String params(@PathParam("p") String p, + @QueryParam("q") String q, + @HeaderParam("h") int h, + @FormParam("f") String f) { + return "params: p: " + p + ", q: " + q + ", h: " + h + ", f: " + f; + } + + @POST + public String post() { + return "POST"; + } + + @DELETE + public String delete() { + return "DELETE"; + } + + @PUT + public String put() { + return "PUT"; + } + + @PATCH + public String patch() { + return "PATCH"; + } + + @OPTIONS + public String options() { + return "OPTIONS"; + } + + @HEAD + public Response head() { + return Response.ok().header("Stef", "head").build(); + } + + @GET + @Path("/person") + @Produces(MediaType.APPLICATION_JSON) + public Person getPerson() { + Person person = new Person(); + person.setFirst("Bob"); + person.setLast("Builder"); + return person; + } + + @GET + @Path("/blocking") + @Blocking + public String blocking() { + return String.valueOf(BlockingOperationSupport.isBlockingAllowed()); + } + + @GET + @Path("providers") + public Response filters(@Context Providers providers) { + // TODO: enhance this test + return Response.ok().entity(providers.getExceptionMapper(TestException.class).getClass().getName()).build(); + } + + @GET + @Path("filters") + public Response filters(@Context HttpHeaders headers, @RestHeader("filter-request") String header) { + return Response.ok().header("filter-request", header).build(); + } + + @GET + @Path("feature-filters") + public Response featureFilters(@Context HttpHeaders headers) { + return Response.ok().header("feature-filter-request", headers.getHeaderString("feature-filter-request")).build(); + } + + @GET + @Path("dynamic-feature-filters") + public Response dynamicFeatureFilters(@Context HttpHeaders headers) { + return Response.ok().header("feature-filter-request", headers.getHeaderString("feature-filter-request")).build(); + } + + @GET + @Path("fooFilters") + @Foo + public Response fooFilters(@Context HttpHeaders headers) { + return Response.ok().header("filter-request", headers.getHeaderString("filter-request")).build(); + } + + @GET + @Path("barFilters") + @Bar + public Response barFilters(@Context HttpHeaders headers) { + return Response.ok().header("filter-request", headers.getHeaderString("filter-request")).build(); + } + + @GET + @Path("fooBarFilters") + @Foo + @Bar + public Response fooBarFilters(@Context HttpHeaders headers) { + return Response.ok().header("filter-request", headers.getHeaderString("filter-request")).build(); + } + + @GET + @Path("mapped-exception") + public String mappedException() { + TestException exception = new TestException(); + exception.setStackTrace(EMPTY_STACK_TRACE); + throw exception; + } + + @GET + @Path("feature-mapped-exception") + public String featureMappedException() { + FeatureMappedException exception = new FeatureMappedException(); + exception.setStackTrace(EMPTY_STACK_TRACE); + throw exception; + } + + @GET + @Path("unknown-exception") + public String unknownException() { + RuntimeException exception = new RuntimeException("OUCH"); + exception.setStackTrace(EMPTY_STACK_TRACE); + throw exception; + } + + @GET + @Path("web-application-exception") + public String webApplicationException() { + throw new WebApplicationException(Response.status(666).entity("OK").build()); + } + + @GET + @Path("writer") + public TestClass writer() { + return new TestClass(); + } + + @GET + @Path("fast-writer") + @Produces("text/plain") + public String fastWriter() { + return "OK"; + } + + @GET + @Path("lookup-writer") + public Object slowWriter() { + return "OK"; + } + + @GET + @Path("writer/vertx-buffer") + public Buffer vertxBuffer() { + return Buffer.buffer("VERTX-BUFFER"); + } + + @GET + @Path("async/cs/ok") + public CompletionStage asyncCompletionStageOK() { + return CompletableFuture.completedFuture("CS-OK"); + } + + @GET + @Path("async/cs/fail") + public CompletionStage asyncCompletionStageFail() { + CompletableFuture ret = new CompletableFuture<>(); + ret.completeExceptionally(new TestException()); + return ret; + } + + @GET + @Path("async/cf/ok") + public CompletableFuture asyncCompletableFutureOK() { + return CompletableFuture.completedFuture("CF-OK"); + } + + @GET + @Path("async/cf/fail") + public CompletableFuture asyncCompletableFutureFail() { + CompletableFuture ret = new CompletableFuture<>(); + ret.completeExceptionally(new TestException()); + return ret; + } + + @GET + @Path("async/uni/ok") + public Uni asyncUniOK() { + return Uni.createFrom().item("UNI-OK"); + } + + @Produces(MediaType.APPLICATION_JSON) + @GET + @Path("async/uni/list") + public Uni> asyncUniListJson() { + Person person = new Person(); + person.setFirst("Bob"); + person.setLast("Builder"); + return Uni.createFrom().item(Arrays.asList(person)); + } + + @GET + @Path("async/uni/fail") + public Uni asyncUniStageFail() { + return Uni.createFrom().failure(new TestException()); + } + + @GET + @Path("pre-match") + public String preMatchGet() { + return "pre-match-get"; + } + + @POST + @Path("pre-match") + public String preMatchPost() { + return "pre-match-post"; + } + + @GET + @Path("request-response-params") + public String requestAndResponseParams(@Context HttpServerRequest request, @Context HttpServerResponse response) { + response.headers().add("dummy", "value"); + return request.remoteAddress().host(); + } + + @GET + @Path("jax-rs-request") + public String jaxRsRequest(@Context Request request) { + return request.getMethod(); + } + + @GET + @Path("resource-info") + public Response resourceInfo(@Context ResourceInfo resourceInfo, @Context HttpHeaders headers) { + return Response.ok() + .header("class-name", resourceInfo.getResourceClass().getSimpleName()) + .header("method-name", headers.getHeaderString("method-name")) + .build(); + } + + @Path("form-map") + @POST + @Produces(MediaType.APPLICATION_FORM_URLENCODED) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public MultivaluedMap map(MultivaluedMap map) { + return map; + } + + @Path("jsonp-object") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) + public String jsonpObject(JsonObject jsonbObject) { + return jsonbObject.getString("k"); + } + + @Path("jsonp-array") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) + public Integer jsonpArray(JsonArray jsonArray) { + return jsonArray.size(); + } + + @Path("/bool") + @POST + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + public boolean bool(boolean bool) { + return bool; + } + + @Path("/trace") + @TRACE + public Response trace() { + return Response.status(Response.Status.OK).build(); + } + + @GET + @Path("simplifiedResourceInfo") + @Produces(MediaType.TEXT_PLAIN) + public String simplifiedResourceInfo(@Context SimpleResourceInfo simplifiedResourceInfo) { + return simplifiedResourceInfo.getResourceClass().getName() + "#" + simplifiedResourceInfo.getMethodName() + "-" + + simplifiedResourceInfo.parameterTypes().length; + } + + @GET + @Path("bigDecimal/{val}") + @Produces(MediaType.TEXT_PLAIN) + public String bigDecimalConverter(BigDecimal val) { + return val.toString(); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleRESTEasyReactiveRestTestCase.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleRESTEasyReactiveRestTestCase.java new file mode 100644 index 0000000000000..9df7ec97e8ede --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SimpleRESTEasyReactiveRestTestCase.java @@ -0,0 +1,438 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.emptyString; + +import io.restassured.RestAssured; +import io.restassured.http.Headers; +import java.util.function.Supplier; +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class SimpleRESTEasyReactiveRestTestCase { + + @RegisterExtension + static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TRACE.class, SimpleQuarkusRestResource.class, Person.class, + TestRequestFilter.class, TestRequestFilterWithHighPriority.class, + TestRequestFilterWithHighestPriority.class, ResourceInfoInjectingFilter.class, + Foo.class, Bar.class, + TestFooRequestFilter.class, TestBarRequestFilter.class, TestFooBarRequestFilter.class, + TestFooResponseFilter.class, TestBarResponseFilter.class, TestFooBarResponseFilter.class, + TestResponseFilter.class, HelloService.class, TestException.class, + TestExceptionMapper.class, TestPreMatchRequestFilter.class, + FeatureMappedException.class, FeatureMappedExceptionMapper.class, + FeatureRequestFilterWithNormalPriority.class, FeatureRequestFilterWithHighestPriority.class, + FeatureResponseFilter.class, DynamicFeatureRequestFilterWithLowPriority.class, + TestFeature.class, TestDynamicFeature.class, + SubResource.class, RootAResource.class, RootBResource.class, + QueryParamResource.class, HeaderParamResource.class, + TestWriter.class, TestClass.class, + SimpleBeanParam.class, OtherBeanParam.class, FieldInjectedResource.class, + ParameterWithFromString.class, BeanParamSubClass.class, FieldInjectedSubClassResource.class, + BeanParamSuperClass.class, + MyParameterProvider.class, MyParameterConverter.class, MyParameter.class, + NewParamsRestResource.class, InterfaceResource.class, InterfaceResourceImpl.class); + } + }); + + @Test + public void simpleTest() { + RestAssured.get("/missing") + .then().statusCode(404); + RestAssured.get("/simple") + .then().body(Matchers.equalTo("GET")); + RestAssured.get("/simple/foo") + .then().body(Matchers.equalTo("GET:foo")); + + RestAssured.post("/simple") + .then().body(Matchers.equalTo("POST")); + + RestAssured.post("/missing") + .then().statusCode(404); + + RestAssured.delete("/missing") + .then().statusCode(404); + + RestAssured.delete("/simple") + .then().body(Matchers.equalTo("DELETE")); + + RestAssured.put("/simple") + .then().body(Matchers.equalTo("PUT")); + + RestAssured.head("/simple") + .then().header("Stef", "head"); + + RestAssured.options("/simple") + .then().body(Matchers.equalTo("OPTIONS")); + + RestAssured.patch("/simple") + .then().body(Matchers.equalTo("PATCH")); + } + + @Test + public void test405() { + RestAssured.put("/ctor-query") + .then().statusCode(405); + + RestAssured.put("/simple/person") + .then().statusCode(405); + } + + @Test + @Disabled("injection") + public void testInjection() { + RestAssured.get("/simple/hello") + .then().body(Matchers.equalTo("Hello")); + } + + @Test + public void testSubResource() { + RestAssured.get("/simple/sub/otherSub") + .then().body(Matchers.equalTo("otherSub")); + RestAssured.get("/simple/sub") + .then().body(Matchers.equalTo("sub")); + } + + @Test + public void testParams() { + RestAssured.with() + .queryParam("q", "qv") + .header("h", "123") + .formParam("f", "fv") + .post("/simple/params/pv") + .then().body(Matchers.equalTo("params: p: pv, q: qv, h: 123, f: fv")); + } + + @Test + public void testBlocking() { + RestAssured.get("/simple/blocking") + .then().body(Matchers.equalTo("true")); + } + + @Test + public void testPreMatchFilter() { + RestAssured.get("/simple/pre-match") + .then().body(Matchers.equalTo("pre-match-post")); + RestAssured.post("/simple/pre-match") + .then().body(Matchers.equalTo("pre-match-post")); + } + + @Test + public void testFilters() { + Headers headers = RestAssured.get("/simple/fooFilters") + .then().extract().headers(); + assertThat(headers.getValues("filter-request")).containsOnly("authentication-authorization-foo-default"); + assertThat(headers.getValues("filter-response")).containsOnly("default-foo"); + + headers = RestAssured.get("/simple/filters") + .then().extract().headers(); + assertThat(headers.getValues("filter-request")).containsOnly("authentication-authorization-default"); + assertThat(headers.getValues("filter-response")).containsOnly("default"); + + headers = RestAssured.get("/simple/barFilters") + .then().extract().headers(); + assertThat(headers.getValues("filter-request")).containsOnly("authentication-authorization-default-bar"); + assertThat(headers.getValues("filter-response")).containsOnly("default-bar"); + + headers = RestAssured.get("/simple/fooBarFilters") + .then().extract().headers(); + assertThat(headers.getValues("filter-request")).containsOnly("authentication-authorization-foo-default-bar-foobar"); + assertThat(headers.getValues("filter-response")).containsOnly("default-foo-bar-foobar"); + } + + @Test + public void testProviders() { + RestAssured.get("/simple/providers") + .then().body(Matchers.containsString("TestException")) + .statusCode(200); + } + + @Test + public void testException() { + RestAssured.get("/simple/mapped-exception") + .then().body(Matchers.equalTo("OK")) + .statusCode(666); + RestAssured.get("/simple/unknown-exception") + .then().statusCode(500); + RestAssured.get("/simple/web-application-exception") + .then().body(Matchers.equalTo("OK")) + .statusCode(666); + } + + @Test + public void testWriter() { + RestAssured.get("/simple/lookup-writer") + .then().body(Matchers.equalTo("OK")); + RestAssured.get("/simple/writer") + .then().body(Matchers.equalTo("WRITER")); + + RestAssured.get("/simple/fast-writer") + .then().body(Matchers.equalTo("OK")); + + RestAssured.get("/simple/writer/vertx-buffer") + .then().body(Matchers.equalTo("VERTX-BUFFER")); + } + + @DisabledOnOs(OS.WINDOWS) + @Test + public void testAsync() { + RestAssured.get("/simple/async/cs/ok") + .then().body(Matchers.equalTo("CS-OK")); + RestAssured.get("/simple/async/cs/fail") + .then().body(Matchers.equalTo("OK")) + .statusCode(666); + RestAssured.get("/simple/async/cf/ok") + .then().body(Matchers.equalTo("CF-OK")); + RestAssured.get("/simple/async/cf/fail") + .then().body(Matchers.equalTo("OK")) + .statusCode(666); + RestAssured.get("/simple/async/uni/ok") + .then().body(Matchers.equalTo("UNI-OK")); + RestAssured.get("/simple/async/uni/fail") + .then().body(Matchers.equalTo("OK")) + .statusCode(666); + } + + @Test + public void testMultiResourceSamePath() { + RestAssured.get("/a") + .then() + .statusCode(200) + .body(Matchers.equalTo("a")); + RestAssured.get("/b") + .then() + .statusCode(200) + .body(Matchers.equalTo("b")); + } + + @Test + public void testRequestAndResponseParams() { + RestAssured.get("/simple/request-response-params") + .then() + .body(Matchers.equalTo("127.0.0.1")) + .header("dummy", "value"); + + } + + @Test + public void testJaxRsRequest() { + RestAssured.get("/simple/jax-rs-request") + .then() + .body(Matchers.equalTo("GET")); + } + + @Test + public void testFeature() { + RestAssured.get("/simple/feature-mapped-exception") + .then() + .statusCode(667); + + Headers headers = RestAssured.get("/simple/feature-filters") + .then().extract().headers(); + assertThat(headers.getValues("feature-filter-request")).containsOnly("authentication-default"); + assertThat(headers.getValues("feature-filter-response")).containsExactly("high-priority", "normal-priority"); + } + + @Test + public void testDynamicFeature() { + Headers headers = RestAssured.get("/simple/dynamic-feature-filters") + .then().extract().headers(); + assertThat(headers.getValues("feature-filter-request")).containsOnly("authentication-default-low"); + assertThat(headers.getValues("feature-filter-response")).containsExactly("high-priority", "normal-priority", + "low-priority"); + } + + @Test + public void testResourceInfo() { + Headers headers = RestAssured.get("/simple/resource-info") + .then().extract().headers(); + assertThat(headers.getValues("class-name")).containsOnly("SimpleQuarkusRestResource"); + assertThat(headers.getValues("method-name")).containsOnly("resourceInfo"); + } + + @Test + @Disabled("injection") + public void testQueryParamInCtor() { + RestAssured.get("/ctor-query") + .then().body(Matchers.is(emptyString())); + + RestAssured.get("/ctor-query?q1=v1") + .then().body(Matchers.equalTo("v1")); + + RestAssured.get("/ctor-query?q1=v11") + .then().body(Matchers.equalTo("v11")); + + RestAssured.get("/ctor-query?q2=v2") + .then().body(Matchers.is(emptyString())); + } + + @Test + @Disabled("injection") + public void testHeaderParamInCtor() { + RestAssured.get("/ctor-header") + .then().body(Matchers.is(emptyString())); + + RestAssured.with().header("h1", "v1").get("/ctor-header") + .then().body(Matchers.equalTo("v1")); + + RestAssured.with().header("h1", "v11").get("/ctor-header") + .then().body(Matchers.equalTo("v11")); + + RestAssured.with().header("h2", "v2").get("/ctor-header") + .then().body(Matchers.is(emptyString())); + } + + @RepeatedTest(100) + public void testFormMap() { + RestAssured + .given() + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Accept", "application/x-www-form-urlencoded") + .formParam("f1", "v1") + .formParam("f2", "v2") + .post("/simple/form-map") + .then() + .statusCode(200) + .contentType("application/x-www-form-urlencoded") + .body(Matchers.equalTo("f1=v1&f2=v2")); + } + + // @Test + // public void testJsonp() { + // RestAssured.with().body("{\"k\": \"v\"}").contentType("application/json; charset=utf-8").post("/simple/jsonp-object") + // .then().statusCode(200).body(Matchers.equalTo("v")); + // + // RestAssured.with().body("[{}, {}]").contentType("application/json").post("/simple/jsonp-array") + // .then().statusCode(200).body(Matchers.equalTo("2")); + // } + + @Test + public void testPrimitiveBody() { + RestAssured.with().body("true").contentType("text/plain").post("/simple/bool") + .then().statusCode(200).contentType("text/plain").body(Matchers.equalTo("true")); + } + + @Test + public void testCustomHttpMethodAnnotation() { + RestAssured.request("TRACE", "/simple/trace") + .then().statusCode(200); + } + + @Test + @Disabled("injection") + public void simpleFieldInjection() { + RestAssured + .with() + .header("header", "one-header") + .queryParam("query", "one-query") + .queryParam("queryList", "one") + .queryParam("queryList", "two") + .queryParam("int", "666") + .get("/injection/field") + .then().statusCode(200).body(Matchers.equalTo("OK")); + RestAssured + .with() + .header("header", "one-header") + .queryParam("query", "one-query") + .queryParam("queryList", "one") + .queryParam("queryList", "two") + .queryParam("int", "666") + .get("/injection/param") + .then().statusCode(200).body(Matchers.equalTo("OK")); + } + + @Test + @Disabled("injection") + public void fieldInjectionWithSubClasses() { + RestAssured + .with() + .header("header", "one-header") + .queryParam("query", "one-query") + .queryParam("queryList", "one") + .queryParam("queryList", "two") + .queryParam("int", "666") + .get("/injection-subclass/field2") + .then().body(Matchers.equalTo("OK")); + RestAssured + .with() + .header("header", "one-header") + .queryParam("query", "one-query") + .queryParam("queryList", "one") + .queryParam("queryList", "two") + .queryParam("int", "666") + .get("/injection-subclass/param2") + .then().body(Matchers.equalTo("OK")); + } + + @DisabledOnOs(OS.WINDOWS) + @Test + @Disabled("quarkus specific") + public void testNewParams() { + RestAssured.get("/new-params/myklass/myregex/context") + .then() + .log().ifError() + .statusCode(200) + .body(Matchers.equalTo("OK")); + RestAssured.get("/new-params/myklass/myregex/mymethod") + .then() + .log().ifError() + .body(Matchers.equalTo("GET:myklass:myregex:mymethod")); + RestAssured.with() + .urlEncodingEnabled(false) + .cookie("c", "cv") + .queryParam("q", "qv") + .queryParam("q3", "999") + .header("h", "123") + .header("X-My-Header", "test") + .header("Test-Header-Param", "test") + .header("Param-Empty", "empty") + .formParam("f", "fv") + .post("/new-params/myklass;m=mv/myregex/params/pv") + .then() + .log().ifError() + .body(Matchers + .equalTo( + "params: p: pv, q: qv, h: 123, xMyHeader: test, testHeaderParam: test, paramEmpty: empty, f: fv, m: mv, c: cv, q2: empty, q3: 999")); + RestAssured.get("/new-params/myklass/myregex/sse") + .then() + .log().ifError() + .body(Matchers.equalTo("data:OK\n\n")); + RestAssured.with() + .urlEncodingEnabled(false) + .formParam("f", "fv") + .post("/new-params/myklass;m=mv/myregex/form-blocking") + .then() + .body(Matchers.equalTo("fv")); + } + + @Test + public void simplifiedResourceInfo() { + RestAssured.get("/simple/simplifiedResourceInfo") + .then().statusCode(200).body(Matchers.containsString("SimpleQuarkusRestResource#simplifiedResourceInfo-1")); + } + + @Test + public void bigDecimal() { + RestAssured.get("/simple/bigDecimal/1.0") + .then().statusCode(200).body(Matchers.equalTo("1.0")); + } + + @Test + public void testInterfaceResource() { + RestAssured.get("/iface") + .then().statusCode(200).body(Matchers.equalTo("Hello")); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SubResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SubResource.java new file mode 100644 index 0000000000000..0ed36f8f934b4 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/SubResource.java @@ -0,0 +1,18 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +public class SubResource { + + @GET + public String sub() { + return "sub"; + } + + @GET + @Path("otherSub") + public String otherPath() { + return "otherSub"; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TRACE.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TRACE.java new file mode 100644 index 0000000000000..150c80bde9563 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TRACE.java @@ -0,0 +1,14 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.ws.rs.HttpMethod; + +@HttpMethod("TRACE") +@Target(value = ElementType.METHOD) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface TRACE { + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestBarRequestFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestBarRequestFilter.java new file mode 100644 index 0000000000000..5275c17fda7f6 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestBarRequestFilter.java @@ -0,0 +1,21 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Bar +@Priority(Priorities.USER + 1) +public class TestBarRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("filter-request"); + requestContext.getHeaders().putSingle("filter-request", previousFilterHeaderValue + "-bar"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestBarResponseFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestBarResponseFilter.java new file mode 100644 index 0000000000000..16c515d0e7fb5 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestBarResponseFilter.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Bar +@Priority(Priorities.USER - 2) +public class TestBarResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + String previousFilterHeaderValue = (String) responseContext.getHeaders().getFirst("filter-response"); + responseContext.getHeaders().putSingle("filter-response", previousFilterHeaderValue + "-bar"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestClass.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestClass.java new file mode 100644 index 0000000000000..25bc215f28ac4 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestClass.java @@ -0,0 +1,5 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +public class TestClass { + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestDynamicFeature.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestDynamicFeature.java new file mode 100644 index 0000000000000..5f147f349db68 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestDynamicFeature.java @@ -0,0 +1,20 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.Priorities; +import javax.ws.rs.container.DynamicFeature; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestDynamicFeature implements DynamicFeature { + + @Override + public void configure(ResourceInfo resourceInfo, FeatureContext context) { + if (resourceInfo.getResourceClass().getName().equals(SimpleQuarkusRestResource.class.getName()) + && resourceInfo.getResourceMethod().getName().equals("dynamicFeatureFilters")) { + context.register(DynamicFeatureRequestFilterWithLowPriority.class); + context.register(new FeatureResponseFilter("feature-filter-response", "low-priority"), Priorities.USER - 1); + } + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestException.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestException.java new file mode 100644 index 0000000000000..9cc191dc7f91d --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestException.java @@ -0,0 +1,6 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +@SuppressWarnings("serial") +public class TestException extends RuntimeException { + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestExceptionMapper.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestExceptionMapper.java new file mode 100644 index 0000000000000..3c3dd3cdae83f --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestExceptionMapper.java @@ -0,0 +1,15 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(TestException exception) { + return Response.status(666).entity("OK").build(); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFeature.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFeature.java new file mode 100644 index 0000000000000..efb8a5d95f6ca --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFeature.java @@ -0,0 +1,19 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import javax.ws.rs.Priorities; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestFeature implements Feature { + @Override + public boolean configure(FeatureContext context) { + context.register(FeatureMappedExceptionMapper.class); + context.register(FeatureRequestFilterWithHighestPriority.class); + context.register(FeatureRequestFilterWithNormalPriority.class); + context.register(new FeatureResponseFilter("feature-filter-response", "normal-priority")); + context.register(new FeatureResponseFilter("feature-filter-response", "high-priority"), Priorities.USER + 1); + return true; + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooBarRequestFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooBarRequestFilter.java new file mode 100644 index 0000000000000..badff4695cc72 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooBarRequestFilter.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Foo +@Bar +@Priority(Priorities.USER + 2) +public class TestFooBarRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("filter-request"); + requestContext.getHeaders().putSingle("filter-request", previousFilterHeaderValue + "-foobar"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooBarResponseFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooBarResponseFilter.java new file mode 100644 index 0000000000000..66ca755ff6dfd --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooBarResponseFilter.java @@ -0,0 +1,23 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Foo +@Bar +@Priority(Priorities.USER - 3) +public class TestFooBarResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + String previousFilterHeaderValue = (String) responseContext.getHeaders().getFirst("filter-response"); + responseContext.getHeaders().putSingle("filter-response", previousFilterHeaderValue + "-foobar"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooRequestFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooRequestFilter.java new file mode 100644 index 0000000000000..dfeaeb1da0707 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooRequestFilter.java @@ -0,0 +1,26 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import io.vertx.core.http.HttpServerRequest; +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; + +@Provider +@Foo +@Priority(Priorities.USER - 1) +public class TestFooRequestFilter implements ContainerRequestFilter { + + @Context + HttpServerRequest request; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("filter-request"); + requestContext.getHeaders().putSingle("filter-request", previousFilterHeaderValue + "-foo"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooResponseFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooResponseFilter.java new file mode 100644 index 0000000000000..cc3d6b7b05c2f --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestFooResponseFilter.java @@ -0,0 +1,22 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Foo +@Priority(Priorities.USER - 1) +public class TestFooResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + String previousFilterHeaderValue = (String) responseContext.getHeaders().getFirst("filter-response"); + responseContext.getHeaders().putSingle("filter-response", previousFilterHeaderValue + "-foo"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestPreMatchRequestFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestPreMatchRequestFilter.java new file mode 100644 index 0000000000000..6a1761fb5f005 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestPreMatchRequestFilter.java @@ -0,0 +1,20 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.ext.Provider; + +@Provider +@PreMatching +public class TestPreMatchRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + if (requestContext.getUriInfo().getPath().equals("/simple/pre-match")) { + requestContext.setMethod("POST"); + } + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilter.java new file mode 100644 index 0000000000000..79d0ef3987617 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilter.java @@ -0,0 +1,17 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("filter-request"); + requestContext.getHeaders().putSingle("filter-request", previousFilterHeaderValue + "-default"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilterWithHighPriority.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilterWithHighPriority.java new file mode 100644 index 0000000000000..1a8d3672347f0 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilterWithHighPriority.java @@ -0,0 +1,20 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Priority(Priorities.AUTHORIZATION) +public class TestRequestFilterWithHighPriority implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + String previousFilterHeaderValue = requestContext.getHeaders().getFirst("filter-request"); + requestContext.getHeaders().putSingle("filter-request", previousFilterHeaderValue + "-authorization"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilterWithHighestPriority.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilterWithHighestPriority.java new file mode 100644 index 0000000000000..e84c7deb58d37 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestRequestFilterWithHighestPriority.java @@ -0,0 +1,19 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.ext.Provider; + +@Provider +@Priority(Priorities.AUTHENTICATION) +public class TestRequestFilterWithHighestPriority implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + requestContext.getHeaders().add("filter-request", "authentication"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestResponseFilter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestResponseFilter.java new file mode 100644 index 0000000000000..4cec319455d1d --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestResponseFilter.java @@ -0,0 +1,17 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + responseContext.getHeaders().add("filter-response", "default"); + } + +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestUtil.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestUtil.java new file mode 100644 index 0000000000000..c496ae43ba91b --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestUtil.java @@ -0,0 +1,290 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.ws.rs.core.Application; +import org.jboss.logging.Logger; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePath; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.Asset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.jupiter.api.Assertions; + +/** + * Base util class for RESTEasy testing. + */ +public class TestUtil { + + protected static Logger LOG; + + private static String baseResourcePath = new StringBuilder() + .append("src").append(File.separator) + .append("test").append(File.separator) + .append("resources").append(File.separator).toString(); + /** + * Try to initialize logger. This is unsuccessful on EAP deployment, because EAP do not contain log4j. + * Logger is not necessary for this class. Some methods could be used without it. + */ + static { + try { + LOG = Logger.getLogger(TestUtil.class.getName()); + } catch (NoClassDefFoundError e) { + // unable to initialize logger, finishContainerPrepare method could not be used + } + } + + public static WebArchive prepareArchiveWithApplication(String deploymentName, Class clazz) { + WebArchive war = ShrinkWrap.create(WebArchive.class, deploymentName + ".war"); + war.addClass(clazz); + return war; + } + + /** + * Finish preparing war deployment and deploy it. + * + * Add classes in @resources to deployment. Also all sub-classes of classes in @resources are added to deployment. + * But only classes in @resources (not sub-classes of classes in @resources) can be used as resources + * (getClasses function of TestApplication class return only classes in @resources). + * + * @param resources classes used in deployment as resources + */ + public static Archive finishContainerPrepare(WebArchive war, Map contextParams, + final Class... resources) { + return finishContainerPrepare(war, contextParams, null, resources); + } + + /** + * Finish preparing war deployment and deploy it. + * + * Add classes in @resources to deployment. Also all sub-classes of classes in @resources are added to deployment. + * But only classes in @resources (not sub-classes of classes in @resources) can be used as resources + * (getClasses function of TestApplication class return only classes in @resources). + * + * @param singletons classes used in deployment as singletons + * @param resources classes used in deployment as resources + */ + public static Archive finishContainerPrepare(WebArchive war, Map contextParams, + List> singletons, final Class... resources) { + + if (contextParams == null) { + contextParams = new Hashtable<>(); + } + + Set classNamesInDeployment = new HashSet<>(); + Set singletonsNamesInDeployment = new HashSet<>(); + + if (resources != null) { + for (final Class clazz : resources) { + war.addClass(clazz); + classNamesInDeployment.add(clazz.getTypeName()); + } + } + + if (singletons != null) { + for (Class singleton : singletons) { + war.addClass(singleton); + singletonsNamesInDeployment.add(singleton.getTypeName()); + } + } + + if (contextParams != null && contextParams.size() > 0 && !war.contains("WEB-INF/web.xml")) { + StringBuilder webXml = new StringBuilder(); + webXml.append(" \n"); + for (Map.Entry entry : contextParams.entrySet()) { + String paramName = entry.getKey(); + String paramValue = entry.getValue(); + LOG.debug("Context param " + paramName + " value " + paramValue); + + webXml.append("\n"); + webXml.append("" + paramName + "\n"); + webXml.append("" + paramValue + "\n"); + webXml.append("\n"); + } + + webXml.append("\n"); + Asset resource = new StringAsset(webXml.toString()); + war.addAsWebInfResource(resource, "web.xml"); + } + + // prepare class list for getClasses function of TestApplication class + StringBuilder classes = new StringBuilder(); + boolean start = true; + for (String clazz : classNamesInDeployment) { + if (start) { + start = false; + } else { + classes.append(","); + } + classes.append(clazz); + } + war.addAsResource(new StringAsset(classes.toString()), "classes.txt"); + + // prepare singleton list for getSingletons function of TestApplication class + StringBuilder singletonBuilder = new StringBuilder(); + start = true; + for (String clazz : singletonsNamesInDeployment) { + if (start) { + start = false; + } else { + singletonBuilder.append(","); + } + singletonBuilder.append(clazz); + } + war.addAsResource(new StringAsset(singletonBuilder.toString()), "singletons.txt"); + + if (System.getProperty("STORE_WAR") != null) { + war.as(ZipExporter.class).exportTo(new File("target", war.getName()), true); + } + return war; + } + + /** + * Add package info to deployment. + * + * @param clazz Package info is for package of this class. + */ + protected WebArchive addPackageInfo(WebArchive war, final Class clazz) { + return war.addPackages(false, new org.jboss.shrinkwrap.api.Filter() { + @Override + public boolean include(final ArchivePath path) { + return path.get().endsWith("package-info.class"); + } + }, clazz.getPackage()); + } + + /** + * Convert input stream to String. + * + * @param in Input stream + * @return Converted string + */ + public static String readString(final InputStream in) throws IOException { + char[] buffer = new char[1024]; + StringBuilder builder = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + int wasRead = 0; + do { + wasRead = reader.read(buffer, 0, 1024); + if (wasRead > 0) { + builder.append(buffer, 0, wasRead); + } + } while (wasRead > -1); + + return builder.toString(); + } + + public static String getErrorMessageForKnownIssue(String jira, String message) { + StringBuilder s = new StringBuilder(); + s.append("https://issues.jboss.org/browse/"); + s.append(jira); + s.append(" - "); + s.append(message); + return s.toString(); + } + + public static String getErrorMessageForKnownIssue(String jira) { + return getErrorMessageForKnownIssue(jira, "known issue"); + } + + public static String getJbossHome() { + return System.getProperty("jboss.home"); + } + + public static String getJbossHome(boolean onServer) { + if (onServer == false) { + return getJbossHome(); + } + return System.getProperty("jboss.home.dir", ""); + } + + /** + * Get the path to the containers base dir for standalone mode (configuration, logs, etc..). + * When arquillian.xml contains more containers that could be started simultaneously the parameter containerQualifier + * is used to determine which base dir to get. + * + * @param containerQualifier container qualifier or null if the arquillian.xml contains max 1 container available + * to be running at time + * @return absolute path to base dir + */ + public static String getStandaloneDir(String containerQualifier) { + return getStandaloneDir(false, containerQualifier); + } + + /** + * Get the path to the containers base dir for standalone mode (configuration, logs, etc..). + * When arquillian.xml contains more containers that could be started simultaneously the parameter containerQualifier + * is used to determine which base dir to get. + * + * @param onServer whether the check is made from client side (the path is constructed) or from deployment (the path + * is read from actual runtime value) + * @param containerQualifier container qualifier or null if the arquillian.xml contains max 1 container available + * to be running at time; this has no effect when onServer is true + * @return absolute path to base dir + */ + public static String getStandaloneDir(boolean onServer, String containerQualifier) { + if (onServer == false) { + if (containerQualifier == null) { + return new File(getJbossHome(), "standalone").getAbsolutePath(); + } else { + return new File("target", containerQualifier).getAbsolutePath(); + } + } else { + return System.getProperty("jboss.server.base.dir", ""); + } + } + + public static boolean isOpenJDK() { + return System.getProperty("java.runtime.name").toLowerCase().contains("openjdk"); + } + + public static boolean isWildFly9x() { + final String sv = System.getProperty("server.version"); + return ("9.0.2.Final".equals(sv) || "9.0.1.Final".equals(sv) || "9.0.0.Final".equals(sv)); + } + + public static boolean isOracleJDK() { + if (isOpenJDK()) { + return false; + } + String vendor = System.getProperty("java.vendor").toLowerCase(); + return vendor.contains("sun") || vendor.contains("oracle"); + } + + public static boolean isIbmJdk() { + return System.getProperty("java.vendor").toLowerCase().contains("ibm"); + } + + /** + * Get resource in test scope for some class. + * Example: class org.test.MyTest and name "my_resource.txt" returns "src/test/resource/org/test/my_resource.txt" + */ + public static String getResourcePath(Class c, String name) { + return new StringBuilder() + .append(baseResourcePath) + .append(c.getPackage().getName().replace('.', File.separatorChar)) + .append(File.separator).append(name) + .toString(); + } + + public static boolean isWindows() { + String osName = System.getProperty("os.name"); + if (osName == null) { + Assertions.fail("Can't get the operating system name"); + } + return (osName.indexOf("Windows") > -1) || (osName.indexOf("windows") > -1); + } +} diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestWriter.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestWriter.java new file mode 100644 index 0000000000000..758b528058804 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/TestWriter.java @@ -0,0 +1,31 @@ +package org.jboss.resteasy.reactive.server.vertx.test.simple; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +@Provider +@Produces(MediaType.TEXT_PLAIN) +public class TestWriter implements MessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return true; + } + + @Override + public void writeTo(TestClass t, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + entityStream.write("WRITER".getBytes(StandardCharsets.UTF_8)); + } + +}