From b5ac295f1242e0fbeaa64953402f640e20707e12 Mon Sep 17 00:00:00 2001 From: Marcel Lohmann Date: Thu, 1 Dec 2022 21:53:14 +0100 Subject: [PATCH 01/28] OutboundSseEvent is not correctly serialized Closes #10673 (cherry picked from commit dc5f031c0f8b5f24f7f5bdba418e6f04b8dcbf25) --- .../server/test/stream/StreamResource.java | 12 ++++++++ .../server/test/stream/StreamTestCase.java | 28 +++++++++++++++++++ .../handlers/PublisherResponseHandler.java | 8 +++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java index def237ac8da81..307d5b77d5d81 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamResource.java @@ -8,7 +8,10 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.sse.OutboundSseEvent; +import javax.ws.rs.sse.Sse; import org.jboss.resteasy.reactive.common.util.MultiCollectors; import org.reactivestreams.Publisher; @@ -152,4 +155,13 @@ public Multi sse() { public Multi sseThrows() { throw new IllegalStateException("STOP"); } + + @Path("sse/raw") + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + public Multi sseRaw(@Context Sse sse) { + return Multi.createFrom().items(sse.newEventBuilder().id("one").data("uno").name("eins").build(), + sse.newEventBuilder().id("two").data("dos").name("zwei").build(), + sse.newEventBuilder().id("three").data("tres").name("drei").build()); + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java index 36df4075e7606..d43b1ddb1249b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/stream/StreamTestCase.java @@ -237,4 +237,32 @@ public void testSseThrows() throws InterruptedException { Assertions.assertEquals(1, errors.size()); } } + + @Test + public void testSseForMultiWithOutboundSseEvent() throws InterruptedException { + Client client = ClientBuilder.newBuilder().build(); + WebTarget target = client.target(this.uri.toString() + "stream/sse/raw"); + try (SseEventSource sse = SseEventSource.target(target).build()) { + CountDownLatch latch = new CountDownLatch(1); + List errors = new CopyOnWriteArrayList<>(); + List results = new CopyOnWriteArrayList<>(); + List ids = new CopyOnWriteArrayList<>(); + List names = new CopyOnWriteArrayList<>(); + sse.register(event -> { + results.add(event.readData()); + ids.add(event.getId()); + names.add(event.getName()); + }, error -> { + errors.add(error); + }, () -> { + latch.countDown(); + }); + sse.open(); + Assertions.assertTrue(latch.await(20, TimeUnit.SECONDS)); + Assertions.assertEquals(Arrays.asList("uno", "dos", "tres"), results); + Assertions.assertEquals(Arrays.asList("one", "two", "three"), ids); + Assertions.assertEquals(Arrays.asList("eins", "zwei", "drei"), names); + Assertions.assertEquals(0, errors.size()); + } + } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java index 3ac1f35a6deb3..45389d6c2ca0e 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java @@ -11,6 +11,7 @@ import java.util.function.Consumer; import javax.ws.rs.core.MediaType; +import javax.ws.rs.sse.OutboundSseEvent; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.util.RestMediaType; @@ -49,7 +50,12 @@ private static class SseMultiSubscriber extends AbstractMultiSubscriber { @Override public void onNext(Object item) { - OutboundSseEventImpl event = new OutboundSseEventImpl.BuilderImpl().data(item).build(); + OutboundSseEvent event; + if (item instanceof OutboundSseEvent) { + event = (OutboundSseEvent) item; + } else { + event = new OutboundSseEventImpl.BuilderImpl().data(item).build(); + } SseUtil.send(requestContext, event, customizers).whenComplete(new BiConsumer() { @Override public void accept(Object v, Throwable t) { From 75652eaf90c52df549f57ccea2b653b036c96b69 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Fri, 2 Dec 2022 23:28:03 +0100 Subject: [PATCH 02/28] Update the description copied from non-reactive variants (cherry picked from commit 92bd8d8c9e27f69e5840d59bf67d8323d1a07997) --- extensions/keycloak-admin-client-reactive/runtime/pom.xml | 2 +- .../hibernate-reactive-rest-data-panache/runtime/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/keycloak-admin-client-reactive/runtime/pom.xml b/extensions/keycloak-admin-client-reactive/runtime/pom.xml index dc194217932f8..8660b35d01ab7 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/pom.xml +++ b/extensions/keycloak-admin-client-reactive/runtime/pom.xml @@ -11,7 +11,7 @@ quarkus-keycloak-admin-client-reactive Quarkus - Keycloak Admin Client - Reactive - Runtime - Administer a Keycloak Instance + Administer a Keycloak Instance using Reactive diff --git a/extensions/panache/hibernate-reactive-rest-data-panache/runtime/pom.xml b/extensions/panache/hibernate-reactive-rest-data-panache/runtime/pom.xml index 3f50888b2350f..f4db684d089fc 100644 --- a/extensions/panache/hibernate-reactive-rest-data-panache/runtime/pom.xml +++ b/extensions/panache/hibernate-reactive-rest-data-panache/runtime/pom.xml @@ -11,7 +11,7 @@ quarkus-hibernate-reactive-rest-data-panache Quarkus - Hibernate Reactive REST data with Panache - Runtime - Generate JAX-RS resources for your Hibernate Panache entities and repositories + Generate JAX-RS resources for your Hibernate Reactive Panache entities and repositories From 98d0d2de8bb975d96b4be6786ee1138ef5b6e0ed Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 2 Dec 2022 20:33:44 +0000 Subject: [PATCH 03/28] Do not remove ConfigMappings marked as not removable via build item (cherry picked from commit 564d112a5cbaae03d5979c7843a261b59f687bc3) --- .../arc/deployment/ConfigBuildStep.java | 16 ++++++++--- .../deployment/UnremovableBeanBuildItem.java | 26 +++++++++++++++++ .../extest/deployment/TestProcessor.java | 7 +++++ .../src/main/resources/application.properties | 2 ++ .../config/UnremoveableConfigMappingTest.java | 28 +++++++++++++++++++ .../UnremovableMappingFromBuildItem.java | 8 ++++++ 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnremoveableConfigMappingTest.java create mode 100644 integration-tests/test-extension/extension/runtime/src/main/java/io/quarkus/extest/runtime/config/UnremovableMappingFromBuildItem.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index 2f2bf5cc46bc7..978b99960df4e 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -383,6 +383,7 @@ void registerConfigMappingConverters(CombinedIndexBuildItem indexBuildItem, void validateConfigMappingsInjectionPoints( ArcConfig arcConfig, ValidationPhaseBuildItem validationPhase, + List unremovableBeans, List configClasses, BuildProducer configMappings) { @@ -422,10 +423,17 @@ void validateConfigMappingsInjectionPoints( } } - for (ConfigClassBuildItem configClass : configMappingTypes.values()) { - // We don't look in the beans here, because SR Config has an API that can retrieve the mapping without CDI - if (!arcConfig.shouldEnableBeanRemoval() || configClass.getConfigClass().isAnnotationPresent(Unremovable.class)) { - toRegister.add(new ConfigMappingBuildItem(configClass.getConfigClass(), configClass.getPrefix())); + if (arcConfig.shouldEnableBeanRemoval()) { + Set unremovableClassNames = unremovableBeans.stream() + .map(UnremovableBeanBuildItem::getClassNames) + .flatMap(Collection::stream) + .collect(toSet()); + + for (ConfigClassBuildItem configClass : configMappingTypes.values()) { + if (configClass.getConfigClass().isAnnotationPresent(Unremovable.class) + || unremovableClassNames.contains(configClass.getName().toString())) { + toRegister.add(new ConfigMappingBuildItem(configClass.getConfigClass(), configClass.getPrefix())); + } } } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java index a8103b6a5ae76..098b5115a60e2 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java @@ -47,15 +47,41 @@ public final class UnremovableBeanBuildItem extends MultiBuildItem { private final Predicate predicate; + private final Set classNames; public UnremovableBeanBuildItem(Predicate predicate) { this.predicate = predicate; + this.classNames = Collections.emptySet(); + } + + public UnremovableBeanBuildItem(BeanClassNameExclusion predicate) { + this.predicate = predicate; + this.classNames = Collections.singleton(predicate.className); + } + + public UnremovableBeanBuildItem(BeanClassNamesExclusion predicate) { + this.predicate = predicate; + this.classNames = predicate.classNames; + } + + public UnremovableBeanBuildItem(BeanTypeExclusion predicate) { + this.predicate = predicate; + this.classNames = Collections.singleton(predicate.dotName.toString()); + } + + public UnremovableBeanBuildItem(BeanTypesExclusion predicate) { + this.predicate = predicate; + this.classNames = predicate.dotNames.stream().map(DotName::toString).collect(Collectors.toSet()); } public Predicate getPredicate() { return predicate; } + public Set getClassNames() { + return classNames; + } + /** * Match beans whose bean class matches any of the specified class names. * diff --git a/integration-tests/test-extension/extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java b/integration-tests/test-extension/extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java index f1f7bf97d718f..01b27cd7d4002 100644 --- a/integration-tests/test-extension/extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java +++ b/integration-tests/test-extension/extension/deployment/src/main/java/io/quarkus/extest/deployment/TestProcessor.java @@ -32,6 +32,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.ConfigPropertyBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; @@ -62,6 +63,7 @@ import io.quarkus.extest.runtime.config.TestMappingBuildTime; import io.quarkus.extest.runtime.config.TestMappingBuildTimeRunTime; import io.quarkus.extest.runtime.config.TestMappingRunTime; +import io.quarkus.extest.runtime.config.UnremovableMappingFromBuildItem; import io.quarkus.extest.runtime.config.XmlConfig; import io.quarkus.extest.runtime.logging.AdditionalLogHandlerValueFactory; import io.quarkus.extest.runtime.runtimeinitializedpackage.RuntimeInitializedClass; @@ -475,6 +477,11 @@ void runTimeConfigBuilder(BuildProducer configBui configBuilders.produce(new RunTimeConfigBuilderBuildItem(RunTimeConfigBuilder.class.getName())); } + @BuildStep + void unremoveableBeans(BuildProducer unremovableBeans) { + unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(UnremovableMappingFromBuildItem.class)); + } + @BuildStep(onlyIf = Never.class) void neverRunThisOne() { throw new IllegalStateException("Not supposed to run!"); diff --git a/integration-tests/test-extension/extension/deployment/src/main/resources/application.properties b/integration-tests/test-extension/extension/deployment/src/main/resources/application.properties index 1170bbeb28cf3..82ee3392f780a 100644 --- a/integration-tests/test-extension/extension/deployment/src/main/resources/application.properties +++ b/integration-tests/test-extension/extension/deployment/src/main/resources/application.properties @@ -184,3 +184,5 @@ my.prefix.bt.nested.oov=nested-1234+nested-5678 another.another-prefix.prop=5678 another.another-prefix.map.prop=5678 + +unremoveable.value=1234 diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnremoveableConfigMappingTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnremoveableConfigMappingTest.java new file mode 100644 index 0000000000000..600a1aa45f98c --- /dev/null +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnremoveableConfigMappingTest.java @@ -0,0 +1,28 @@ +package io.quarkus.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.extest.runtime.config.UnremovableMappingFromBuildItem; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.SmallRyeConfig; + +public class UnremoveableConfigMappingTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(UnremovableMappingFromBuildItem.class)); + + @Inject + SmallRyeConfig config; + + @Test + void unremoveableMapping() { + UnremovableMappingFromBuildItem mapping = config.getConfigMapping(UnremovableMappingFromBuildItem.class); + assertEquals("1234", mapping.value()); + } +} diff --git a/integration-tests/test-extension/extension/runtime/src/main/java/io/quarkus/extest/runtime/config/UnremovableMappingFromBuildItem.java b/integration-tests/test-extension/extension/runtime/src/main/java/io/quarkus/extest/runtime/config/UnremovableMappingFromBuildItem.java new file mode 100644 index 0000000000000..b844fdc10cd8e --- /dev/null +++ b/integration-tests/test-extension/extension/runtime/src/main/java/io/quarkus/extest/runtime/config/UnremovableMappingFromBuildItem.java @@ -0,0 +1,8 @@ +package io.quarkus.extest.runtime.config; + +import io.smallrye.config.ConfigMapping; + +@ConfigMapping(prefix = "unremoveable") +public interface UnremovableMappingFromBuildItem { + String value(); +} From 82edba602b76d5a78a1acd493f8def05493439bb Mon Sep 17 00:00:00 2001 From: Damon Sutherland Date: Sat, 3 Dec 2022 11:20:43 -0700 Subject: [PATCH 04/28] Adding additional logic to catch and address invalid JSON input. (cherry picked from commit 0785582523e64d2f7d2128fdd4eadb04ae8bc1d2) --- .../test/MessageBodyReaderTests.java | 274 ++++++++++++++++++ .../deployment/test/SimpleJsonResource.java | 7 +- .../ServerJacksonMessageBodyReader.java | 27 +- .../ClientJacksonMessageBodyReader.java | 21 ++ .../JacksonBasicMessageBodyReader.java | 8 +- 5 files changed, 325 insertions(+), 12 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java new file mode 100644 index 0000000000000..eaa5ec0f8bb30 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/MessageBodyReaderTests.java @@ -0,0 +1,274 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.container.CompletionCallback; +import javax.ws.rs.container.ConnectionCallback; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader; +import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; +import org.jboss.resteasy.reactive.server.spi.ContentType; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo; +import org.jboss.resteasy.reactive.server.spi.ServerHttpResponse; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIdentityReference; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; +import com.fasterxml.jackson.databind.exc.ValueInstantiationException; + +import io.quarkus.resteasy.reactive.jackson.runtime.serialisers.ServerJacksonMessageBodyReader; + +@SuppressWarnings("unchecked") +class MessageBodyReaderTests { + + static class CommonReaderTests { + private final AbstractJsonMessageBodyReader reader; + + public CommonReaderTests(AbstractJsonMessageBodyReader reader) { + this.reader = reader; + } + + void deserializeMissingToken() throws IOException { + var stream = new ByteArrayInputStream("{\"model\": \"model\", \"cost\": 2".getBytes(StandardCharsets.UTF_8)); + Object widget = new Widget("", 1d); + reader.readFrom((Class) widget.getClass(), null, null, null, null, stream); + } + + void deserializeMissingRequiredProperty() throws IOException { + // missing non-nullable property + var stream = new ByteArrayInputStream("{\"cost\": 2}".getBytes(StandardCharsets.UTF_8)); + Object widget = new Widget("", 1d); + reader.readFrom((Class) widget.getClass(), null, null, null, null, stream); + } + + void deserializeMissingReferenceProperty() throws IOException { + var json = "{\n" + + " \"id\" : 1,\n" + + " \"name\" : \"Learn HTML\",\n" + + " \"owner\" : 1\n" + // unresolved reference to student + "}"; + + var stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + Object book = new Book(1, null, null); + reader.readFrom((Class) book.getClass(), null, null, null, null, stream); + } + + void deserializeClassWithInvalidDefinition() throws IOException { + var json = "{\n" + + " \"arg\" : \"Learn HTML\"" + + "}"; + + var stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + Object invalid = new InvalidDefinition(null); + reader.readFrom((Class) invalid.getClass(), null, null, null, null, stream); + } + } + + @Nested + @DisplayName("JacksonMessageBodyReader") + class JacksonMessageBodyReaderTests { + private final CommonReaderTests tests = new CommonReaderTests(new JacksonBasicMessageBodyReader(new ObjectMapper())); + + @Test + void shouldThrowStreamReadException() { + assertThrows(StreamReadException.class, tests::deserializeMissingToken); + } + + @Test + void shouldThrowValueInstantiationException() { + assertThrows(ValueInstantiationException.class, tests::deserializeMissingRequiredProperty); + } + + @Test + void shouldThrowDatabindException() { + assertThrows(DatabindException.class, tests::deserializeMissingReferenceProperty); + } + + @Test + void shouldThrowInvalidDefinitionException() { + assertThrows(InvalidDefinitionException.class, tests::deserializeClassWithInvalidDefinition); + } + } + + @Nested + @DisplayName("ServerJacksonMessageBodyReader") + class ServerJacksonMessageBodyReaderTests { + private final CommonReaderTests tests = new CommonReaderTests(new ServerJacksonMessageBodyReader(new ObjectMapper())); + + @Test + void shouldThrowWebExceptionWithStreamReadExceptionCause() { + var e = assertThrows(WebApplicationException.class, tests::deserializeMissingToken); + assertThat(StreamReadException.class).isAssignableFrom(e.getCause().getClass()); + } + + @Test + void shouldThrowWebExceptionWithValueInstantiationExceptionCause() { + var e = assertThrows(WebApplicationException.class, tests::deserializeMissingRequiredProperty); + assertThat(ValueInstantiationException.class).isAssignableFrom(e.getCause().getClass()); + } + + @Test + void shouldThrowWebExceptionWithDatabindExceptionCause() { + var e = assertThrows(WebApplicationException.class, tests::deserializeMissingReferenceProperty); + assertThat(DatabindException.class).isAssignableFrom(e.getCause().getClass()); + } + + @Test + void shouldThrowInvalidDefinitionException() { + assertThrows(InvalidDefinitionException.class, tests::deserializeClassWithInvalidDefinition); + } + + @Test + void shouldThrowWebExceptionWithValueInstantiationExceptionCauseUsingServerRequestContext() throws IOException { + var reader = new ServerJacksonMessageBodyReader(new ObjectMapper()); + // missing non-nullable property + var stream = new ByteArrayInputStream("{\"cost\": 2}".getBytes(StandardCharsets.UTF_8)); + var context = new MockServerRequestContext(stream); + Object widget = new Widget("", 1d); + + try { + reader.readFrom((Class) widget.getClass(), null, MediaType.APPLICATION_JSON_TYPE, context); + } catch (WebApplicationException e) { + assertThat(ValueInstantiationException.class).isAssignableFrom(e.getCause().getClass()); + } + } + } + + static class InvalidDefinition { + // Note: Multiple constructors marked as JsonCreators should throw InvalidDefinitionException + + private final Object arg; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public InvalidDefinition(Object arg) { + this.arg = arg; + } + } + + static class Widget { + + public final String model; + public final double cost; + + @JsonCreator + public Widget( + @JsonProperty("model") String model, + @JsonProperty("cost") double cost) { + this.model = Objects.requireNonNull(model, "'model' must be supplied"); + this.cost = cost; + } + } + + @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") + static class Student { + public int id; + public int rollNo; + public String name; + public List books; + + Student(int id, int rollNo, String name) { + this.id = id; + this.rollNo = rollNo; + this.name = name; + this.books = new ArrayList<>(); + } + } + + @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") + static class Book { + @JsonProperty("id") + public int id; + @JsonProperty("name") + public String name; + + Book() { + // do nothing ... for Jackson + } + + Book(int id, String name, Student owner) { + this.id = id; + this.name = name; + this.owner = owner; + } + + @JsonIdentityReference(alwaysAsId = true) + @JsonProperty("owner") + public Student owner; + } + + private static class MockServerRequestContext implements ServerRequestContext { + private final InputStream stream; + + public MockServerRequestContext(InputStream stream) { + this.stream = stream; + } + + @Override + public void registerCompletionCallback(CompletionCallback callback) { + + } + + @Override + public void registerConnectionCallback(ConnectionCallback callback) { + + } + + @Override + public ServerHttpResponse serverResponse() { + return null; + } + + @Override + public InputStream getInputStream() { + return stream; + } + + @Override + public ContentType getResponseContentType() { + return null; + } + + @Override + public MediaType getResponseMediaType() { + return null; + } + + @Override + public OutputStream getOrCreateOutputStream() { + return null; + } + + @Override + public ResteasyReactiveResourceInfo getResteasyReactiveResourceInfo() { + return null; + } + + @Override + public void abortWith(Response response) { + + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java index 579d8f1428c0b..d76704b01e1d5 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/SimpleJsonResource.java @@ -14,6 +14,7 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; @@ -23,7 +24,6 @@ import org.jboss.resteasy.reactive.server.ServerExceptionMapper; import com.fasterxml.jackson.annotation.JsonView; -import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; @@ -42,8 +42,9 @@ public class SimpleJsonResource extends SuperClass { @ServerExceptionMapper - public Response handleParseException(JsonParseException jpe) { - return Response.status(Response.Status.BAD_REQUEST).entity(jpe.getMessage()).build(); + public Response handleParseException(WebApplicationException e) { + var cause = e.getCause() == null ? e : e.getCause(); + return Response.status(Response.Status.BAD_REQUEST).entity(cause.getMessage()).build(); } @GET diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/ServerJacksonMessageBodyReader.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/ServerJacksonMessageBodyReader.java index 99be966aa88bd..755eb1997c913 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/ServerJacksonMessageBodyReader.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/ServerJacksonMessageBodyReader.java @@ -17,8 +17,11 @@ import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; import com.fasterxml.jackson.databind.exc.MismatchedInputException; public class ServerJacksonMessageBodyReader extends JacksonBasicMessageBodyReader implements ServerMessageBodyReader { @@ -33,7 +36,27 @@ public Object readFrom(Class type, Type genericType, Annotation[] annota MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { return doReadFrom(type, genericType, entityStream); - } catch (MismatchedInputException e) { + } catch (MismatchedInputException | InvalidDefinitionException e) { + /* + * To extract additional details when running in dev mode or test mode, Quarkus previously offered the + * DefaultMismatchedInputException(Mapper). That mapper provides additional details about bad input, + * beyond Jackson's default, when running in Dev or Test mode. To preserve that behavior, we rethrow + * MismatchedInputExceptions we encounter. + * + * An InvalidDefinitionException is thrown when there is a problem with the way a type is + * set up/annotated for consumption by the Jackson API. We don't wrap it in a WebApplicationException + * (as a Server Error), since unhandled exceptions will end up as a 500 anyway. In addition, this + * allows built-in features like the NativeInvalidDefinitionExceptionMapper to be registered and + * communicate potential Jackson integration issues, and potential solutions for resolving them. + */ + throw e; + } catch (StreamReadException | DatabindException e) { + /* + * As JSON is evaluated, it can be invalid due to one of two reasons: + * 1) Malformed JSON. Un-parsable JSON results in a StreamReadException + * 2) Valid JSON that violates some binding constraint, i.e., a required property, mismatched data types, etc. + * Violations of these types are captured via a DatabindException. + */ throw new WebApplicationException(e, Response.Status.BAD_REQUEST); } } @@ -51,7 +74,7 @@ public boolean isReadable(Class type, Type genericType, ResteasyReactiveResou @Override public Object readFrom(Class type, Type genericType, MediaType mediaType, ServerRequestContext context) throws WebApplicationException, IOException { - return doReadFrom(type, genericType, context.getInputStream()); + return readFrom(type, genericType, null, mediaType, null, context.getInputStream()); } private Object doReadFrom(Class type, Type genericType, InputStream entityStream) throws IOException { diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java index d2815b5b6eebb..662a9a77ee378 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyReader.java @@ -1,15 +1,26 @@ package io.quarkus.rest.client.reactive.jackson.runtime.serialisers; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import javax.inject.Inject; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import org.jboss.resteasy.reactive.ClientWebApplicationException; import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; @@ -23,6 +34,16 @@ public ClientJacksonMessageBodyReader(ObjectMapper mapper) { super(mapper); } + @Override + public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + try { + return super.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream); + } catch (StreamReadException | DatabindException e) { + throw new ClientWebApplicationException(e, Response.Status.BAD_REQUEST); + } + } + @Override public void handle(RestClientRequestContext requestContext) { this.context = requestContext; diff --git a/independent-projects/resteasy-reactive/server/jackson/src/main/java/org/jboss/resteasy/reactive/server/jackson/JacksonBasicMessageBodyReader.java b/independent-projects/resteasy-reactive/server/jackson/src/main/java/org/jboss/resteasy/reactive/server/jackson/JacksonBasicMessageBodyReader.java index f20abb80ac7e4..dbf2cd8b8b91c 100644 --- a/independent-projects/resteasy-reactive/server/jackson/src/main/java/org/jboss/resteasy/reactive/server/jackson/JacksonBasicMessageBodyReader.java +++ b/independent-projects/resteasy-reactive/server/jackson/src/main/java/org/jboss/resteasy/reactive/server/jackson/JacksonBasicMessageBodyReader.java @@ -9,14 +9,12 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import org.jboss.resteasy.reactive.common.providers.serialisers.AbstractJsonMessageBodyReader; import org.jboss.resteasy.reactive.common.util.EmptyInputStream; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; public class JacksonBasicMessageBodyReader extends AbstractJsonMessageBodyReader { @@ -30,11 +28,7 @@ public JacksonBasicMessageBodyReader(ObjectMapper mapper) { @Override public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - try { - return doReadFrom(type, genericType, entityStream); - } catch (MismatchedInputException e) { - throw new WebApplicationException(e, Response.Status.BAD_REQUEST); - } + return doReadFrom(type, genericType, entityStream); } protected ObjectReader getEffectiveReader() { From 959e0f73f45fa49caa83dbf6a00f223415278819 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 5 Dec 2022 10:21:08 +0200 Subject: [PATCH 05/28] Introduce the UniAsserter API to the Hibernate Reactive documentation Closes: #29617 (cherry picked from commit 06b13d5e4fbbd51731db32cbc9e3354d3f23c4f9) --- .../src/main/asciidoc/hibernate-reactive.adoc | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/src/main/asciidoc/hibernate-reactive.adoc b/docs/src/main/asciidoc/hibernate-reactive.adoc index 744a298b0014c..890e27eb0831d 100644 --- a/docs/src/main/asciidoc/hibernate-reactive.adoc +++ b/docs/src/main/asciidoc/hibernate-reactive.adoc @@ -216,6 +216,40 @@ You can also inject an instance of `Uni` using the exact same me Uni session; ---- +=== Testing + +Using Hibernate Reactive in a `@QuarkusTest` is slightly more involved than using Hibernate ORM due to the asynchronous nature of the APIs and the fact that all operations need to run on a Vert.x Event Loop. + +Two components are necessary to write these tests: + +* The use of `@io.quarkus.test.vertx.RunOnVertxContext` or `@io.quarkus.test.TestReactiveTransaction` on the test methods +* The use of `io.quarkus.test.vertx.UniAsserter` as a test method parameter. + +IMPORTANT: These classes are provided by the `quarkus-test-vertx` dependency. + +A very simple example usage looks like: + +[source,java] +---- +@QuarkusTest +public class SomeTest { + + @Inject + Mutiny.SessionFactory sessionFactory; + + @Test + @RunOnVertxContext + public void testQuery(UniAsserter asserter) { + asserter.assertThat(() -> sessionFactory.withSession(s -> s.createQuery( + "from Gift g where g.name = :name").setParameter("name", "Lego").getResultList()), + list -> org.junit.jupiter.api.Assertions.assertEquals(list.size(), 1)); + } + +} +---- + +NOTE: See the Javadoc of `UniAsserter` for a full description of the various methods that can be used for creating assertions. + [[hr-limitations]] == Limitations and other things you should know From 91f7485418f05ced908640cae02667419fff1676 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 2 Nov 2022 08:15:14 +0100 Subject: [PATCH 06/28] Rest Client: Add property to skip hostname verification Before these changes, we only can disable the hostname verification in Rest Client classic by providing the following property: ``` quarkus.rest-client.extensions-api.hostname-verifier=io.quarkus.restclient.NoopHostnameVerifier ``` However, this is not working in Rest Client reactive because setting a hostname verifier strategy is not supported by the Vert-x HTTP Client. With these changes, we have added a new property in both Rest Client classic and reactive `quarkus.rest-client.extensions-api.verify-host=true or false`. In Rest Client classic, when disabling the verify host, internally it will add the `NoopHostnameVerifier` strategy. In Rest Client reactive, it will properly configure the Vert.x HTTP client to disable the hostname verification. Therefore, in both Rest Client implementations (classic and reactive), the behaviour is the same. Fix https://github.com/quarkusio/quarkus/issues/27901 (cherry picked from commit 941d3a6f7a1857d01a1a2b0eada67e3628895c0c) --- .../main/asciidoc/rest-client-reactive.adoc | 13 ++++++++++ docs/src/main/asciidoc/rest-client.adoc | 18 ++++++++++++- .../restclient/config/RestClientConfig.java | 9 +++++++ ...ClientFallbackConfigSourceInterceptor.java | 1 + .../restclient/config/RestClientsConfig.java | 8 ++++++ .../restclient/runtime/RestClientBase.java | 8 ++++++ .../runtime/RestClientBuilderImpl.java | 6 +++++ .../runtime/RestClientCDIDelegateBuilder.java | 3 +++ .../client/impl/ClientBuilderImpl.java | 7 +++++ .../rest-client-reactive/pom.xml | 19 ++++++++++++++ .../client/main/ClientCallingResource.java | 7 +++++ .../main/wronghost/WrongHostClient.java | 18 +++++++++++++ .../src/main/resources/application.properties | 6 ++++- .../wronghost/ExternalWrongHostTestCase.java | 20 ++++++++++++++ .../trustall/ExternalTlsTrustAllTestCase.java | 2 ++ ...ava => BaseExternalWrongHostTestCase.java} | 7 +---- ...ostTestResourceUsingHostnameVerifier.java} | 2 +- ...lWrongHostTestResourceUsingVerifyHost.java | 26 +++++++++++++++++++ ...rnalWrongHostUsingHostnameVerifierIT.java} | 2 +- ...rongHostUsingHostnameVerifierTestCase.java | 9 +++++++ ...ernalWrongHostUsingVerifyHostTestCase.java | 9 +++++++ 21 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostClient.java create mode 100644 integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java rename integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/{ExternalWrongHostTestCase.java => BaseExternalWrongHostTestCase.java} (79%) rename integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/{ExternalWrongHostTestResource.java => ExternalWrongHostTestResourceUsingHostnameVerifier.java} (87%) create mode 100644 integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingVerifyHost.java rename integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/{ExternalWrongHostIT.java => ExternalWrongHostUsingHostnameVerifierIT.java} (53%) create mode 100644 integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierTestCase.java create mode 100644 integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingVerifyHostTestCase.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index 08cc292a1fcc4..69d714a5beb28 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -283,6 +283,19 @@ quarkus.rest-client.extensions-api.url=https://stage.code.quarkus.io/api quarkus.rest-client.extensions-api.scope=javax.inject.Singleton ---- +=== Disabling Hostname Verification + +To disable the SSL hostname verification for a specific REST client, add the following property to your configuration: + +[source,properties] +---- +quarkus.rest-client.extensions-api.verify-host=false +---- +[WARNING] +==== +This setting should not be used in production as it will disable the SSL hostname verification. +==== + == Create the JAX-RS resource Create the `src/main/java/org/acme/rest/client/ExtensionsResource.java` file with the following content: diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index 4e51d0674663a..97990eb23ff1f 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -232,8 +232,24 @@ To disable the SSL hostname verification for a specific REST client, add the fol [source,properties] ---- -quarkus.rest-client.extensions-api.hostname-verifier=io.quarkus.restclient.NoopHostnameVerifier +quarkus.rest-client.extensions-api.verify-host=false ---- +[WARNING] +==== +This setting should not be used in production as it will disable the SSL hostname verification. +==== + +Moreover, you can configure a REST client to use your custom hostname verify strategy. All you need to do is to provide a class that implements the interface `javax.net.ssl.HostnameVerifier` and add the following property to your configuration: + +[source,properties] +---- +quarkus.rest-client.extensions-api.hostname-verifier= +---- + +[NOTE] +==== +Quarkus REST client provides an embedded hostname verifier strategy to disable the hostname verification called `io.quarkus.restclient.NoopHostnameVerifier`. +==== === Disabling SSL verifications diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java index c2ae9b2fc6df0..881a3fd2cef2d 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java @@ -31,6 +31,7 @@ public class RestClientConfig { EMPTY.proxyPassword = Optional.empty(); EMPTY.nonProxyHosts = Optional.empty(); EMPTY.queryParamStyle = Optional.empty(); + EMPTY.verifyHost = Optional.empty(); EMPTY.trustStore = Optional.empty(); EMPTY.trustStorePassword = Optional.empty(); EMPTY.trustStoreType = Optional.empty(); @@ -134,6 +135,12 @@ public class RestClientConfig { @ConfigItem public Optional queryParamStyle; + /** + * Set whether hostname verification is enabled. + */ + @ConfigItem + public Optional verifyHost; + /** * The trust store location. Can point to either a classpath resource or a file. */ @@ -246,6 +253,7 @@ public static RestClientConfig load(String configKey) { instance.proxyPassword = getConfigValue(configKey, "proxy-password", String.class); instance.nonProxyHosts = getConfigValue(configKey, "non-proxy-hosts", String.class); instance.queryParamStyle = getConfigValue(configKey, "query-param-style", QueryParamStyle.class); + instance.verifyHost = getConfigValue(configKey, "verify-host", Boolean.class); instance.trustStore = getConfigValue(configKey, "trust-store", String.class); instance.trustStorePassword = getConfigValue(configKey, "trust-store-password", String.class); instance.trustStoreType = getConfigValue(configKey, "trust-store-type", String.class); @@ -279,6 +287,7 @@ public static RestClientConfig load(Class interfaceClass) { instance.proxyPassword = getConfigValue(interfaceClass, "proxy-password", String.class); instance.nonProxyHosts = getConfigValue(interfaceClass, "non-proxy-hosts", String.class); instance.queryParamStyle = getConfigValue(interfaceClass, "query-param-style", QueryParamStyle.class); + instance.verifyHost = getConfigValue(interfaceClass, "verify-host", Boolean.class); instance.trustStore = getConfigValue(interfaceClass, "trust-store", String.class); instance.trustStorePassword = getConfigValue(interfaceClass, "trust-store-password", String.class); instance.trustStoreType = getConfigValue(interfaceClass, "trust-store-type", String.class); diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientFallbackConfigSourceInterceptor.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientFallbackConfigSourceInterceptor.java index 02986f7bf1159..d407269ba7fb9 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientFallbackConfigSourceInterceptor.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientFallbackConfigSourceInterceptor.java @@ -31,6 +31,7 @@ public class RestClientFallbackConfigSourceInterceptor extends FallbackConfigSou CLIENT_PROPERTIES.put("connect-timeout", "connectTimeout"); CLIENT_PROPERTIES.put("read-timeout", "readTimeout"); CLIENT_PROPERTIES.put("hostname-verifier", "hostnameVerifier"); + CLIENT_PROPERTIES.put("verify-host", "verifyHost"); CLIENT_PROPERTIES.put("trust-store", "trustStore"); CLIENT_PROPERTIES.put("trust-store-password", "trustStorePassword"); CLIENT_PROPERTIES.put("trust-store-type", "trustStoreType"); diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java index 99f4c9c117f9e..cd400ada1b8cf 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java @@ -217,6 +217,14 @@ public class RestClientsConfig { @ConfigItem public Optional queryParamStyle; + /** + * Set whether hostname verification is enabled. + * + * Can be overwritten by client-specific settings. + */ + @ConfigItem + public Optional verifyHost; + /** * The trust store location. Can point to either a classpath resource or a file. * diff --git a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java index 4380c9ef7b333..9b8136cc6e9fb 100644 --- a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java +++ b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java @@ -24,6 +24,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; +import io.quarkus.restclient.NoopHostnameVerifier; import io.quarkus.restclient.config.RestClientConfig; import io.quarkus.restclient.config.RestClientsConfig; @@ -149,6 +150,13 @@ protected void configureSsl(RestClientBuilder builder) { clientConfigByConfigKey().hostnameVerifier, configRoot.hostnameVerifier); if (hostnameVerifier.isPresent()) { registerHostnameVerifier(hostnameVerifier.get(), builder); + } else { + // If `verify-host` is disabled, we configure the client using the `NoopHostnameVerifier` verifier. + Optional verifyHost = oneOf(clientConfigByClassName().verifyHost, clientConfigByConfigKey().verifyHost, + configRoot.verifyHost); + if (verifyHost.isPresent() && !verifyHost.get()) { + registerHostnameVerifier(NoopHostnameVerifier.class.getName(), builder); + } } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java index 81c315c22837d..a6f18ac1f16eb 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java @@ -89,6 +89,11 @@ public RestClientBuilderImpl sslContext(SSLContext sslContext) { return this; } + public RestClientBuilderImpl verifyHost(boolean verifyHost) { + clientBuilder.verifyHost(verifyHost); + return this; + } + @Override public RestClientBuilderImpl trustStore(KeyStore trustStore) { clientBuilder.trustStore(trustStore); @@ -319,6 +324,7 @@ public T build(Class aClass) throws IllegalStateException, RestClientDefi .orElse(false); clientBuilder.trustAll(trustAll); + restClientsConfig.verifyHost.ifPresent(clientBuilder::verifyHost); String userAgent = (String) getConfiguration().getProperty(QuarkusRestClientProperties.USER_AGENT); if (userAgent != null) { diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index d5f998178a40c..da933186bb855 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -201,6 +201,9 @@ private void configureSsl(RestClientBuilderImpl builder) { if (maybeHostnameVerifier.isPresent()) { registerHostnameVerifier(maybeHostnameVerifier.get(), builder); } + + oneOf(clientConfigByClassName().verifyHost, clientConfigByConfigKey().verifyHost, configRoot.verifyHost) + .ifPresent(builder::verifyHost); } private void registerHostnameVerifier(String verifier, RestClientBuilder builder) { diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index 9f6e50abe605d..9d323b012a410 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -59,6 +59,7 @@ public class ClientBuilderImpl extends ClientBuilder { private boolean followRedirects; private boolean trustAll; + private boolean verifyHost; private LoggingScope loggingScope; private Integer loggingBodySize = 100; @@ -178,6 +179,7 @@ public ClientImpl build() { HttpClientOptions options = Optional.ofNullable(configuration.getFromContext(HttpClientOptions.class)) .orElseGet(HttpClientOptions::new); + options.setVerifyHost(verifyHost); if (trustAll) { options.setTrustAll(true); options.setVerifyHost(false); @@ -347,6 +349,11 @@ public ClientBuilderImpl trustAll(boolean trustAll) { return this; } + public ClientBuilderImpl verifyHost(boolean verifyHost) { + this.verifyHost = verifyHost; + return this; + } + public ClientBuilderImpl setUserAgent(String userAgent) { this.userAgent = userAgent; return this; diff --git a/integration-tests/rest-client-reactive/pom.xml b/integration-tests/rest-client-reactive/pom.xml index 030ba003a3c9a..f7fd7db83b29a 100644 --- a/integration-tests/rest-client-reactive/pom.xml +++ b/integration-tests/rest-client-reactive/pom.xml @@ -14,6 +14,8 @@ ${project.build.directory}/self-signed.p12 changeit + ${project.build.directory}/wrong-host.p12 + changeit @@ -175,6 +177,23 @@ LEAF + + wrong-host-truststore + generate-test-resources + + generate-truststore + + + PKCS12 + ${wrong-host.trust-store} + ${wrong-host.trust-store-password} + + wrong.host.badssl.com:443 + + true + LEAF + + diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 1e311fef17815..9e88e5bd86c63 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -22,6 +22,7 @@ import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; import io.quarkus.it.rest.client.main.selfsigned.ExternalSelfSignedClient; +import io.quarkus.it.rest.client.main.wronghost.WrongHostClient; import io.smallrye.mutiny.Uni; import io.vertx.core.Future; import io.vertx.core.json.Json; @@ -48,6 +49,9 @@ public class ClientCallingResource { @RestClient ExternalSelfSignedClient externalSelfSignedClient; + @RestClient + WrongHostClient wrongHostClient; + @Inject InMemorySpanExporter inMemorySpanExporter; @@ -172,6 +176,9 @@ void init(@Observes Router router) { router.get("/self-signed").blockingHandler( rc -> rc.response().setStatusCode(200).end(String.valueOf(externalSelfSignedClient.invoke().getStatus()))); + + router.get("/wrong-host").blockingHandler( + rc -> rc.response().setStatusCode(200).end(String.valueOf(wrongHostClient.invoke().getStatus()))); } private Future success(RoutingContext rc, String body) { diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostClient.java new file mode 100644 index 0000000000000..5ad6b037d7b18 --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/wronghost/WrongHostClient.java @@ -0,0 +1,18 @@ +package io.quarkus.it.rest.client.main.wronghost; + +import javax.ws.rs.GET; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "https://wrong.host.badssl.com/", configKey = "wrong-host") +public interface WrongHostClient { + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Retry(delay = 1000) + Response invoke(); +} diff --git a/integration-tests/rest-client-reactive/src/main/resources/application.properties b/integration-tests/rest-client-reactive/src/main/resources/application.properties index 03c013f85ce30..4bf8d7c8f6404 100644 --- a/integration-tests/rest-client-reactive/src/main/resources/application.properties +++ b/integration-tests/rest-client-reactive/src/main/resources/application.properties @@ -2,6 +2,10 @@ w-exception-mapper/mp-rest/url=${test.url} w-fault-tolerance/mp-rest/url=${test.url} io.quarkus.it.rest.client.main.ParamClient/mp-rest/url=${test.url} io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} -# HTTPS +# Self-Signed client quarkus.rest-client.self-signed.trust-store=${self-signed.trust-store} quarkus.rest-client.self-signed.trust-store-password=${self-signed.trust-store-password} +# Wrong Host client +quarkus.rest-client.wrong-host.trust-store=${wrong-host.trust-store} +quarkus.rest-client.wrong-host.trust-store-password=${wrong-host.trust-store-password} +quarkus.rest-client.wrong-host.verify-host=false diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java new file mode 100644 index 0000000000000..d963c850c0dce --- /dev/null +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java @@ -0,0 +1,20 @@ +package io.quarkus.it.rest.client.wronghost; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ExternalWrongHostTestCase { + @Test + public void restClient() { + when() + .get("/wrong-host") + .then() + .statusCode(200) + .body(is("200")); + } +} diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/trustall/ExternalTlsTrustAllTestCase.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/trustall/ExternalTlsTrustAllTestCase.java index ba89527182f5d..99e93df420eaf 100644 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/trustall/ExternalTlsTrustAllTestCase.java +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/trustall/ExternalTlsTrustAllTestCase.java @@ -5,11 +5,13 @@ import org.junit.jupiter.api.Test; +import io.quarkus.it.rest.client.wronghost.ExternalWrongHostTestResourceUsingHostnameVerifier; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(ExternalTlsTrustAllTestResource.class) +@QuarkusTestResource(value = ExternalWrongHostTestResourceUsingHostnameVerifier.class, restrictToAnnotatedClass = true) public class ExternalTlsTrustAllTestCase { @Test diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/BaseExternalWrongHostTestCase.java similarity index 79% rename from integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java rename to integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/BaseExternalWrongHostTestCase.java index 5cfc0380d7c2e..3e3a5a85599d6 100644 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestCase.java +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/BaseExternalWrongHostTestCase.java @@ -7,12 +7,7 @@ import org.junit.jupiter.api.Test; -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -@QuarkusTestResource(ExternalWrongHostTestResource.class) -public class ExternalWrongHostTestCase { +public abstract class BaseExternalWrongHostTestCase { @Test public void restClient() { diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResource.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingHostnameVerifier.java similarity index 87% rename from integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResource.java rename to integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingHostnameVerifier.java index 723ea722b6638..e9f7cfe023c9e 100644 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResource.java +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingHostnameVerifier.java @@ -9,7 +9,7 @@ /** * The only point of this class is to propagate the properties when running the native tests */ -public class ExternalWrongHostTestResource implements QuarkusTestResourceLifecycleManager { +public class ExternalWrongHostTestResourceUsingHostnameVerifier implements QuarkusTestResourceLifecycleManager { @Override public Map start() { diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingVerifyHost.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingVerifyHost.java new file mode 100644 index 0000000000000..8ee70c21f8db0 --- /dev/null +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostTestResourceUsingVerifyHost.java @@ -0,0 +1,26 @@ +package io.quarkus.it.rest.client.wronghost; + +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +/** + * The only point of this class is to propagate the properties when running the native tests + */ +public class ExternalWrongHostTestResourceUsingVerifyHost implements QuarkusTestResourceLifecycleManager { + + @Override + public Map start() { + Map result = new HashMap<>(); + result.put("wrong-host/mp-rest/trustStore", System.getProperty("rest-client.trustStore")); + result.put("wrong-host/mp-rest/trustStorePassword", System.getProperty("rest-client.trustStorePassword")); + result.put("wrong-host/mp-rest/verifyHost", Boolean.FALSE.toString()); + return result; + } + + @Override + public void stop() { + + } +} diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostIT.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierIT.java similarity index 53% rename from integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostIT.java rename to integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierIT.java index ab7625fee03e1..4b1cad5c37f40 100644 --- a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostIT.java +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierIT.java @@ -3,5 +3,5 @@ import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest -public class ExternalWrongHostIT extends ExternalWrongHostTestCase { +public class ExternalWrongHostUsingHostnameVerifierIT extends ExternalWrongHostUsingHostnameVerifierTestCase { } diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierTestCase.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierTestCase.java new file mode 100644 index 0000000000000..382b68e36ceff --- /dev/null +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingHostnameVerifierTestCase.java @@ -0,0 +1,9 @@ +package io.quarkus.it.rest.client.wronghost; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@QuarkusTestResource(value = ExternalWrongHostTestResourceUsingHostnameVerifier.class, restrictToAnnotatedClass = true) +public class ExternalWrongHostUsingHostnameVerifierTestCase extends BaseExternalWrongHostTestCase { +} diff --git a/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingVerifyHostTestCase.java b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingVerifyHostTestCase.java new file mode 100644 index 0000000000000..9ad9ce8d3930f --- /dev/null +++ b/integration-tests/rest-client/src/test/java/io/quarkus/it/rest/client/wronghost/ExternalWrongHostUsingVerifyHostTestCase.java @@ -0,0 +1,9 @@ +package io.quarkus.it.rest.client.wronghost; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@QuarkusTestResource(value = ExternalWrongHostTestResourceUsingVerifyHost.class, restrictToAnnotatedClass = true) +public class ExternalWrongHostUsingVerifyHostTestCase extends BaseExternalWrongHostTestCase { +} From a3c0e70300ebd9aa2b67fdc89079310d9f4f16e3 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 2 Dec 2022 17:23:24 +0100 Subject: [PATCH 07/28] Upgrade to Jandex 3.0.5 (cherry picked from commit ddc481f69a4b584ed84ea2554d8594530dd74a8f) --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- independent-projects/arc/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 2 +- independent-projects/qute/pom.xml | 2 +- independent-projects/resteasy-reactive/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 0734e337a6bf4..9ff81cbdb108c 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -18,7 +18,7 @@ 1.0.2.3 1.0.14 3.0.2 - 3.0.4 + 3.0.5 4.7.7.Final 0.33.0 0.2.4 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 4a86fa60338ad..591a45865970a 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -34,7 +34,7 @@ ${version.surefire.plugin} - 3.0.4 + 3.0.5 1.0.0 2.5.7 diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index 356733efabf4c..ab7cdca02cd3c 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -42,7 +42,7 @@ 2.0.2 1.3.3 - 3.0.4 + 3.0.5 5.9.1 3.8.6 3.23.1 diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index bf693209f566c..193a6b2c70c11 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -36,7 +36,7 @@ 11 3.0.0-M7 1.6.8 - 3.0.4 + 3.0.5 3.23.1 diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 856eefad28561..9deb1e5dff1aa 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -42,7 +42,7 @@ 11 5.9.1 3.23.1 - 3.0.4 + 3.0.5 1.4.0.Final 3.5.0.Final 3.0.0-M7 diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index ac543a5bbd098..e8979f9a7d211 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -41,7 +41,7 @@ 11 2.0.2 - 3.0.4 + 3.0.5 1.12.12 5.9.1 3.8.6 diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index 444edc532844a..c5bdaf4912b75 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -62,7 +62,7 @@ 21 2.11.0 1.13.2 - 3.0.4 + 3.0.5 registry-client From 47adaf9d72dd92eab2dee3300f14fde962ce576b Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 5 Dec 2022 10:00:40 +0100 Subject: [PATCH 08/28] Add a paragraph about tiered compilation in the snapstart guide. (cherry picked from commit 2f9ff6f7d0b57afb480f9d364548179759a3b7a1) --- docs/src/main/asciidoc/amazon-snapstart.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/src/main/asciidoc/amazon-snapstart.adoc b/docs/src/main/asciidoc/amazon-snapstart.adoc index 79b8122887337..89358253766f3 100644 --- a/docs/src/main/asciidoc/amazon-snapstart.adoc +++ b/docs/src/main/asciidoc/amazon-snapstart.adoc @@ -185,3 +185,10 @@ public class HelloPriming implements Resource { ---- WARNING: Restoration is limited to 2 seconds. + +== TieredCompilation + +It is also recommended to use _tiered compilation_ when using SnapStart. +To achieve this, set the `JAVA_TOOL_OPTIONS` environment property to `-XX:+TieredCompilation -XX:TieredStopAtLevel=1`. + +TIP: `TieredCompilation` can also be interesting for regular Lambda functions. \ No newline at end of file From c190ed854573140cbeed0ca5b369238cf84527c0 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 5 Dec 2022 09:33:57 +0100 Subject: [PATCH 09/28] Revert "Reactive Rest Client closing connections after server failures" This reverts commit 23f9abc6e13994edb8b3ab99cf5cf66abb3c604a. (cherry picked from commit bd57642ab2d5451a5652e0cf2f318f1cd9c402c1) --- .../client/reactive/BasicRestClientTest.java | 22 --------------- .../rest/client/reactive/HelloClient2.java | 7 ----- .../rest/client/reactive/HelloResource.java | 19 ------------- .../handlers/ClientSendRequestHandler.java | 27 ++++++++----------- 4 files changed, 11 insertions(+), 64 deletions(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java index ab885f48c3866..3148c53acf512 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java @@ -1,10 +1,7 @@ package io.quarkus.rest.client.reactive; -import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import java.time.Duration; import java.util.Map; import java.util.Set; @@ -75,23 +72,4 @@ void shouldMapQueryParamsWithSpecialCharacters() { assertThat(map.get("p5")).isEqualTo("5"); assertThat(map.get("p6")).isEqualTo("6"); } - - /** - * Test to reproduce https://github.com/quarkusio/quarkus/issues/28818. - */ - @Test - void shouldCloseConnectionsWhenFailures() { - // It's using 30 seconds because it's the default timeout to release connections. This timeout should not be taken into - // account when there are failures, and we should be able to call 3 times to the service without waiting. - await().atMost(Duration.ofSeconds(30)) - .until(() -> { - for (int call = 0; call < 3; call++) { - given() - .when().get("/hello/callClientForImageInfo") - .then() - .statusCode(500); - } - return true; - }); - } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClient2.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClient2.java index 2c2c31eab3473..660c55a0260d9 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClient2.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClient2.java @@ -4,7 +4,6 @@ import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; -import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @@ -24,10 +23,4 @@ public interface HelloClient2 { @GET @Path("delay") Uni delay(); - - @POST - @Path("/imageInfo") - @Consumes("image/gif") - @Produces(MediaType.TEXT_PLAIN) - String imageInfo(byte[] imageFile); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloResource.java index 65b0dc25760c7..f298c0b303011 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloResource.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloResource.java @@ -78,23 +78,4 @@ public Uni delay() { return Uni.createFrom().item("Hello") .onItem().delayIt().by(Duration.ofMillis(500)); } - - @Path("callClientForImageInfo") - @GET - public String callClientForImageInfo() { - int size = 1024 * 1024 * 5; - - byte[] buffer = new byte[size]; - - //Should provoke 415 Unsupported Media Type - return client2.imageInfo(buffer); - } - - @POST - @Consumes({ "image/jpeg", "image/png" }) - @Path("/imageInfo") - @Produces(MediaType.TEXT_PLAIN) - public String imageInfo(byte[] imageFile) { - throw new IllegalStateException("This method should never be invoked"); - } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java index 2bf3f434ecb41..8d1d7d62c3cbb 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java @@ -20,7 +20,6 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import javax.ws.rs.core.Variant; import org.jboss.logging.Logger; @@ -244,11 +243,6 @@ public void handle(HttpClientResponse clientResponse) { } } - if (Response.Status.Family.familyOf(status) != Response.Status.Family.SUCCESSFUL) { - httpClientRequest.connection().close(); - requestContext.resume(); - } - if (isResponseMultipart(requestContext)) { QuarkusMultipartResponseDecoder multipartDecoder = new QuarkusMultipartResponseDecoder( clientResponse); @@ -372,16 +366,17 @@ public void handle(Buffer buffer) { requestContext.resume(t); } } - }).onFailure(new Handler<>() { - @Override - public void handle(Throwable failure) { - if (failure instanceof IOException) { - requestContext.resume(new ProcessingException(failure)); - } else { - requestContext.resume(failure); - } - } - }); + }) + .onFailure(new Handler<>() { + @Override + public void handle(Throwable failure) { + if (failure instanceof IOException) { + requestContext.resume(new ProcessingException(failure)); + } else { + requestContext.resume(failure); + } + } + }); } private boolean isResponseMultipart(RestClientRequestContext requestContext) { From 9032b25d88f5028488ba8b16367e2b8815681390 Mon Sep 17 00:00:00 2001 From: Josef Andersson Date: Fri, 2 Dec 2022 16:02:26 +0100 Subject: [PATCH 10/28] Add further details about podman This adds further details about the fix and the discussion in https://github.com/quarkusio/quarkus/pull/29562. It clearifies that alias is also fine instead of podman-docker, just redirects the user to podman for install instructions ( they change over time, and more linux variant are shown there, no need duplicate and maintain), and finally, it shows a way of running podman as service without systemd (relevant on wsl up until recently for example, and users a most likely stuck with slightly older wsl's in a few places). Signed-off-by: Josef Andersson (cherry picked from commit 9e85d7e36fcbee9c06d3caf4467ff3cb39cd1521) --- docs/src/main/asciidoc/podman.adoc | 36 ++++++++++++------------------ 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/docs/src/main/asciidoc/podman.adoc b/docs/src/main/asciidoc/podman.adoc index 3dc68c1c3e842..6d83e73b80fcd 100644 --- a/docs/src/main/asciidoc/podman.adoc +++ b/docs/src/main/asciidoc/podman.adoc @@ -71,23 +71,8 @@ This action only needs to be done once. === Linux The Podman package is available in several Linux distributions. -Podman can be used the same way as Docker with the `podman-docker` package. -To install it for your OS, please refer to the https://podman.io/getting-started/installation[Podman installation guide]. -Below is the short installation instruction for popular Linux distributions: - -==== Fedora - -[source,bash] ----- -sudo dnf install podman podman-docker docker-compose ----- - -==== Ubuntu (21.04 and later) - -[source,bash] ----- -sudo apt install podman podman-docker docker-compose ----- +Podman can in most cases be used as an drop-in-replacement for Docker, either with the `podman-docker` package, or using an alias (`alias docker=podman`). +To install it for your Linux OS, please refer to the https://podman.io/getting-started/installation#installing-on-linux[Podman installation guide]. === Setting DOCKER_HOST on Linux @@ -96,17 +81,24 @@ On Linux, the REST API Unix socket is, by default, restricted to only allow the This prevents someone from using a container to achieve a privilege escalation on the system. While these restrictions can be softened to allow a special group instead of just root, the recommended approach is to use rootless Podman on Linux. To use rootless Podman, you need to set a `DOCKER_HOST` environment variable to point to the user-specific socket. -In both cases, you need to start the REST API by enabling the Podman socket service through systemd. -[source] +NOTE: In both cases, you need to start the REST API by enabling the Podman socket service through systemd, or at least by making sure Podman is running as a service. + +[source,bash] ---- -# Enable the podman socket with Docker REST API (only needs to be done once) +# Example 1: Enable the podman socket with Docker REST API with systemd (only needs to be done once) systemctl --user enable podman.socket --now ---- +[source,bash] +---- +# Example 2: Enable the podman socket with Docker REST API on a system where systemd is not running (WSL etc) +podman system service --time=0 +---- + Then, you can obtain the path of the socket with the following command: -[source] +[source,bash] ---- $ podman info | grep -A2 'remoteSocket' @@ -117,7 +109,7 @@ remoteSocket: Setting the `DOCKER_HOST` environment variable must be done every time or added to the profile: -[source] +[source,bash] ---- export DOCKER_HOST=unix:///path/to/podman.sock <1> ---- From b6fb71c4602abb42b96934aaa7f9df074d406db5 Mon Sep 17 00:00:00 2001 From: brunobat Date: Tue, 18 Oct 2022 19:30:43 +0100 Subject: [PATCH 11/28] Fix vertx otel sdk reload in devmode with Delegate on tracer factory (cherry picked from commit 71c6f7d7df8f85a866e019f6ced1ceb08a74bb05) --- .../deployment/OpenTelemetryProcessor.java | 32 ++--- .../deployment/tracing/TracerProcessor.java | 59 --------- .../InstrumentationProcessor.java | 115 ++++++++++++++++++ .../runtime/config/OpenTelemetryConfig.java | 4 +- .../runtime/tracing/TracerRecorder.java | 20 --- .../InstrumentationRecorder.java | 66 ++++++++++ .../vertx/HttpInstrumenterVertxTracer.java | 2 +- .../vertx/InstrumenterVertxTracer.java | 2 +- ...enTelemetryVertxTracingDevModeFactory.java | 84 +++++++++++++ .../OpenTelemetryVertxTracingFactory.java | 19 ++- 10 files changed, 284 insertions(+), 119 deletions(-) create mode 100644 extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java create mode 100644 extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingDevModeFactory.java diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java index f22257e9c1d7b..9cda1e84141f3 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java @@ -19,14 +19,11 @@ import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.InterceptorBindingRegistrar; -import io.quarkus.deployment.Capabilities; -import io.quarkus.deployment.Capability; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; @@ -36,8 +33,7 @@ import io.quarkus.opentelemetry.runtime.QuarkusContextStorage; import io.quarkus.opentelemetry.runtime.config.OpenTelemetryConfig; import io.quarkus.opentelemetry.runtime.tracing.cdi.WithSpanInterceptor; -import io.quarkus.opentelemetry.runtime.tracing.intrumentation.reactivemessaging.ReactiveMessagingTracingDecorator; -import io.quarkus.opentelemetry.runtime.tracing.intrumentation.restclient.OpenTelemetryClientFilter; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.InstrumentationRecorder; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.RuntimeValue; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; @@ -130,31 +126,12 @@ public void transform(TransformationContext context) { })); } - @BuildStep - void registerRestClientClassicProvider( - Capabilities capabilities, - BuildProducer additionalIndexed, - BuildProducer additionalBeans) { - if (capabilities.isPresent(Capability.REST_CLIENT) && capabilities.isMissing(Capability.REST_CLIENT_REACTIVE)) { - additionalIndexed.produce(new AdditionalIndexedClassesBuildItem(OpenTelemetryClientFilter.class.getName())); - additionalBeans.produce(new AdditionalBeanBuildItem(OpenTelemetryClientFilter.class)); - } - } - - @BuildStep - void registerReactiveMessagingMessageDecorator( - Capabilities capabilities, - BuildProducer additionalBeans) { - if (capabilities.isPresent(Capability.SMALLRYE_REACTIVE_MESSAGING)) { - additionalBeans.produce(new AdditionalBeanBuildItem(ReactiveMessagingTracingDecorator.class)); - } - } - @BuildStep @Record(ExecutionTime.STATIC_INIT) void createOpenTelemetry( OpenTelemetryConfig openTelemetryConfig, OpenTelemetryRecorder recorder, + InstrumentationRecorder instrumentationRecorder, Optional tracerProviderBuildItem, LaunchModeBuildItem launchMode) { @@ -166,6 +143,11 @@ void createOpenTelemetry( .orElse(null); recorder.createOpenTelemetry(tracerProvider, openTelemetryConfig); recorder.eagerlyCreateContextStorage(); + + // just checking for live reload would bypass the OpenTelemetryDevModeTest + if (launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT) { + instrumentationRecorder.setTracerDevMode(instrumentationRecorder.createTracers()); + } } @BuildStep diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java index 98e4957b2dd69..647561f9e4e8f 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/TracerProcessor.java @@ -1,18 +1,12 @@ package io.quarkus.opentelemetry.deployment.tracing; -import static io.quarkus.bootstrap.classloading.QuarkusClassLoader.isClassPresentAtRuntime; -import static javax.interceptor.Interceptor.Priority.LIBRARY_AFTER; - import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.BooleanSupplier; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.DotName; @@ -43,10 +37,7 @@ import io.quarkus.opentelemetry.runtime.config.TracerRuntimeConfig; import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder; import io.quarkus.opentelemetry.runtime.tracing.cdi.TracerProducer; -import io.quarkus.opentelemetry.runtime.tracing.intrumentation.grpc.GrpcTracingClientInterceptor; -import io.quarkus.opentelemetry.runtime.tracing.intrumentation.grpc.GrpcTracingServerInterceptor; import io.quarkus.runtime.configuration.ConfigurationException; -import io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem; import io.quarkus.vertx.http.deployment.spi.FrameworkEndpointsBuildItem; import io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem; @@ -58,38 +49,6 @@ public class TracerProcessor { private static final DotName SPAN_EXPORTER = DotName.createSimple(SpanExporter.class.getName()); private static final DotName SPAN_PROCESSOR = DotName.createSimple(SpanProcessor.class.getName()); - static class MetricsExtensionAvailable implements BooleanSupplier { - private static final boolean IS_MICROMETER_EXTENSION_AVAILABLE = isClassPresentAtRuntime( - "io.quarkus.micrometer.runtime.binder.vertx.VertxHttpServerMetrics"); - - @Override - public boolean getAsBoolean() { - Config config = ConfigProvider.getConfig(); - if (IS_MICROMETER_EXTENSION_AVAILABLE) { - if (config.getOptionalValue("quarkus.micrometer.enabled", Boolean.class).orElse(true)) { - Optional httpServerEnabled = config - .getOptionalValue("quarkus.micrometer.binder.http-server.enabled", Boolean.class); - if (httpServerEnabled.isPresent()) { - return httpServerEnabled.get(); - } else { - return config.getOptionalValue("quarkus.micrometer.binder-enabled-default", Boolean.class).orElse(true); - } - } - } - return false; - } - } - - static class GrpcExtensionAvailable implements BooleanSupplier { - private static final boolean IS_GRPC_EXTENSION_AVAILABLE = isClassPresentAtRuntime( - "io.quarkus.grpc.runtime.GrpcServerRecorder"); - - @Override - public boolean getAsBoolean() { - return IS_GRPC_EXTENSION_AVAILABLE; - } - } - @BuildStep UnremovableBeanBuildItem ensureProducersAreRetained( CombinedIndexBuildItem indexBuildItem, @@ -179,24 +138,6 @@ void dropNames( dropStaticResources.produce(new DropStaticResourcesBuildItem(resources)); } - @BuildStep(onlyIf = GrpcExtensionAvailable.class) - void grpcTracers(BuildProducer additionalBeans) { - additionalBeans.produce(new AdditionalBeanBuildItem(GrpcTracingServerInterceptor.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(GrpcTracingClientInterceptor.class)); - } - - @BuildStep - @Record(ExecutionTime.STATIC_INIT) - VertxOptionsConsumerBuildItem vertxTracingOptions(TracerRecorder recorder) { - return new VertxOptionsConsumerBuildItem(recorder.getVertxTracingOptions(), LIBRARY_AFTER); - } - - @BuildStep(onlyIfNot = MetricsExtensionAvailable.class) - @Record(ExecutionTime.STATIC_INIT) - VertxOptionsConsumerBuildItem vertxTracingMetricsOptions(TracerRecorder recorder) { - return new VertxOptionsConsumerBuildItem(recorder.getVertxTracingMetricsOptions(), LIBRARY_AFTER + 1); - } - @BuildStep @Record(ExecutionTime.STATIC_INIT) TracerProviderBuildItem createTracerProvider( diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java new file mode 100644 index 0000000000000..16bb2819c906f --- /dev/null +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java @@ -0,0 +1,115 @@ +package io.quarkus.opentelemetry.deployment.tracing.instrumentation; + +import static io.quarkus.bootstrap.classloading.QuarkusClassLoader.isClassPresentAtRuntime; +import static javax.interceptor.Interceptor.Priority.LIBRARY_AFTER; + +import java.util.Optional; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.opentelemetry.deployment.tracing.TracerEnabled; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.InstrumentationRecorder; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.grpc.GrpcTracingClientInterceptor; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.grpc.GrpcTracingServerInterceptor; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.reactivemessaging.ReactiveMessagingTracingDecorator; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.restclient.OpenTelemetryClientFilter; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.vertx.core.deployment.VertxOptionsConsumerBuildItem; +import io.vertx.core.VertxOptions; + +@BuildSteps(onlyIf = TracerEnabled.class) +public class InstrumentationProcessor { + static class MetricsExtensionAvailable implements BooleanSupplier { + private static final boolean IS_MICROMETER_EXTENSION_AVAILABLE = isClassPresentAtRuntime( + "io.quarkus.micrometer.runtime.binder.vertx.VertxHttpServerMetrics"); + + @Override + public boolean getAsBoolean() { + Config config = ConfigProvider.getConfig(); + if (IS_MICROMETER_EXTENSION_AVAILABLE) { + if (config.getOptionalValue("quarkus.micrometer.enabled", Boolean.class).orElse(true)) { + Optional httpServerEnabled = config + .getOptionalValue("quarkus.micrometer.binder.http-server.enabled", Boolean.class); + if (httpServerEnabled.isPresent()) { + return httpServerEnabled.get(); + } else { + return config.getOptionalValue("quarkus.micrometer.binder-enabled-default", Boolean.class).orElse(true); + } + } + } + return false; + } + } + + static class GrpcExtensionAvailable implements BooleanSupplier { + private static final boolean IS_GRPC_EXTENSION_AVAILABLE = isClassPresentAtRuntime( + "io.quarkus.grpc.runtime.GrpcServerRecorder"); + + @Override + public boolean getAsBoolean() { + return IS_GRPC_EXTENSION_AVAILABLE; + } + } + + @BuildStep(onlyIf = GrpcExtensionAvailable.class) + void grpcTracers(BuildProducer additionalBeans) { + additionalBeans.produce(new AdditionalBeanBuildItem(GrpcTracingServerInterceptor.class)); + additionalBeans.produce(new AdditionalBeanBuildItem(GrpcTracingClientInterceptor.class)); + } + + @BuildStep + void registerRestClientClassicProvider( + Capabilities capabilities, + BuildProducer additionalIndexed, + BuildProducer additionalBeans) { + if (capabilities.isPresent(Capability.REST_CLIENT) && capabilities.isMissing(Capability.REST_CLIENT_REACTIVE)) { + additionalIndexed.produce(new AdditionalIndexedClassesBuildItem(OpenTelemetryClientFilter.class.getName())); + additionalBeans.produce(new AdditionalBeanBuildItem(OpenTelemetryClientFilter.class)); + } + } + + @BuildStep + void registerReactiveMessagingMessageDecorator( + Capabilities capabilities, + BuildProducer additionalBeans) { + if (capabilities.isPresent(Capability.SMALLRYE_REACTIVE_MESSAGING)) { + additionalBeans.produce(new AdditionalBeanBuildItem(ReactiveMessagingTracingDecorator.class)); + } + } + + @BuildStep(onlyIfNot = MetricsExtensionAvailable.class) + @Record(ExecutionTime.STATIC_INIT) + VertxOptionsConsumerBuildItem vertxTracingMetricsOptions(InstrumentationRecorder recorder) { + return new VertxOptionsConsumerBuildItem(recorder.getVertxTracingMetricsOptions(), LIBRARY_AFTER + 1); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + VertxOptionsConsumerBuildItem vertxTracingOptions(InstrumentationRecorder recorder, + LaunchModeBuildItem launchMode) { + Consumer vertxTracingOptions; + if (launchMode.getLaunchMode() == LaunchMode.DEVELOPMENT) { + // tracers are set in the OpenTelemetryProcessor + vertxTracingOptions = recorder.getVertxTracingOptionsDevMode(); + } else { + vertxTracingOptions = recorder.getVertxTracingOptionsProd(recorder.createTracers()); + } + return new VertxOptionsConsumerBuildItem( + vertxTracingOptions, + LIBRARY_AFTER); + } + +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OpenTelemetryConfig.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OpenTelemetryConfig.java index 1fe8bfa306b48..73f7c5fafb594 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OpenTelemetryConfig.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OpenTelemetryConfig.java @@ -27,6 +27,8 @@ public final class OpenTelemetryConfig { @ConfigItem(defaultValue = "tracecontext,baggage") public List propagators; - /** Build / static runtime config for tracer */ + /** + * Build / static runtime config for tracer + */ public TracerConfig tracer; } diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java index 65b786efaa48e..1066280c6baf4 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/TracerRecorder.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; import javax.enterprise.inject.Any; import javax.enterprise.inject.Instance; @@ -21,31 +20,12 @@ import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import io.quarkus.arc.Arc; import io.quarkus.opentelemetry.runtime.config.TracerRuntimeConfig; -import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxMetricsFactory; -import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxTracingFactory; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; -import io.vertx.core.VertxOptions; -import io.vertx.core.metrics.MetricsOptions; -import io.vertx.core.tracing.TracingOptions; @Recorder public class TracerRecorder { - /* STATIC INIT */ - public Consumer getVertxTracingOptions() { - TracingOptions tracingOptions = new TracingOptions() - .setFactory(new OpenTelemetryVertxTracingFactory()); - return vertxOptions -> vertxOptions.setTracingOptions(tracingOptions); - } - - public Consumer getVertxTracingMetricsOptions() { - MetricsOptions metricsOptions = new MetricsOptions() - .setEnabled(true) - .setFactory(new OpenTelemetryVertxMetricsFactory()); - return vertxOptions -> vertxOptions.setMetricsOptions(metricsOptions); - } - /* STATIC INIT */ public RuntimeValue createTracerProvider( String quarkusVersion, diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java new file mode 100644 index 0000000000000..3fa73c26955b0 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java @@ -0,0 +1,66 @@ +package io.quarkus.opentelemetry.runtime.tracing.intrumentation; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.EventBusInstrumenterVertxTracer; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.HttpInstrumenterVertxTracer; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.InstrumenterVertxTracer; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxMetricsFactory; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxTracer; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxTracingDevModeFactory; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxTracingFactory; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.SqlClientInstrumenterVertxTracer; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.VertxOptions; +import io.vertx.core.metrics.MetricsOptions; +import io.vertx.core.tracing.TracingOptions; + +@Recorder +public class InstrumentationRecorder { + + public static final OpenTelemetryVertxTracingDevModeFactory FACTORY = new OpenTelemetryVertxTracingDevModeFactory(); + + /* RUNTIME INIT */ + public RuntimeValue createTracers() { + OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); + List> instrumenterVertxTracers = new ArrayList<>(); + instrumenterVertxTracers.add(new HttpInstrumenterVertxTracer(openTelemetry)); + instrumenterVertxTracers.add(new EventBusInstrumenterVertxTracer(openTelemetry)); + // TODO - Selectively register this in the recorder if the SQL Client is available. + instrumenterVertxTracers.add(new SqlClientInstrumenterVertxTracer(openTelemetry)); + return new RuntimeValue<>(new OpenTelemetryVertxTracer(instrumenterVertxTracers)); + } + + /* RUNTIME INIT */ + public Consumer getVertxTracingOptionsProd( + RuntimeValue tracer) { + TracingOptions tracingOptions = new TracingOptions() + .setFactory(new OpenTelemetryVertxTracingFactory(tracer.getValue())); + return vertxOptions -> vertxOptions.setTracingOptions(tracingOptions); + } + + /* RUNTIME INIT */ + public Consumer getVertxTracingOptionsDevMode() { + TracingOptions tracingOptions = new TracingOptions() + .setFactory(FACTORY); + return vertxOptions -> vertxOptions.setTracingOptions(tracingOptions); + } + + /* RUNTIME INIT */ + public void setTracerDevMode(RuntimeValue tracer) { + FACTORY.getVertxTracerDelegator().setDelegate(tracer.getValue()); + } + + /* RUNTIME INIT */ + public Consumer getVertxTracingMetricsOptions() { + MetricsOptions metricsOptions = new MetricsOptions() + .setEnabled(true) + .setFactory(new OpenTelemetryVertxMetricsFactory()); + return vertxOptions -> vertxOptions.setMetricsOptions(metricsOptions); + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java index 10abcd55de8f2..9667e75917103 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/HttpInstrumenterVertxTracer.java @@ -44,7 +44,7 @@ import io.vertx.core.spi.observability.HttpResponse; import io.vertx.core.spi.tracing.TagExtractor; -class HttpInstrumenterVertxTracer implements InstrumenterVertxTracer { +public class HttpInstrumenterVertxTracer implements InstrumenterVertxTracer { private final Instrumenter serverInstrumenter; private final Instrumenter clientInstrumenter; diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/InstrumenterVertxTracer.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/InstrumenterVertxTracer.java index 6c9408872dcea..c1ec2a629ef98 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/InstrumenterVertxTracer.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/InstrumenterVertxTracer.java @@ -17,7 +17,7 @@ import io.vertx.core.tracing.TracingPolicy; @SuppressWarnings("unchecked") -interface InstrumenterVertxTracer extends VertxTracer { +public interface InstrumenterVertxTracer extends VertxTracer { @Override default SpanOperation receiveRequest( // The Vert.x context passed to use is already duplicated. diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingDevModeFactory.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingDevModeFactory.java new file mode 100644 index 0000000000000..a26fb8322aa83 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingDevModeFactory.java @@ -0,0 +1,84 @@ +package io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx; + +import java.util.function.BiConsumer; + +import io.vertx.core.Context; +import io.vertx.core.spi.VertxTracerFactory; +import io.vertx.core.spi.tracing.SpanKind; +import io.vertx.core.spi.tracing.TagExtractor; +import io.vertx.core.spi.tracing.VertxTracer; +import io.vertx.core.tracing.TracingOptions; +import io.vertx.core.tracing.TracingPolicy; + +public class OpenTelemetryVertxTracingDevModeFactory implements VertxTracerFactory { + private final Delegator vertxTracerDelegator = new Delegator(); + + public OpenTelemetryVertxTracingDevModeFactory() { + } + + public Delegator getVertxTracerDelegator() { + return vertxTracerDelegator; + } + + @Override + public VertxTracer tracer(final TracingOptions options) { + return vertxTracerDelegator; + } + + public static class Delegator implements VertxTracer { + private VertxTracer delegate; + + public VertxTracer getDelegate() { + return delegate; + } + + public Delegator setDelegate(final VertxTracer delegate) { + this.delegate = delegate; + return this; + } + + @Override + public Object receiveRequest( + final Context context, + final SpanKind kind, + final TracingPolicy policy, + final Object request, + final String operation, + final Iterable headers, + final TagExtractor tagExtractor) { + return delegate.receiveRequest(context, kind, policy, request, operation, headers, tagExtractor); + } + + @Override + public void sendResponse( + final Context context, + final Object response, + final Object payload, + final Throwable failure, + final TagExtractor tagExtractor) { + delegate.sendResponse(context, response, payload, failure, tagExtractor); + } + + @Override + public Object sendRequest( + final Context context, + final SpanKind kind, + final TracingPolicy policy, + final Object request, + final String operation, + final BiConsumer headers, + final TagExtractor tagExtractor) { + return delegate.sendRequest(context, kind, policy, request, operation, headers, tagExtractor); + } + + @Override + public void receiveResponse( + final Context context, + final Object response, + final Object payload, + final Throwable failure, + final TagExtractor tagExtractor) { + delegate.receiveResponse(context, response, payload, failure, tagExtractor); + } + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingFactory.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingFactory.java index 43f2835f61ae2..c90e8a5a3208a 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingFactory.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxTracingFactory.java @@ -1,23 +1,18 @@ package io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx; -import java.util.ArrayList; -import java.util.List; - -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; import io.vertx.core.spi.VertxTracerFactory; import io.vertx.core.spi.tracing.VertxTracer; import io.vertx.core.tracing.TracingOptions; public class OpenTelemetryVertxTracingFactory implements VertxTracerFactory { + private final OpenTelemetryVertxTracer openTelemetryVertxTracer; + + public OpenTelemetryVertxTracingFactory(OpenTelemetryVertxTracer openTelemetryVertxTracer) { + this.openTelemetryVertxTracer = openTelemetryVertxTracer; + } + @Override public VertxTracer tracer(final TracingOptions options) { - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - List> instrumenterVertxTracers = new ArrayList<>(); - instrumenterVertxTracers.add(new HttpInstrumenterVertxTracer(openTelemetry)); - instrumenterVertxTracers.add(new EventBusInstrumenterVertxTracer(openTelemetry)); - // TODO - Selectively register this in the recorder if the SQL Client is available. - instrumenterVertxTracers.add(new SqlClientInstrumenterVertxTracer(openTelemetry)); - return new OpenTelemetryVertxTracer(instrumenterVertxTracers); + return openTelemetryVertxTracer; } } From fe0b43ac8be43664f372c6ac66b87e828ce8ce02 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 23 Nov 2022 15:43:34 +0200 Subject: [PATCH 12/28] Take conditional annotation into account for @ServerExceptionMapper Closes: #29043 (cherry picked from commit 17eecf23817b0bd50e40085d8e84aa3971fe6b66) --- .../spi/ExceptionMapperBuildItem.java | 20 +++ .../deployment/ResteasyReactiveProcessor.java | 3 +- .../ResteasyReactiveScanningProcessor.java | 46 +++++- .../ConditionalExceptionMappersTest.java | 141 ++++++++++++++++++ ...InvalidConditional\316\234appersTest.java" | 62 ++++++++ .../runtime/ResteasyReactiveRecorder.java | 14 ++ .../common/model/ResourceExceptionMapper.java | 36 ++++- .../ServerExceptionMapperGenerator.java | 29 +++- .../ServerExceptionMappingFeature.java | 2 +- .../server/core/ExceptionMapping.java | 98 +++++++++++- .../server/core/RuntimeExceptionMapper.java | 2 +- 11 files changed, 436 insertions(+), 17 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/ConditionalExceptionMappersTest.java create mode 100644 "extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/InvalidConditional\316\234appersTest.java" diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ExceptionMapperBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ExceptionMapperBuildItem.java index 727213f77799f..1479091055640 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ExceptionMapperBuildItem.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ExceptionMapperBuildItem.java @@ -1,5 +1,7 @@ package io.quarkus.resteasy.reactive.spi; +import org.jboss.jandex.ClassInfo; + import io.quarkus.builder.item.MultiBuildItem; public final class ExceptionMapperBuildItem extends MultiBuildItem implements CheckBean { @@ -8,12 +10,14 @@ public final class ExceptionMapperBuildItem extends MultiBuildItem implements Ch private final Integer priority; private final String handledExceptionName; private final boolean registerAsBean; + private final ClassInfo declaringClass; public ExceptionMapperBuildItem(String className, String handledExceptionName, Integer priority, boolean registerAsBean) { this.className = className; this.priority = priority; this.handledExceptionName = handledExceptionName; this.registerAsBean = registerAsBean; + this.declaringClass = null; } private ExceptionMapperBuildItem(Builder builder) { @@ -21,6 +25,7 @@ private ExceptionMapperBuildItem(Builder builder) { this.handledExceptionName = builder.handledExceptionName; this.priority = builder.priority; this.registerAsBean = builder.registerAsBean; + this.declaringClass = builder.declaringClass; } public String getClassName() { @@ -40,6 +45,10 @@ public boolean isRegisterAsBean() { return registerAsBean; } + public ClassInfo getDeclaringClass() { + return declaringClass; + } + public static class Builder { private final String className; private final String handledExceptionName; @@ -47,6 +56,12 @@ public static class Builder { private Integer priority; private boolean registerAsBean = true; + /** + * Used to track the class that resulted in the registration of the exception mapper. + * This is only set for exception mappers created from {@code @ServerExceptionMapper} + */ + private ClassInfo declaringClass; + public Builder(String className, String handledExceptionName) { this.className = className; this.handledExceptionName = handledExceptionName; @@ -62,6 +77,11 @@ public Builder setRegisterAsBean(boolean registerAsBean) { return this; } + public Builder setDeclaringClass(ClassInfo declaringClass) { + this.declaringClass = declaringClass; + return this; + } + public ExceptionMapperBuildItem build() { return new ExceptionMapperBuildItem(this); } 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 fdfab1e721441..407faa9ee5491 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 @@ -1106,8 +1106,9 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem, Function> factoryFunction = s -> FactoryUtils.factory(s, singletonClasses, recorder, beanContainerBuildItem); interceptors.initializeDefaultFactories(factoryFunction); - exceptionMapping.initializeDefaultFactories(factoryFunction); contextResolvers.initializeDefaultFactories(factoryFunction); + exceptionMapping.initializeDefaultFactories(factoryFunction); + exceptionMapping.replaceDiscardAtRuntimeIfBeanIsUnavailable(className -> recorder.beanUnavailable(className)); paramConverterProviders.initializeDefaultFactories(factoryFunction); paramConverterProviders.sort(); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java index aacd2043ff891..c535061abd937 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java @@ -123,7 +123,6 @@ public List defaultUnwrappedException() { new UnwrappedExceptionBuildItem(RollbackException.class)); } - @SuppressWarnings({ "unchecked", "rawtypes" }) @BuildStep public ExceptionMappersBuildItem scanForExceptionMappers(CombinedIndexBuildItem combinedIndexBuildItem, ApplicationResultBuildItem applicationResultBuildItem, @@ -163,12 +162,32 @@ public ExceptionMappersBuildItem scanForExceptionMappers(CombinedIndexBuildItem ResourceExceptionMapper mapper = new ResourceExceptionMapper<>(); mapper.setPriority(priority); mapper.setClassName(additionalExceptionMapper.getClassName()); + addRuntimeCheckIfNecessary(additionalExceptionMapper, mapper); exceptions.addExceptionMapper(additionalExceptionMapper.getHandledExceptionName(), mapper); } additionalBeanBuildItemBuildProducer.produce(beanBuilder.build()); return new ExceptionMappersBuildItem(exceptions); } + private static void addRuntimeCheckIfNecessary(ExceptionMapperBuildItem additionalExceptionMapper, + ResourceExceptionMapper mapper) { + ClassInfo declaringClass = additionalExceptionMapper.getDeclaringClass(); + if (declaringClass != null) { + boolean needsRuntimeCheck = false; + List classAnnotations = declaringClass.declaredAnnotations(); + for (AnnotationInstance classAnnotation : classAnnotations) { + if (CONDITIONAL_BEAN_ANNOTATIONS.contains(classAnnotation.name())) { + needsRuntimeCheck = true; + break; + } + } + if (needsRuntimeCheck) { + mapper.setDiscardAtRuntime(new ResourceExceptionMapper.DiscardAtRuntimeIfBeanIsUnavailable( + declaringClass.name().toString())); + } + } + } + @BuildStep public ParamConverterProvidersBuildItem scanForParamConverters( BuildProducer additionalBeanBuildItemBuildProducer, @@ -387,10 +406,31 @@ public void handleCustomAnnotatedMethods( additionalBeans.addBeanClass(methodInfo.declaringClass().name().toString()); Map generatedClassNames = ServerExceptionMapperGenerator.generateGlobalMapper(methodInfo, new GeneratedBeanGizmoAdaptor(generatedBean), - Set.of(HTTP_SERVER_REQUEST, HTTP_SERVER_RESPONSE, ROUTING_CONTEXT), Set.of(Unremovable.class.getName())); + Set.of(HTTP_SERVER_REQUEST, HTTP_SERVER_RESPONSE, ROUTING_CONTEXT), Set.of(Unremovable.class.getName()), + (m -> { + List methodAnnotations = m.annotations(); + for (AnnotationInstance methodAnnotation : methodAnnotations) { + if (CONDITIONAL_BEAN_ANNOTATIONS.contains(methodAnnotation.name())) { + throw new RuntimeException( + "The combination of '@" + methodAnnotation.name().withoutPackagePrefix() + + "' and '@ServerExceptionMapper' is not allowed. Offending method is '" + + m.name() + "' of class '" + m.declaringClass().name() + "'"); + } + } + + List classAnnotations = m.declaringClass().declaredAnnotations(); + for (AnnotationInstance classAnnotation : classAnnotations) { + if (CONDITIONAL_BEAN_ANNOTATIONS.contains(classAnnotation.name())) { + return true; + } + } + return false; + })); for (Map.Entry entry : generatedClassNames.entrySet()) { ExceptionMapperBuildItem.Builder builder = new ExceptionMapperBuildItem.Builder(entry.getValue(), - entry.getKey()).setRegisterAsBean(false);// it has already been made a bean + entry.getKey()) + .setRegisterAsBean(false) // it has already been made a bean + .setDeclaringClass(methodInfo.declaringClass()); // we'll use this later on AnnotationValue priorityValue = instance.value("priority"); if (priorityValue != null) { builder.setPriority(priorityValue.asInt()); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/ConditionalExceptionMappersTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/ConditionalExceptionMappersTest.java new file mode 100644 index 0000000000000..31c3270b60806 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/ConditionalExceptionMappersTest.java @@ -0,0 +1,141 @@ +package io.quarkus.resteasy.reactive.server.test.customexceptions; + +import static io.restassured.RestAssured.*; + +import java.util.function.Supplier; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Priorities; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +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; + +import io.quarkus.arc.lookup.LookupUnlessProperty; +import io.quarkus.arc.profile.IfBuildProfile; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; + +public class ConditionalExceptionMappersTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(AbstractException.class, FirstException.class, SecondException.class, + WontBeEnabledMappers.class, WillBeEnabledMappers.class, AlwaysEnabledMappers.class, + TestResource.class); + } + }); + + @Test + public void test() { + get("/first").then().statusCode(903); + get("/second").then().statusCode(801); + get("/third").then().statusCode(555); + } + + @Path("") + public static class TestResource { + + @Path("first") + @GET + public String first() { + throw new FirstException(); + } + + @Path("second") + @GET + public String second() { + throw new SecondException(); + } + + @Path("third") + @GET + public String third() { + throw new ThirdException(); + } + } + + public static abstract class AbstractException extends RuntimeException { + + public AbstractException() { + setStackTrace(new StackTraceElement[0]); + } + } + + public static class FirstException extends AbstractException { + + } + + public static class SecondException extends AbstractException { + + } + + public static class ThirdException extends AbstractException { + + } + + @IfBuildProfile("dummy") + public static class WontBeEnabledMappers { + + @ServerExceptionMapper(FirstException.class) + public Response first() { + return Response.status(900).build(); + } + + @ServerExceptionMapper(value = FirstException.class, priority = Priorities.USER - 100) + public Response firstWithLowerPriority() { + return Response.status(901).build(); + } + + @ServerExceptionMapper(priority = Priorities.USER - 100) + public Response second(SecondException ignored) { + return Response.status(800).build(); + } + } + + @LookupUnlessProperty(name = "notexistingproperty", stringValue = "true", lookupIfMissing = true) + public static class WillBeEnabledMappers { + + @ServerExceptionMapper(value = FirstException.class, priority = Priorities.USER + 10) + public Response first() { + return Response.status(902).build(); + } + + @ServerExceptionMapper(value = FirstException.class, priority = Priorities.USER - 10) + public Response firstWithLowerPriority() { + return Response.status(903).build(); + } + + @ServerExceptionMapper(priority = Priorities.USER - 10) + public RestResponse second(SecondException ignored) { + return RestResponse.status(801); + } + } + + public static class AlwaysEnabledMappers { + + @ServerExceptionMapper(value = FirstException.class, priority = Priorities.USER + 1000) + public Response first() { + return Response.status(555).build(); + } + + @ServerExceptionMapper(value = SecondException.class, priority = Priorities.USER + 1000) + public Response second() { + return Response.status(555).build(); + } + + @ServerExceptionMapper(value = ThirdException.class, priority = Priorities.USER + 1000) + public Uni third() { + return Uni.createFrom().item(Response.status(555).build()); + } + } +} diff --git "a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/InvalidConditional\316\234appersTest.java" "b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/InvalidConditional\316\234appersTest.java" new file mode 100644 index 0000000000000..08cb2f23ae8fc --- /dev/null +++ "b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/InvalidConditional\316\234appersTest.java" @@ -0,0 +1,62 @@ +package io.quarkus.resteasy.reactive.server.test.customexceptions; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; +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; + +import io.quarkus.arc.profile.IfBuildProfile; +import io.quarkus.test.QuarkusUnitTest; + +public class InvalidConditionalÎœappersTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(TestResource.class, Mappers.class); + } + }).assertException(t -> { + String message = t.getMessage(); + assertTrue(message.contains("@ServerExceptionMapper")); + assertTrue(message.contains("request")); + assertTrue(message.contains(Mappers.class.getName())); + }); + + @Test + public void test() { + fail("Should never have been called"); + } + + @Path("test") + public static class TestResource { + + @GET + public String hello() { + return "hello"; + } + + } + + public static class Mappers { + + @IfBuildProfile("test") + @ServerExceptionMapper + public Response request(IllegalArgumentException ignored) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + + } +} 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 8cc7fde9154a7..c0a81b7d277bb 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 @@ -333,6 +333,20 @@ public void handle(RoutingContext event) { }; } + public Supplier beanUnavailable(String className) { + return new Supplier<>() { + @Override + public Boolean get() { + try { + return !Arc.container().select(Class.forName(className, false, Thread.currentThread() + .getContextClassLoader())).isResolvable(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to determine if bean '" + className + "' is available", e); + } + } + }; + } + private static final class FailingDefaultAuthFailureHandler implements BiConsumer { @Override diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceExceptionMapper.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceExceptionMapper.java index 2d695d3c645ab..705e560e66530 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceExceptionMapper.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceExceptionMapper.java @@ -1,16 +1,20 @@ package org.jboss.resteasy.reactive.common.model; +import java.util.function.Supplier; + import javax.ws.rs.Priorities; import javax.ws.rs.ext.ExceptionMapper; import org.jboss.resteasy.reactive.spi.BeanFactory; -public class ResourceExceptionMapper { +public class ResourceExceptionMapper implements Comparable> { private BeanFactory> factory; private int priority = Priorities.USER; private String className; + private Supplier discardAtRuntime; + public void setFactory(BeanFactory> factory) { this.factory = factory; } @@ -35,4 +39,34 @@ public ResourceExceptionMapper setClassName(String className) { this.className = className; return this; } + + public Supplier getDiscardAtRuntime() { + return discardAtRuntime; + } + + public void setDiscardAtRuntime(Supplier discardAtRuntime) { + this.discardAtRuntime = discardAtRuntime; + } + + @Override + public int compareTo(ResourceExceptionMapper o) { + return Integer.compare(this.priority, o.priority); + } + + public static final class DiscardAtRuntimeIfBeanIsUnavailable implements Supplier { + private final String beanClass; + + public DiscardAtRuntimeIfBeanIsUnavailable(String beanClass) { + this.beanClass = beanClass; + } + + @Override + public Boolean get() { + throw new IllegalStateException("should never be called"); + } + + public String getBeanClass() { + return beanClass; + } + } } diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java index c5ac16bb7d8c6..e40a932562258 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java @@ -24,7 +24,9 @@ import java.util.Map; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Predicate; +import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.container.ResourceInfo; @@ -240,7 +242,7 @@ public static Map generatePerClassMapper(MethodInfo targetMethod * Returns a map containing the handled exception name as the key and the generated class name as the value */ public static Map generateGlobalMapper(MethodInfo targetMethod, ClassOutput classOutput, - Set unwrappableTypes, Set additionalBeanAnnotations) { + Set unwrappableTypes, Set additionalBeanAnnotations, Predicate isOptionalMapper) { ReturnType returnType = determineReturnType(targetMethod); checkModifiers(targetMethod); @@ -264,14 +266,31 @@ public static Map generateGlobalMapper(MethodInfo targetMethod, .setModifiers(Modifier.PRIVATE | Modifier.FINAL) .getFieldDescriptor(); - // generate a constructor that takes the target class as an argument - this class is a CDI bean so Arc will be able to inject into the generated class - MethodCreator ctor = cc.getMethodCreator("", void.class, targetClass.name().toString()); + MethodCreator ctor; + + boolean isOptional = isOptionalMapper.test(targetMethod); + if (isOptional) { + // generate a constructor that takes the Instance as an argument in order to avoid missing bean issues if the target has been conditionally disabled + // the body can freely read the instance value because if the target has been conditionally disabled, the generated class will not be instantiated + ctor = cc.getMethodCreator("", void.class, Instance.class).setSignature( + String.format("(Ljavax/enterprise/inject/Instance;)V", + targetClass.name().toString().replace('.', '/'))); + } else { + // generate a constructor that takes the target class as an argument - this class is a CDI bean so Arc will be able to inject into the generated class + ctor = cc.getMethodCreator("", void.class, targetClass.name().toString()); + } + ctor.setModifiers(Modifier.PUBLIC); ctor.addAnnotation(Inject.class); ctor.invokeSpecialMethod(ofConstructor(Object.class), ctor.getThis()); ResultHandle self = ctor.getThis(); - ResultHandle config = ctor.getMethodParam(0); - ctor.writeInstanceField(delegateField, self, config); + ResultHandle param = ctor.getMethodParam(0); + if (isOptional) { + ctor.writeInstanceField(delegateField, self, ctor + .invokeInterfaceMethod(MethodDescriptor.ofMethod(Instance.class, "get", Object.class), param)); + } else { + ctor.writeInstanceField(delegateField, self, param); + } ctor.returnValue(null); if (returnType == ReturnType.RESPONSE || returnType == ReturnType.REST_RESPONSE) { diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMappingFeature.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMappingFeature.java index ef2e8ce4c1ae3..7c6e8e9c73710 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMappingFeature.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMappingFeature.java @@ -76,7 +76,7 @@ public FeatureScanResult integrate(IndexView index, ScannedApplication scannedAp // the user class itself is made to be a bean as we want the user to be able to declare dependencies //additionalBeans.addBeanClass(methodInfo.declaringClass().name().toString()); Map generatedClassNames = ServerExceptionMapperGenerator.generateGlobalMapper(methodInfo, - classOutput, unwrappableTypes, additionalBeanAnnotations); + classOutput, unwrappableTypes, additionalBeanAnnotations, (m) -> false); for (Map.Entry entry : generatedClassNames.entrySet()) { ResourceExceptionMapper mapper = new ResourceExceptionMapper<>().setClassName(entry.getValue()); scannedApplication.getExceptionMappers().addExceptionMapper(entry.getKey(), mapper); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ExceptionMapping.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ExceptionMapping.java index 680cd22425ae2..cbf952beb1f2f 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ExceptionMapping.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ExceptionMapping.java @@ -1,6 +1,7 @@ package org.jboss.resteasy.reactive.server.core; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -8,13 +9,28 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import org.jboss.resteasy.reactive.common.model.ResourceExceptionMapper; import org.jboss.resteasy.reactive.spi.BeanFactory; +@SuppressWarnings({ "unchecked", "unused" }) public class ExceptionMapping { + /** + * The idea behind having two different maps is the following: + * Under normal circumstances, mappers are added to the first map, + * and we don't need to track multiple mappings because the priority is enough + * to distinguish. + * + * However, in the case where ExceptionMapper classes may not be active at runtime + * (due to the presence of conditional bean annotations), then we need to track + * all the possible mappings and at runtime determine the one with the lowest priority + * value that is active. + */ final Map> mappers = new HashMap<>(); + // this is going to be used when there are mappers that are removable at runtime + final Map>> runtimeCheckMappers = new HashMap<>(); /** * Exceptions that indicate an blocking operation was performed on an IO thread. @@ -61,17 +77,44 @@ public void addExceptionMapper(String exceptionClass, Reso ResourceExceptionMapper existing = mappers.get(exceptionClass); if (existing != null) { if (existing.getPriority() < mapper.getPriority()) { - //already a higher priority mapper + // we already have a lower priority mapper registered return; + } else { + mappers.remove(exceptionClass); + List> list = new ArrayList<>(2); + list.add(mapper); + list.add(existing); + runtimeCheckMappers.put(exceptionClass, list); + } + } else { + var list = runtimeCheckMappers.get(exceptionClass); + if (list == null) { + if (mapper.getDiscardAtRuntime() == null) { + mappers.put(exceptionClass, mapper); + } else { + list = new ArrayList<>(1); + list.add(mapper); + runtimeCheckMappers.put(exceptionClass, list); + } + } else { + list.add(mapper); + Collections.sort(list); } } - mappers.put(exceptionClass, mapper); } public void initializeDefaultFactories(Function> factoryCreator) { - for (Map.Entry> entry : mappers.entrySet()) { - if (entry.getValue().getFactory() == null) { - entry.getValue().setFactory((BeanFactory) factoryCreator.apply(entry.getValue().getClassName())); + for (var resourceExceptionMapper : mappers.values()) { + if (resourceExceptionMapper.getFactory() == null) { + resourceExceptionMapper.setFactory((BeanFactory) factoryCreator.apply(resourceExceptionMapper.getClassName())); + } + } + for (var list : runtimeCheckMappers.values()) { + for (var resourceExceptionMapper : list) { + if (resourceExceptionMapper.getFactory() == null) { + resourceExceptionMapper + .setFactory((BeanFactory) factoryCreator.apply(resourceExceptionMapper.getClassName())); + } } } } @@ -80,6 +123,51 @@ public Map> getMappers() { return mappers; } + public Map>> getRuntimeCheckMappers() { + return runtimeCheckMappers; + } + + public Map> effectiveMappers() { + if (runtimeCheckMappers.isEmpty()) { + return mappers; + } + Map> result = new HashMap<>(); + for (var entry : runtimeCheckMappers.entrySet()) { + String exceptionClass = entry.getKey(); + var list = entry.getValue(); + for (var resourceExceptionMapper : list) { + if (resourceExceptionMapper.getDiscardAtRuntime() == null) { + result.put(exceptionClass, resourceExceptionMapper); + break; + } else { + if (!resourceExceptionMapper.getDiscardAtRuntime().get()) { + result.put(exceptionClass, resourceExceptionMapper); + break; + } + } + } + } + result.putAll(mappers); + return result; + } + + public void replaceDiscardAtRuntimeIfBeanIsUnavailable(Function> function) { + if (runtimeCheckMappers.isEmpty()) { + return; + } + for (var list : runtimeCheckMappers.values()) { + for (var resourceExceptionMapper : list) { + if (resourceExceptionMapper + .getDiscardAtRuntime() instanceof ResourceExceptionMapper.DiscardAtRuntimeIfBeanIsUnavailable) { + var discardAtRuntimeIfBeanIsUnavailable = (ResourceExceptionMapper.DiscardAtRuntimeIfBeanIsUnavailable) resourceExceptionMapper + .getDiscardAtRuntime(); + resourceExceptionMapper + .setDiscardAtRuntime(function.apply(discardAtRuntimeIfBeanIsUnavailable.getBeanClass())); + } + } + } + } + public static class ExceptionTypePredicate implements Predicate { private Class throwable; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/RuntimeExceptionMapper.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/RuntimeExceptionMapper.java index 04be9002bf53b..a72022cea3288 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/RuntimeExceptionMapper.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/RuntimeExceptionMapper.java @@ -45,7 +45,7 @@ public class RuntimeExceptionMapper { public RuntimeExceptionMapper(ExceptionMapping mapping, ClassLoader classLoader) { try { mappers = new HashMap<>(); - for (var i : mapping.mappers.entrySet()) { + for (var i : mapping.effectiveMappers().entrySet()) { mappers.put((Class) Class.forName(i.getKey(), false, classLoader), i.getValue()); } blockingProblemPredicates = new ArrayList<>(mapping.blockingProblemPredicates); From 1a28ab3f2e4408f96d6ecf6529c3555fbf2e7641 Mon Sep 17 00:00:00 2001 From: zedbeit Date: Thu, 1 Dec 2022 00:07:35 +0100 Subject: [PATCH 13/28] Add hibernate.query.in_clause_parameter_padding as a supported configuration parameter (cherry picked from commit d1dfafad0a61eab500b585e5486f96c3a6f866f4) --- .../deployment/HibernateOrmConfigPersistenceUnit.java | 9 ++++++++- .../hibernate/orm/deployment/HibernateOrmProcessor.java | 3 +++ .../reactive/deployment/HibernateReactiveProcessor.java | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java index 3d7fa832cde4b..84384ff7b0ce0 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java @@ -329,9 +329,16 @@ public enum NullOrdering { @ConfigItem(defaultValue = "none") public NullOrdering defaultNullOrdering; + /** + * Enables IN clause parameter padding which improves statement caching. + */ + @ConfigItem(defaultValue = "true") + public boolean inClauseParameterPadding; + public boolean isAnyPropertySet() { return queryPlanCacheMaxSize != DEFAULT_QUERY_PLAN_CACHE_MAX_SIZE - || defaultNullOrdering != NullOrdering.NONE; + || defaultNullOrdering != NullOrdering.NONE + || !inClauseParameterPadding; } } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 3b9d8b956f323..0159dc8f6fa87 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -1072,6 +1072,9 @@ private static void producePersistenceUnitDescriptorFromConfig( descriptor.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, persistenceUnitConfig.query.defaultNullOrdering.name().toLowerCase(Locale.ROOT)); + descriptor.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, + String.valueOf(persistenceUnitConfig.query.inClauseParameterPadding)); + // Disable sequence validations: they are reportedly slow, and people already get the same validation from normal schema validation descriptor.getProperties().put(AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY, SequenceMismatchStrategy.NONE); diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java index cb388fe109cb1..c5f5207fa3be5 100644 --- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java +++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java @@ -306,6 +306,9 @@ private static ParsedPersistenceXmlDescriptor generateReactivePersistenceUnit( desc.getProperties().setProperty(AvailableSettings.DEFAULT_NULL_ORDERING, persistenceUnitConfig.query.defaultNullOrdering.name().toLowerCase()); + desc.getProperties().setProperty(AvailableSettings.IN_CLAUSE_PARAMETER_PADDING, + String.valueOf(persistenceUnitConfig.query.inClauseParameterPadding)); + // JDBC persistenceUnitConfig.jdbc.statementBatchSize.ifPresent( statementBatchSize -> desc.getProperties().setProperty(AvailableSettings.STATEMENT_BATCH_SIZE, From bc6cf9cc31bb13f96af15b9e933f201b23000734 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 29 Nov 2022 09:26:14 +0200 Subject: [PATCH 14/28] Introduce @Separator annotation for query parameters This annotation allows to actually turn a String into a List automatically for each query param value Resolves: #29528 (cherry picked from commit f695369f071463e4c865a4e826c9269c576dbb6f) --- .../asciidoc/resteasy-reactive-migration.adoc | 4 ++ .../CustomResourceProducersGenerator.java | 5 +- .../test/simple/SeparatorQueryParamTest.java | 69 +++++++++++++++++++ .../scanning/ClientEndpointIndexer.java | 2 +- .../common/processor/EndpointIndexer.java | 9 +++ .../processor/ResteasyReactiveDotNames.java | 2 + .../jboss/resteasy/reactive/Separator.java | 18 +++++ .../common/model/MethodParameter.java | 4 +- .../processor/ServerEndpointIndexer.java | 3 +- .../core/parameters/QueryParamExtractor.java | 30 +++++++- .../startup/RuntimeResourceDeployment.java | 2 +- .../server/model/ServerMethodParameter.java | 4 +- 12 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java create mode 100644 independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Separator.java diff --git a/docs/src/main/asciidoc/resteasy-reactive-migration.adoc b/docs/src/main/asciidoc/resteasy-reactive-migration.adoc index f4e6674e015a9..96caaba04f23f 100644 --- a/docs/src/main/asciidoc/resteasy-reactive-migration.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive-migration.adoc @@ -87,6 +87,10 @@ The following table matches the legacy RESTEasy annotations with the new RESTEas |`org.jboss.resteasy.reactive.RestStreamElementType` | +|`org.jboss.resteasy.annotations.Separator` +|`org.jboss.resteasy.reactive.Separator` +| + |=== NOTE: The previous table does not include the `org.jboss.resteasy.annotations.Form` annotation because there is no RESTEasy Reactive specific replacement for it. diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/CustomResourceProducersGenerator.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/CustomResourceProducersGenerator.java index a0b95bbb21214..600dc62fc03da 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/CustomResourceProducersGenerator.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/CustomResourceProducersGenerator.java @@ -136,8 +136,9 @@ public static void generate(Map resourcesThatNeedCustomProd ResultHandle quarkusRestContextHandle = m.invokeVirtualMethod(getContextMethodCreator.getMethodDescriptor(), m.getThis()); ResultHandle extractorHandle = m.newInstance( - MethodDescriptor.ofConstructor(QueryParamExtractor.class, String.class, boolean.class, boolean.class), - m.getMethodParam(0), m.load(true), m.load(false)); + MethodDescriptor.ofConstructor(QueryParamExtractor.class, String.class, boolean.class, boolean.class, + String.class), + m.getMethodParam(0), m.load(true), m.load(false), m.loadNull()); ResultHandle resultHandle = m.invokeVirtualMethod(MethodDescriptor.ofMethod(QueryParamExtractor.class, "extractParameter", Object.class, ResteasyReactiveRequestContext.class), extractorHandle, quarkusRestContextHandle); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java new file mode 100644 index 0000000000000..b8973cd1edce6 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java @@ -0,0 +1,69 @@ +package io.quarkus.resteasy.reactive.server.test.simple; + +import static 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.RestResponse; +import org.jboss.resteasy.reactive.Separator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class SeparatorQueryParamTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(HelloResource.class)); + + @Test + public void noQueryParams() { + get("/hello") + .then() + .statusCode(200) + .body(Matchers.equalTo("hello world")) + .header("x-size", "0"); + } + + @Test + public void singleQueryParam() { + get("/hello?name=foo") + .then() + .statusCode(200) + .body(Matchers.equalTo("hello foo")) + .header("x-size", "1"); + } + + @Test + public void multipleQueryParams() { + get("/hello?name=foo,bar&name=one,two,three&name=yolo") + .then() + .statusCode(200) + .body(Matchers.equalTo("hello foo bar one two three yolo")) + .header("x-size", "6"); + } + + @Path("hello") + public static class HelloResource { + + @GET + public RestResponse hello(@RestQuery("name") @Separator(",") List names) { + int size = names.size(); + String body = ""; + if (names.isEmpty()) { + body = "hello world"; + } else { + body = "hello " + String.join(" ", names); + } + return RestResponse.ResponseBuilder.ok(body).header("x-size", size).build(); + } + + } +} 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 ba58ddbc17f5b..7fb5137508642 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 @@ -143,7 +143,7 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas elementType, declaredTypes.getDeclaredType(), declaredTypes.getDeclaredUnresolvedType(), signature, type, single, defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded, - mimePart, partFileName); + mimePart, partFileName, null); } private String getPartFileName(Map annotations) { 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 9d30b7b60041e..f0c129876ac7e 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 @@ -1500,6 +1500,15 @@ protected String getPartMime(Map annotations) { return mimeType; } + protected String getSeparator(Map annotations) { + AnnotationInstance separator = annotations.get(ResteasyReactiveDotNames.SEPARATOR); + String result = null; + if (separator != null && separator.value() != null) { + result = separator.value().asString(); + } + return result; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) public static abstract class Builder, B extends Builder, METHOD extends ResourceMethod> { private Function> factoryCreator; diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java index c86e07b6d04ac..8a7efd8526c8b 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java @@ -92,6 +92,7 @@ import org.jboss.resteasy.reactive.RestResponse; import org.jboss.resteasy.reactive.RestSseElementType; import org.jboss.resteasy.reactive.RestStreamElementType; +import org.jboss.resteasy.reactive.Separator; import org.reactivestreams.Publisher; import io.smallrye.common.annotation.Blocking; @@ -136,6 +137,7 @@ public final class ResteasyReactiveDotNames { public static final DotName MULTI_PART_FORM_PARAM = DotName.createSimple(MultipartForm.class.getName()); public static final DotName PART_TYPE_NAME = DotName.createSimple(PartType.class.getName()); public static final DotName PART_FILE_NAME = DotName.createSimple(PartFilename.class.getName()); + public static final DotName SEPARATOR = DotName.createSimple(Separator.class.getName()); public static final DotName REST_MATRIX_PARAM = DotName.createSimple(RestMatrix.class.getName()); public static final DotName REST_COOKIE_PARAM = DotName.createSimple(RestCookie.class.getName()); public static final DotName GET = DotName.createSimple(javax.ws.rs.GET.class.getName()); diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Separator.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Separator.java new file mode 100644 index 0000000000000..86e2629e80c46 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/Separator.java @@ -0,0 +1,18 @@ +package org.jboss.resteasy.reactive; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; + +/** + * When used on a {@link List} parameter annotated with {@link RestQuery}, RESTEasy Reactive will split the value of the + * parameter (using the value of the annotation) and populate the list with those values. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.PARAMETER }) +public @interface Separator { + + String value(); +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java index 3490ee77bde90..cf86ba52d4261 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java @@ -25,6 +25,7 @@ public class MethodParameter { private boolean isObtainedAsCollection; public String mimeType; public String partFileName; + public String separator; public MethodParameter() { } @@ -33,7 +34,7 @@ public MethodParameter(String name, String type, String declaredType, String dec ParameterType parameterType, boolean single, String defaultValue, boolean isObtainedAsCollection, boolean optional, boolean encoded, - String mimeType, String partFileName) { + String mimeType, String partFileName, String separator) { this.name = name; this.type = type; this.declaredType = declaredType; @@ -47,6 +48,7 @@ public MethodParameter(String name, String type, String declaredType, String dec this.encoded = encoded; this.mimeType = mimeType; this.partFileName = partFileName; + this.separator = separator; } public String getName() { 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 247375f997f6b..caf772ebb5e66 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 @@ -315,11 +315,12 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas ParameterConverterSupplier converter = parameterResult.getConverter(); DeclaredTypes declaredTypes = getDeclaredTypes(paramType, currentClassInfo, actualEndpointInfo); String mimeType = getPartMime(parameterResult.getAnns()); + String separator = getSeparator(parameterResult.getAnns()); return new ServerMethodParameter(name, elementType, declaredTypes.getDeclaredType(), declaredTypes.getDeclaredUnresolvedType(), type, single, signature, converter, defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded, - parameterResult.getCustomParameterExtractor(), mimeType); + parameterResult.getCustomParameterExtractor(), mimeType, separator); } protected void handleOtherParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java index ba9efb34ea930..f6d31259de9bb 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java @@ -1,5 +1,9 @@ package org.jboss.resteasy.reactive.server.core.parameters; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; public class QueryParamExtractor implements ParameterExtractor { @@ -7,15 +11,37 @@ public class QueryParamExtractor implements ParameterExtractor { private final String name; private final boolean single; private final boolean encoded; + private final String separator; - public QueryParamExtractor(String name, boolean single, boolean encoded) { + public QueryParamExtractor(String name, boolean single, boolean encoded, String separator) { this.name = name; this.single = single; this.encoded = encoded; + this.separator = separator; } @Override + @SuppressWarnings("unchecked") public Object extractParameter(ResteasyReactiveRequestContext context) { - return context.getQueryParameter(name, single, encoded); + Object queryParameter = context.getQueryParameter(name, single, encoded); + if (separator != null) { + if (queryParameter instanceof List) { // it's List + List list = (List) queryParameter; + List result = new ArrayList<>(list.size()); + for (int i = 0; i < list.size(); i++) { + String[] parts = list.get(i).split(separator); + result.addAll(Arrays.asList(parts)); + } + queryParameter = result; + } else if (queryParameter instanceof String) { + List result = new ArrayList<>(1); + String[] parts = ((String) queryParameter).split(separator); + result.addAll(Arrays.asList(parts)); + queryParameter = result; + } else { + // can't really happen + } + } + return queryParameter; } } 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 3c2710fef8cf4..1b93682704e40 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 @@ -676,7 +676,7 @@ public ParameterExtractor parameterExtractor(Map pathParameterI case ASYNC_RESPONSE: return new AsyncResponseExtractor(); case QUERY: - extractor = new QueryParamExtractor(param.name, param.isSingle(), param.encoded); + extractor = new QueryParamExtractor(param.name, param.isSingle(), param.encoded, param.separator); return extractor; case BODY: return new BodyParamExtractor(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java index 9a24d48043bba..d866d393ea817 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java @@ -21,10 +21,10 @@ public ServerMethodParameter(String name, String type, String declaredType, Stri ParameterConverterSupplier converter, String defaultValue, boolean obtainedAsCollection, boolean optional, boolean encoded, ParameterExtractor customParameterExtractor, - String mimeType) { + String mimeType, String separator) { super(name, type, declaredType, declaredUnresolvedType, signature, parameterType, single, defaultValue, obtainedAsCollection, optional, - encoded, mimeType, null /* not useful for server params */); + encoded, mimeType, null /* not useful for server params */, separator); this.converter = converter; this.customParameterExtractor = customParameterExtractor; } From f0117a19500e61c0d60feff197a5732a4f2b7525 Mon Sep 17 00:00:00 2001 From: Jose Date: Mon, 5 Dec 2022 14:28:51 +0100 Subject: [PATCH 15/28] Add spec.metadata.labels in Deployment resource for OpenShift Fix https://github.com/quarkusio/quarkus/issues/29497 (cherry picked from commit e673ea5989f422866bcd37cfac05fac6453f13d0) --- .../deployment/AddDeploymentResourceDecorator.java | 2 ++ .../OpenshiftWithDeploymentResourceTest.java | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddDeploymentResourceDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddDeploymentResourceDecorator.java index c877ad32e26e0..2afcf46e728de 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddDeploymentResourceDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AddDeploymentResourceDecorator.java @@ -24,6 +24,8 @@ public void visit(KubernetesListFluent list) { .withMatchLabels(new HashMap()) .endSelector() .withNewTemplate() + .withNewMetadata() + .endMetadata() .withNewSpec() .addNewContainer() .withName(name) diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithDeploymentResourceTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithDeploymentResourceTest.java index d4e5ae89ea863..4b86271cfdcbf 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithDeploymentResourceTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/OpenshiftWithDeploymentResourceTest.java @@ -2,6 +2,7 @@ package io.quarkus.it.kubernetes; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import java.io.IOException; import java.nio.file.Path; @@ -29,10 +30,12 @@ // public class OpenshiftWithDeploymentResourceTest { + private static final String NAME = "openshift-with-deployment-resource"; + @RegisterExtension static final QuarkusProdModeTest config = new QuarkusProdModeTest() .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) - .setApplicationName("openshift-with-deployment-resource") + .setApplicationName(NAME) .setApplicationVersion("0.1-SNAPSHOT") .overrideConfigKey("quarkus.openshift.deployment-kind", "Deployment") .overrideConfigKey("quarkus.openshift.replicas", "3") @@ -55,17 +58,21 @@ public void assertGeneratedResources() throws IOException { assertThat(kubernetesList).filteredOn(h -> "BuildConfig".equals(h.getKind())).hasSize(1); assertThat(kubernetesList).filteredOn(h -> "ImageStream".equals(h.getKind())).hasSize(2); assertThat(kubernetesList).filteredOn(h -> "ImageStream".equals(h.getKind()) - && h.getMetadata().getName().equals("openshift-with-deployment-resource")).hasSize(1); + && h.getMetadata().getName().equals(NAME)).hasSize(1); assertThat(kubernetesList).filteredOn(i -> i instanceof Deployment).singleElement().satisfies(i -> { assertThat(i).isInstanceOfSatisfying(Deployment.class, d -> { assertThat(d.getMetadata()).satisfies(m -> { - assertThat(m.getName()).isEqualTo("openshift-with-deployment-resource"); + assertThat(m.getName()).isEqualTo(NAME); }); assertThat(d.getSpec()).satisfies(deploymentSpec -> { assertThat(deploymentSpec.getReplicas()).isEqualTo(3); assertThat(deploymentSpec.getTemplate()).satisfies(t -> { + assertThat(t.getMetadata()).satisfies(metadata -> assertThat(metadata.getLabels()).containsAnyOf( + entry("app.kubernetes.io/name", NAME), + entry("app.kubernetes.io/version", "0.1-SNAPSHOT"))); + assertThat(t.getSpec()).satisfies(podSpec -> { assertThat(podSpec.getContainers()).singleElement().satisfies(container -> { assertThat(container.getImage()) From 8c25ae91d4b7ee8473c5a39f5a1aa83bfe76faa5 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 2 Dec 2022 17:26:02 +0100 Subject: [PATCH 16/28] Allow defining additional data passed to codestarts for project creation Fixes #28372 (cherry picked from commit e8b82f633f2844e0fddb99734b96bfd23c690ee8) --- .../main/java/io/quarkus/cli/CreateApp.java | 6 ++++++ .../main/java/io/quarkus/cli/CreateCli.java | 7 +++++++ .../io/quarkus/cli/common/DataOptions.java | 21 +++++++++++++++++++ .../io/quarkus/maven/CreateProjectMojo.java | 6 +++++- .../devtools/commands/CreateProject.java | 9 ++++++++ .../handlers/CreateProjectCommandHandler.java | 2 ++ 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 devtools/cli/src/main/java/io/quarkus/cli/common/DataOptions.java diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java index 74f30f1996bc7..ddb910e5a8cdc 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.Set; +import io.quarkus.cli.common.DataOptions; import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.cli.create.BaseCreateCommand; @@ -51,6 +52,9 @@ public class CreateApp extends BaseCreateCommand { CodeGenerationGroup codeGeneration = new CodeGenerationGroup(); @CommandLine.ArgGroup(order = 6, exclusive = false, validate = false) + DataOptions dataOptions = new DataOptions(); + + @CommandLine.ArgGroup(order = 7, exclusive = false, validate = false) PropertiesOptions propertiesOptions = new PropertiesOptions(); @Override @@ -72,6 +76,7 @@ public Integer call() throws Exception { setCodegenOptions(codeGeneration); setValue(CreateProjectKey.PROJECT_NAME, name); setValue(CreateProjectKey.PROJECT_DESCRIPTION, description); + setValue(CreateProjectKey.DATA, dataOptions.data); QuarkusCommandInvocation invocation = build(buildTool, targetQuarkusVersion, propertiesOptions.properties, extensions); @@ -112,6 +117,7 @@ public String toString() { + ", name=" + name + ", description=" + description + ", project=" + super.toString() + + ", data=" + dataOptions.data + ", properties=" + propertiesOptions.properties + '}'; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java index 4c5788b6d3968..4a0b9b6e10783 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.Set; +import io.quarkus.cli.common.DataOptions; import io.quarkus.cli.common.PropertiesOptions; import io.quarkus.cli.common.TargetQuarkusVersionGroup; import io.quarkus.cli.create.BaseCreateCommand; @@ -10,6 +11,7 @@ import io.quarkus.cli.create.TargetBuildToolGroup; import io.quarkus.cli.create.TargetGAVGroup; import io.quarkus.cli.create.TargetLanguageGroup; +import io.quarkus.devtools.commands.CreateProject.CreateProjectKey; import io.quarkus.devtools.commands.SourceType; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; @@ -43,6 +45,9 @@ public class CreateCli extends BaseCreateCommand { CodeGenerationGroup codeGeneration = new CodeGenerationGroup(); @CommandLine.ArgGroup(order = 6, exclusive = false, validate = false) + DataOptions dataOptions = new DataOptions(); + + @CommandLine.ArgGroup(order = 7, exclusive = false, validate = false) PropertiesOptions propertiesOptions = new PropertiesOptions(); @Override @@ -64,6 +69,7 @@ public Integer call() throws Exception { setJavaVersion(sourceType, targetLanguage.getJavaVersion()); setSourceTypeExtensions(extensions, sourceType); setCodegenOptions(codeGeneration); + setValue(CreateProjectKey.DATA, dataOptions.data); QuarkusCommandInvocation invocation = build(buildTool, targetQuarkusVersion, propertiesOptions.properties, extensions); @@ -101,6 +107,7 @@ public String toString() { + ", codeGeneration=" + codeGeneration + ", extensions=" + extensions + ", project=" + super.toString() + + ", data=" + dataOptions.data + ", properties=" + propertiesOptions.properties + '}'; } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/DataOptions.java b/devtools/cli/src/main/java/io/quarkus/cli/common/DataOptions.java new file mode 100644 index 0000000000000..9cc70fee084ae --- /dev/null +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/DataOptions.java @@ -0,0 +1,21 @@ +package io.quarkus.cli.common; + +import java.util.HashMap; +import java.util.Map; + +import picocli.CommandLine; + +public class DataOptions { + + public Map data = new HashMap<>(); + + @CommandLine.Option(names = "--data", mapFallbackValue = "", description = "Additional data") + void setData(Map data) { + this.data = data; + } + + @Override + public String toString() { + return data.toString(); + } +} diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index 21932062a482c..2c3ed1d1769f1 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -187,6 +187,9 @@ public class CreateProjectMojo extends AbstractMojo { @Parameter(property = "appConfig") private String appConfig; + @Parameter(property = "data") + private String data; + @Override public void execute() throws MojoExecutionException { @@ -304,7 +307,8 @@ public void execute() throws MojoExecutionException { .resourcePath(path) .example(example) .noCode(noCode) - .appConfig(appConfig); + .appConfig(appConfig) + .data(data); success = createProject.execute().isSuccess(); if (success && parentPomModel != null && BuildTool.MAVEN.equals(buildToolEnum)) { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java index 7240d81cdce68..72a53d5c95ce6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateProject.java @@ -48,6 +48,8 @@ public interface CreateProjectKey { String NO_CODE = "codegen.no-code"; String EXAMPLE = "codegen.example"; String EXTRA_CODESTARTS = "codegen.extra-codestarts"; + + String DATA = "data"; } private QuarkusProject quarkusProject; @@ -195,6 +197,13 @@ public CreateProject noDockerfiles() { return noDockerfiles(true); } + public CreateProject data(String dataAsString) { + setValue(DATA, StringUtils.isNoneBlank(dataAsString) ? ToolsUtils.stringToMap(dataAsString, ",", "=") + : Collections.emptyMap()); + + return this; + } + public CreateProject setValue(String name, Object value) { if (value != null) { values.put(name, value); diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java index ee77d4f09ba05..9fa13dd32274d 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java @@ -1,5 +1,6 @@ package io.quarkus.devtools.commands.handlers; +import static io.quarkus.devtools.commands.CreateProject.CreateProjectKey.DATA; import static io.quarkus.devtools.commands.CreateProject.CreateProjectKey.EXAMPLE; import static io.quarkus.devtools.commands.CreateProject.CreateProjectKey.EXTENSIONS; import static io.quarkus.devtools.commands.CreateProject.CreateProjectKey.EXTRA_CODESTARTS; @@ -149,6 +150,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws .noDockerfiles(invocation.getValue(NO_DOCKERFILES, false)) .addData(platformData) .addData(toCodestartData(invocation.getValues())) + .addData(invocation.getValue(DATA, Collections.emptyMap())) .messageWriter(invocation.log()) .build(); invocation.log().info("-----------"); From fd96c5b6fa3a7423d857b949eaba3a3302754874 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 2 Dec 2022 17:50:34 +0100 Subject: [PATCH 17/28] Fix ordering of option in CreateApp (cherry picked from commit 6cb6ae7c49f695b394fda1f2e2622caf7a6907b7) --- .../src/main/java/io/quarkus/cli/CreateApp.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java index ddb910e5a8cdc..9cbaaf94e0e40 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateApp.java @@ -32,29 +32,29 @@ public class CreateApp extends BaseCreateCommand { "--extension", "--extensions" }, description = "Extension(s) to add to the project.", split = ",") Set extensions = new HashSet<>(); - @CommandLine.Option(paramLabel = "NAME", names = { "--name" }, description = "Name of the project.") + @CommandLine.Option(order = 2, paramLabel = "NAME", names = { "--name" }, description = "Name of the project.") String name; - @CommandLine.Option(paramLabel = "DESCRIPTION", names = { + @CommandLine.Option(order = 3, paramLabel = "DESCRIPTION", names = { "--description" }, description = "Description of the project.") String description; - @CommandLine.ArgGroup(order = 2, heading = "%nQuarkus version:%n") + @CommandLine.ArgGroup(order = 4, heading = "%nQuarkus version:%n") TargetQuarkusVersionGroup targetQuarkusVersion = new TargetQuarkusVersionGroup(); - @CommandLine.ArgGroup(order = 3, heading = "%nBuild tool (Maven):%n") + @CommandLine.ArgGroup(order = 5, heading = "%nBuild tool (Maven):%n") TargetBuildToolGroup targetBuildTool = new TargetBuildToolGroup(); - @CommandLine.ArgGroup(order = 4, exclusive = false, heading = "%nTarget language:%n") + @CommandLine.ArgGroup(order = 6, exclusive = false, heading = "%nTarget language:%n") TargetLanguageGroup targetLanguage = new TargetLanguageGroup(); - @CommandLine.ArgGroup(order = 5, exclusive = false, heading = "%nCode Generation:%n") + @CommandLine.ArgGroup(order = 7, exclusive = false, heading = "%nCode Generation:%n") CodeGenerationGroup codeGeneration = new CodeGenerationGroup(); - @CommandLine.ArgGroup(order = 6, exclusive = false, validate = false) + @CommandLine.ArgGroup(order = 8, exclusive = false, validate = false) DataOptions dataOptions = new DataOptions(); - @CommandLine.ArgGroup(order = 7, exclusive = false, validate = false) + @CommandLine.ArgGroup(order = 9, exclusive = false, validate = false) PropertiesOptions propertiesOptions = new PropertiesOptions(); @Override From 6bc15d7985cae2e8bd0c6350d86ecc5074a4ec81 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 2 Dec 2022 17:50:47 +0100 Subject: [PATCH 18/28] Support setting name and description in CreateCli (cherry picked from commit 102d15e4ddf9785911e3aba5ed8dfc5bee810491) --- .../main/java/io/quarkus/cli/CreateCli.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java index 4a0b9b6e10783..9369dcbec9e2a 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/CreateCli.java @@ -32,22 +32,29 @@ public class CreateCli extends BaseCreateCommand { "--extension", "--extensions" }, description = "Extension(s) to add to the project.", split = ",") Set extensions = new HashSet<>(); - @CommandLine.ArgGroup(order = 2, heading = "%nQuarkus version:%n") + @CommandLine.Option(order = 2, paramLabel = "NAME", names = { "--name" }, description = "Name of the project.") + String name; + + @CommandLine.Option(order = 3, paramLabel = "DESCRIPTION", names = { + "--description" }, description = "Description of the project.") + String description; + + @CommandLine.ArgGroup(order = 4, heading = "%nQuarkus version:%n") TargetQuarkusVersionGroup targetQuarkusVersion = new TargetQuarkusVersionGroup(); - @CommandLine.ArgGroup(order = 3, heading = "%nBuild tool (Maven):%n") + @CommandLine.ArgGroup(order = 5, heading = "%nBuild tool (Maven):%n") TargetBuildToolGroup targetBuildTool = new TargetBuildToolGroup(); - @CommandLine.ArgGroup(order = 4, exclusive = false, heading = "%nTarget language:%n") + @CommandLine.ArgGroup(order = 6, exclusive = false, heading = "%nTarget language:%n") TargetLanguageGroup targetLanguage = new TargetLanguageGroup(); - @CommandLine.ArgGroup(order = 5, exclusive = false, heading = "%nCode Generation:%n") + @CommandLine.ArgGroup(order = 7, exclusive = false, heading = "%nCode Generation:%n") CodeGenerationGroup codeGeneration = new CodeGenerationGroup(); - @CommandLine.ArgGroup(order = 6, exclusive = false, validate = false) + @CommandLine.ArgGroup(order = 8, exclusive = false, validate = false) DataOptions dataOptions = new DataOptions(); - @CommandLine.ArgGroup(order = 7, exclusive = false, validate = false) + @CommandLine.ArgGroup(order = 9, exclusive = false, validate = false) PropertiesOptions propertiesOptions = new PropertiesOptions(); @Override @@ -69,6 +76,8 @@ public Integer call() throws Exception { setJavaVersion(sourceType, targetLanguage.getJavaVersion()); setSourceTypeExtensions(extensions, sourceType); setCodegenOptions(codeGeneration); + setValue(CreateProjectKey.PROJECT_NAME, name); + setValue(CreateProjectKey.PROJECT_DESCRIPTION, description); setValue(CreateProjectKey.DATA, dataOptions.data); QuarkusCommandInvocation invocation = build(buildTool, targetQuarkusVersion, From 7c6405b42177f3f53336c3655fed9fdc7372609c Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 5 Dec 2022 11:30:00 +0100 Subject: [PATCH 19/28] Add a test for new --data feature of CLI project creation (cherry picked from commit 811b283cb8a8308e6954065e40eb09b168b383d3) --- .../src/test/java/io/quarkus/cli/CliProjectMavenTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java index 796ecd0acb71c..2419cfd619cc8 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java @@ -77,11 +77,13 @@ public void testCreateAppOverrides() throws Exception { List configs = Arrays.asList("custom.app.config1=val1", "custom.app.config2=val2", "lib.config=val3"); + List data = Arrays.asList("resteasy-reactive-codestart.resource.response=An awesome response"); CliDriver.Result result = CliDriver.execute(workspaceRoot, "create", "app", "--verbose", "-e", "-B", "--no-wrapper", "--package-name=custom.pkg", "--output-directory=" + nested, "--app-config=" + String.join(",", configs), + "--data=" + String.join(",", data), "-x resteasy-reactive,micrometer-registry-prometheus", "silly:my-project:0.1.0"); @@ -110,6 +112,10 @@ public void testCreateAppOverrides() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code. " + result); Assertions.assertTrue(result.stdout.contains("WARN"), "Expected a warning that the directory already exists. " + result); + + String greetingResource = CliDriver.readFileAsString(project, + project.resolve("src/main/java/custom/pkg/GreetingResource.java")); + Assertions.assertTrue(greetingResource.contains("return \"An awesome response\";")); } @Test From 0b15f5b575eb5cddb2a2fc980def7527caef61c8 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Mon, 5 Dec 2022 11:47:52 +0100 Subject: [PATCH 20/28] Simplify CliDriver.readFileAsString signature Per Andy's request. (cherry picked from commit f670cfcc370d75d7b26c43ca30bc3b41f4b241a6) --- .../io/quarkus/cli/CliCreateExtensionTest.java | 2 +- .../src/test/java/io/quarkus/cli/CliDriver.java | 12 ++++++------ .../java/io/quarkus/cli/CliProjectGradleTest.java | 14 +++++++------- .../java/io/quarkus/cli/CliProjectJBangTest.java | 10 +++++----- .../java/io/quarkus/cli/CliProjectMavenTest.java | 11 +++++------ 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliCreateExtensionTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliCreateExtensionTest.java index 160a2edbafe92..7ec866320be8f 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliCreateExtensionTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliCreateExtensionTest.java @@ -172,7 +172,7 @@ String validateBasicIdentifiers(Path pom, String group, String artifact, String Assertions.assertTrue(pom.toFile().exists(), "pom.xml should exist: " + pom.toAbsolutePath().toString()); - String pomContent = CliDriver.readFileAsString(project, pom); + String pomContent = CliDriver.readFileAsString(pom); Assertions.assertTrue(pomContent.contains("" + group + ""), pom + " should contain group id " + group + ":\n" + pomContent); Assertions.assertTrue(pomContent.contains("" + artifact + ""), diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java index 8ed984f892df2..6bbbc63a26463 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliDriver.java @@ -250,7 +250,7 @@ public static void deleteDir(Path path) throws Exception { Assertions.assertFalse(path.toFile().exists()); } - public static String readFileAsString(Path projectRoot, Path path) throws Exception { + public static String readFileAsString(Path path) throws Exception { return new String(Files.readAllBytes(path)); } @@ -293,7 +293,7 @@ public static Result invokeExtensionAddQute(Path projectRoot, Path file) throws Assertions.assertTrue(result.stdout.contains("quarkus-qute"), "Expected quarkus-qute to be in the list of extensions. Result:\n" + result); - String content = readFileAsString(projectRoot, file); + String content = readFileAsString(file); Assertions.assertTrue(content.contains("quarkus-qute"), "quarkus-qute should be listed as a dependency. Result:\n" + content); @@ -311,7 +311,7 @@ public static Result invokeExtensionRemoveQute(Path projectRoot, Path file) thro Assertions.assertFalse(result.stdout.contains("quarkus-qute"), "Expected quarkus-qute to be missing from the list of extensions. Result:\n" + result); - String content = readFileAsString(projectRoot, file); + String content = readFileAsString(file); Assertions.assertFalse(content.contains("quarkus-qute"), "quarkus-qute should not be listed as a dependency. Result:\n" + content); @@ -333,7 +333,7 @@ public static Result invokeExtensionAddMultiple(Path projectRoot, Path file) thr Assertions.assertTrue(result.stdout.contains("quarkus-jackson"), "Expected quarkus-jackson to be in the list of extensions. Result:\n" + result); - String content = CliDriver.readFileAsString(projectRoot, file); + String content = CliDriver.readFileAsString(file); Assertions.assertTrue(content.contains("quarkus-qute"), "quarkus-qute should still be listed as a dependency. Result:\n" + content); Assertions.assertTrue(content.contains("quarkus-amazon-lambda-http"), @@ -359,7 +359,7 @@ public static Result invokeExtensionRemoveMultiple(Path projectRoot, Path file) Assertions.assertFalse(result.stdout.contains("quarkus-jackson"), "quarkus-jackson should not be in the list of extensions. Result:\n" + result); - String content = CliDriver.readFileAsString(projectRoot, file); + String content = CliDriver.readFileAsString(file); Assertions.assertFalse(content.contains("quarkus-qute"), "quarkus-qute should not be listed as a dependency. Result:\n" + content); Assertions.assertFalse(content.contains("quarkus-amazon-lambda-http"), @@ -455,7 +455,7 @@ public static void validateApplicationProperties(Path projectRoot, List Path properties = projectRoot.resolve("src/main/resources/application.properties"); Assertions.assertTrue(properties.toFile().exists(), "application.properties should exist: " + properties.toAbsolutePath().toString()); - String propertiesFile = CliDriver.readFileAsString(projectRoot, properties); + String propertiesFile = CliDriver.readFileAsString(properties); configs.forEach(conf -> Assertions.assertTrue(propertiesFile.contains(conf), "Properties file should contain " + conf + ". Found:\n" + propertiesFile)); } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java index 0a99027fe806b..51ac69761cb79 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectGradleTest.java @@ -207,7 +207,7 @@ public void testExtensionList() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path buildGradle = project.resolve("build.gradle"); - String buildGradleContent = CliDriver.readFileAsString(project, buildGradle); + String buildGradleContent = CliDriver.readFileAsString(buildGradle); Assertions.assertFalse(buildGradleContent.contains("quarkus-qute"), "Dependencies should not contain qute extension by default. Found:\n" + buildGradleContent); @@ -379,7 +379,7 @@ public void testCreateArgJava11() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path buildGradle = project.resolve("build.gradle"); - String buildGradleContent = CliDriver.readFileAsString(project, buildGradle); + String buildGradleContent = CliDriver.readFileAsString(buildGradle); Assertions.assertTrue(buildGradleContent.contains("sourceCompatibility = JavaVersion.VERSION_11"), "Java 11 should be used when specified. Found:\n" + buildGradle); } @@ -394,7 +394,7 @@ public void testCreateArgJava17() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path buildGradle = project.resolve("build.gradle"); - String buildGradleContent = CliDriver.readFileAsString(project, buildGradle); + String buildGradleContent = CliDriver.readFileAsString(buildGradle); Assertions.assertTrue(buildGradleContent.contains("sourceCompatibility = JavaVersion.VERSION_17"), "Java 17 should be used when specified. Found:\n" + buildGradleContent); @@ -405,7 +405,7 @@ String validateBasicGradleGroovyIdentifiers(Path project, String group, String a Assertions.assertTrue(buildGradle.toFile().exists(), "build.gradle should exist: " + buildGradle.toAbsolutePath().toString()); - String buildContent = CliDriver.readFileAsString(project, buildGradle); + String buildContent = CliDriver.readFileAsString(buildGradle); Assertions.assertTrue(buildContent.contains("group '" + group + "'"), "build.gradle should include the group id:\n" + buildContent); Assertions.assertTrue(buildContent.contains("version '" + version + "'"), @@ -414,7 +414,7 @@ String validateBasicGradleGroovyIdentifiers(Path project, String group, String a Path settings = project.resolve("settings.gradle"); Assertions.assertTrue(settings.toFile().exists(), "settings.gradle should exist: " + settings.toAbsolutePath().toString()); - String settingsContent = CliDriver.readFileAsString(project, settings); + String settingsContent = CliDriver.readFileAsString(settings); Assertions.assertTrue(settingsContent.contains(artifact), "settings.gradle should include the artifact id:\n" + settingsContent); @@ -426,7 +426,7 @@ String validateBasicGradleKotlinIdentifiers(Path project, String group, String a Assertions.assertTrue(buildGradle.toFile().exists(), "build.gradle.kts should exist: " + buildGradle.toAbsolutePath().toString()); - String buildContent = CliDriver.readFileAsString(project, buildGradle); + String buildContent = CliDriver.readFileAsString(buildGradle); Assertions.assertTrue(buildContent.contains("group = \"" + group + "\""), "build.gradle.kts should include the group id:\n" + buildContent); Assertions.assertTrue(buildContent.contains("version = \"" + version + "\""), @@ -435,7 +435,7 @@ String validateBasicGradleKotlinIdentifiers(Path project, String group, String a Path settings = project.resolve("settings.gradle.kts"); Assertions.assertTrue(settings.toFile().exists(), "settings.gradle.kts should exist: " + settings.toAbsolutePath().toString()); - String settingsContent = CliDriver.readFileAsString(project, settings); + String settingsContent = CliDriver.readFileAsString(settings); Assertions.assertTrue(settingsContent.contains(artifact), "settings.gradle.kts should include the artifact id:\n" + settingsContent); diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java index e41807efd0574..d085158c37492 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectJBangTest.java @@ -53,7 +53,7 @@ public void testCreateAppDefaults() throws Exception { Path javaMain = valdiateJBangSourcePackage(project, ""); // no package name - String source = CliDriver.readFileAsString(project, javaMain); + String source = CliDriver.readFileAsString(javaMain); Assertions.assertTrue(source.contains("quarkus-resteasy"), "Generated source should reference resteasy. Found:\n" + source); @@ -89,7 +89,7 @@ public void testCreateAppOverrides() throws Exception { validateBasicIdentifiers(project, "silly", "my-project", "0.1.0"); Path javaMain = valdiateJBangSourcePackage(project, ""); - String source = CliDriver.readFileAsString(project, javaMain); + String source = CliDriver.readFileAsString(javaMain); Assertions.assertTrue(source.contains("quarkus-reactive-routes"), "Generated source should reference quarkus-reactive-routes. Found:\n" + source); @@ -114,7 +114,7 @@ public void testCreateCliDefaults() throws Exception { Path javaMain = valdiateJBangSourcePackage(project, ""); // no package name - String source = CliDriver.readFileAsString(project, javaMain); + String source = CliDriver.readFileAsString(javaMain); Assertions.assertFalse(source.contains("quarkus-resteasy"), "Generated source should reference resteasy. Found:\n" + source); Assertions.assertTrue(source.contains("quarkus-picocli"), @@ -178,7 +178,7 @@ public void testCreateArgJava11() throws Exception { Path javaMain = valdiateJBangSourcePackage(project, ""); // no package name - String source = CliDriver.readFileAsString(project, javaMain); + String source = CliDriver.readFileAsString(javaMain); Assertions.assertTrue(source.contains("//JAVA 11"), "Generated source should contain //JAVA 11. Found:\n" + source); } @@ -193,7 +193,7 @@ public void testCreateArgJava17() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path javaMain = valdiateJBangSourcePackage(project, ""); // no package name - String source = CliDriver.readFileAsString(project, javaMain); + String source = CliDriver.readFileAsString(javaMain); Assertions.assertTrue(source.contains("//JAVA 17"), "Generated source should contain //JAVA 17. Found:\n" + source); } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java index 2419cfd619cc8..4d74100b21eaa 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliProjectMavenTest.java @@ -113,8 +113,7 @@ public void testCreateAppOverrides() throws Exception { Assertions.assertTrue(result.stdout.contains("WARN"), "Expected a warning that the directory already exists. " + result); - String greetingResource = CliDriver.readFileAsString(project, - project.resolve("src/main/java/custom/pkg/GreetingResource.java")); + String greetingResource = CliDriver.readFileAsString(project.resolve("src/main/java/custom/pkg/GreetingResource.java")); Assertions.assertTrue(greetingResource.contains("return \"An awesome response\";")); } @@ -124,7 +123,7 @@ public void testExtensionList() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path pom = project.resolve("pom.xml"); - String pomContent = CliDriver.readFileAsString(project, pom); + String pomContent = CliDriver.readFileAsString(pom); Assertions.assertFalse(pomContent.contains("quarkus-qute"), "Dependencies should not contain qute extension by default. Found:\n" + pomContent); @@ -335,7 +334,7 @@ public void testCreateArgJava11() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path pom = project.resolve("pom.xml"); - String pomContent = CliDriver.readFileAsString(project, pom); + String pomContent = CliDriver.readFileAsString(pom); Assertions.assertTrue(pomContent.contains("maven.compiler.release>11<"), "Java 11 should be used when specified. Found:\n" + pomContent); @@ -351,7 +350,7 @@ public void testCreateArgJava17() throws Exception { Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Path pom = project.resolve("pom.xml"); - String pomContent = CliDriver.readFileAsString(project, pom); + String pomContent = CliDriver.readFileAsString(pom); Assertions.assertTrue(pomContent.contains("maven.compiler.release>17<"), "Java 17 should be used when specified. Found:\n" + pomContent); @@ -383,7 +382,7 @@ String validateBasicIdentifiers(String group, String artifact, String version) t Assertions.assertTrue(pom.toFile().exists(), "pom.xml should exist: " + pom.toAbsolutePath().toString()); - String pomContent = CliDriver.readFileAsString(project, pom); + String pomContent = CliDriver.readFileAsString(pom); Assertions.assertTrue(pomContent.contains("" + group + ""), "pom.xml should contain group id:\n" + pomContent); Assertions.assertTrue(pomContent.contains("" + artifact + ""), From c84d53747c27e51e3fa7771dce0d387f2d77220d Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 2 Dec 2022 14:45:32 +0100 Subject: [PATCH 21/28] ArC - introduce immutable bean archive index - this is index is mandatory and is used to discover beans - the computing index is optional and can be used for other tasks, e.g. during type-safe resolution - if the computing index is not present the immutable index is used instead - fixes #29575 (cherry picked from commit c2ab3421a74bf755aa2bb595645bcf8bbfd009d6) --- .../quarkus/arc/deployment/ArcProcessor.java | 3 +- .../deployment/BeanArchiveIndexBuildItem.java | 24 +++++-- .../arc/deployment/BeanArchiveProcessor.java | 12 ++-- .../di/deployment/SpringDIProcessorTest.java | 3 +- .../SpringScheduledProcessorTest.java | 3 +- .../processor/AnnotationLiteralProcessor.java | 2 +- .../quarkus/arc/processor/BeanArchives.java | 17 +++-- .../quarkus/arc/processor/BeanDeployment.java | 62 ++++++++++--------- .../quarkus/arc/processor/BeanProcessor.java | 47 ++++++++++---- .../arc/processor/BeanInfoInjectionsTest.java | 2 +- .../arc/processor/BeanInfoQualifiersTest.java | 2 +- .../arc/processor/BeanInfoTypesTest.java | 2 +- .../io/quarkus/arc/processor/TypesTest.java | 3 +- .../io/quarkus/arc/test/ArcTestContainer.java | 12 ++-- 14 files changed, 128 insertions(+), 66 deletions(-) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 00fe8ebcf5324..f2fb782c542b7 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -277,7 +277,8 @@ public void transform(TransformationContext transformationContext) { } }); - builder.setBeanArchiveIndex(index); + builder.setComputingBeanArchiveIndex(index); + builder.setImmutableBeanArchiveIndex(beanArchiveIndex.getImmutableIndex()); builder.setApplicationIndex(combinedIndex.getIndex()); List beanDefiningAnnotations = additionalBeanDefiningAnnotations.stream() .map((s) -> new BeanDefiningAnnotation(s.getName(), s.getDefaultScope())).collect(Collectors.toList()); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java index fbb2813c63846..0c0550f7d7e4a 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java @@ -14,26 +14,42 @@ * Compared to {@link io.quarkus.deployment.builditem.CombinedIndexBuildItem} this index can contain additional classes * that were indexed while bean discovery was in progress. * - * It also holds information about all programmatically registered beans and all generated bean classes. - * * @see GeneratedBeanBuildItem - * @see AdditionalBeanBuildItem * @see io.quarkus.deployment.builditem.CombinedIndexBuildItem */ public final class BeanArchiveIndexBuildItem extends SimpleBuildItem { private final IndexView index; + private final IndexView immutableIndex; private final Set generatedClassNames; - public BeanArchiveIndexBuildItem(IndexView index, Set generatedClassNames) { + public BeanArchiveIndexBuildItem(IndexView index, IndexView immutableIndex, Set generatedClassNames) { this.index = index; + this.immutableIndex = immutableIndex; this.generatedClassNames = generatedClassNames; } + /** + * This index is built on top of the immutable index. + * + * @return the computing index that can also index classes on demand + */ public IndexView getIndex() { return index; } + /** + * + * @return an immutable index that represents the bean archive + */ + public IndexView getImmutableIndex() { + return immutableIndex; + } + + /** + * + * @return the set of classes generated via {@link GeneratedBeanBuildItem} + */ public Set getGeneratedClassNames() { return generatedClassNames; } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java index 29283e4df9516..cb75954e4b327 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java @@ -84,12 +84,12 @@ public BeanArchiveIndexBuildItem build(ArcConfig config, ApplicationArchivesBuil additionalClasses.put(knownMissingClass, Optional.empty()); } - // Finally, index ArC/CDI API built-in classes - return new BeanArchiveIndexBuildItem( - BeanArchives.buildBeanArchiveIndex(Thread.currentThread().getContextClassLoader(), additionalClasses, - applicationIndex, - additionalBeanIndexer.complete()), - generatedClassNames); + IndexView immutableBeanArchiveIndex = BeanArchives.buildImmutableBeanArchiveIndex(applicationIndex, + additionalBeanIndexer.complete()); + IndexView computingBeanArchiveIndex = BeanArchives.buildComputingBeanArchiveIndex( + Thread.currentThread().getContextClassLoader(), + additionalClasses, immutableBeanArchiveIndex); + return new BeanArchiveIndexBuildItem(computingBeanArchiveIndex, immutableBeanArchiveIndex, generatedClassNames); } private IndexView buildApplicationIndex(ArcConfig config, ApplicationArchivesBuildItem applicationArchivesBuildItem, diff --git a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java index 5791891b3fca2..41e6975855dec 100644 --- a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java +++ b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java @@ -205,7 +205,8 @@ public void getAnnotationsToAddBeanMethodWithScope() { private IndexView getIndex(final Class... classes) { try { Index index = Index.of(classes); - return BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), index); + return BeanArchives.buildComputingBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), + BeanArchives.buildImmutableBeanArchiveIndex(index)); } catch (IOException e) { throw new IllegalStateException("Failed to index classes", e); } diff --git a/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java b/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java index 3abb0f2f3334b..45db58171f526 100644 --- a/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java +++ b/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java @@ -115,7 +115,8 @@ public void testBuildDelayParamFromInvalidFormat() { private IndexView getIndex(final Class... classes) { try { Index index = Index.of(classes); - return BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), index); + return BeanArchives.buildComputingBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), + BeanArchives.buildImmutableBeanArchiveIndex(index)); } catch (IOException e) { throw new IllegalStateException("Failed to index classes", e); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java index 88e8e83a89377..78c2a429fec3c 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java @@ -46,7 +46,7 @@ public class AnnotationLiteralProcessor { generateAnnotationLiteralClassName(key.annotationName()), applicationClassPredicate.test(key.annotationName()), key.annotationClass)); - this.beanArchiveIndex = beanArchiveIndex; + this.beanArchiveIndex = Objects.requireNonNull(beanArchiveIndex); } boolean hasLiteralsToGenerate() { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java index bd14d9e679fbd..4a6dd5c88542b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java @@ -52,14 +52,23 @@ public final class BeanArchives { /** * * @param applicationIndexes - * @return the final bean archive index + * @return the immutable bean archive index */ - public static IndexView buildBeanArchiveIndex(ClassLoader deploymentClassLoader, - Map> additionalClasses, IndexView... applicationIndexes) { + public static IndexView buildImmutableBeanArchiveIndex(IndexView... applicationIndexes) { List indexes = new ArrayList<>(); Collections.addAll(indexes, applicationIndexes); indexes.add(buildAdditionalIndex()); - return new IndexWrapper(CompositeIndex.create(indexes), deploymentClassLoader, additionalClasses); + return CompositeIndex.create(indexes); + } + + /** + * + * @param wrappedIndexes + * @return the computing bean archive index + */ + public static IndexView buildComputingBeanArchiveIndex(ClassLoader deploymentClassLoader, + Map> additionalClasses, IndexView immutableIndex) { + return new IndexWrapper(immutableIndex, deploymentClassLoader, additionalClasses); } private static IndexView buildAdditionalIndex() { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index dfcca8ff7ebde..4997affe0f467 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -55,8 +56,8 @@ public class BeanDeployment { private final BuildContextImpl buildContext; - private final IndexView beanArchiveIndex; - + private final IndexView beanArchiveComputingIndex; + private final IndexView beanArchiveImmutableIndex; private final IndexView applicationIndex; private final Map qualifiers; @@ -124,7 +125,8 @@ public class BeanDeployment { } this.beanDefiningAnnotations = beanDefiningAnnotations; this.resourceAnnotations = new HashSet<>(builder.resourceAnnotations); - this.beanArchiveIndex = builder.beanArchiveIndex; + this.beanArchiveComputingIndex = builder.beanArchiveComputingIndex; + this.beanArchiveImmutableIndex = Objects.requireNonNull(builder.beanArchiveImmutableIndex); this.applicationIndex = builder.applicationIndex; this.annotationStore = new AnnotationStore(initAndSort(builder.annotationTransformers, buildContext), buildContext); if (buildContext != null) { @@ -141,11 +143,11 @@ public class BeanDeployment { this.excludeTypes = builder.excludeTypes != null ? new ArrayList<>(builder.excludeTypes) : Collections.emptyList(); qualifierNonbindingMembers = new HashMap<>(); - qualifiers = findQualifiers(this.beanArchiveIndex); + qualifiers = findQualifiers(); for (QualifierRegistrar registrar : builder.qualifierRegistrars) { for (Map.Entry> entry : registrar.getAdditionalQualifiers().entrySet()) { DotName dotName = entry.getKey(); - ClassInfo classInfo = getClassByName(this.beanArchiveIndex, dotName); + ClassInfo classInfo = getClassByName(getBeanArchiveIndex(), dotName); if (classInfo != null) { Set nonbindingMembers = entry.getValue(); if (nonbindingMembers == null) { @@ -156,15 +158,15 @@ public class BeanDeployment { } } } - repeatingQualifierAnnotations = findContainerAnnotations(qualifiers, this.beanArchiveIndex); + repeatingQualifierAnnotations = findContainerAnnotations(qualifiers); buildContextPut(Key.QUALIFIERS.asString(), Collections.unmodifiableMap(qualifiers)); interceptorNonbindingMembers = new HashMap<>(); - interceptorBindings = findInterceptorBindings(this.beanArchiveIndex); + interceptorBindings = findInterceptorBindings(); for (InterceptorBindingRegistrar registrar : builder.interceptorBindingRegistrars) { for (InterceptorBindingRegistrar.InterceptorBinding binding : registrar.getAdditionalBindings()) { DotName dotName = binding.getName(); - ClassInfo annotationClass = getClassByName(this.beanArchiveIndex, dotName); + ClassInfo annotationClass = getClassByName(getBeanArchiveIndex(), dotName); if (annotationClass != null) { Set nonbinding = new HashSet<>(); for (MethodInfo method : annotationClass.methods()) { @@ -177,7 +179,7 @@ public class BeanDeployment { interceptorBindings.put(dotName, annotationClass); } } - repeatingInterceptorBindingAnnotations = findContainerAnnotations(interceptorBindings, this.beanArchiveIndex); + repeatingInterceptorBindingAnnotations = findContainerAnnotations(interceptorBindings); buildContextPut(Key.INTERCEPTOR_BINDINGS.asString(), Collections.unmodifiableMap(interceptorBindings)); Set additionalStereotypes = new HashSet<>(); @@ -185,12 +187,11 @@ public class BeanDeployment { additionalStereotypes.addAll(stereotypeRegistrar.getAdditionalStereotypes()); } - this.stereotypes = findStereotypes(this.beanArchiveIndex, interceptorBindings, customContexts, additionalStereotypes, + this.stereotypes = findStereotypes(interceptorBindings, customContexts, additionalStereotypes, annotationStore); buildContextPut(Key.STEREOTYPES.asString(), Collections.unmodifiableMap(stereotypes)); this.transitiveInterceptorBindings = findTransitiveInterceptorBindings(interceptorBindings.keySet(), - this.beanArchiveIndex, new HashMap<>(), interceptorBindings, annotationStore); this.injectionPoints = new CopyOnWriteArrayList<>(); @@ -199,7 +200,7 @@ public class BeanDeployment { this.beans = new CopyOnWriteArrayList<>(); this.observers = new CopyOnWriteArrayList<>(); - this.assignabilityCheck = new AssignabilityCheck(beanArchiveIndex, applicationIndex); + this.assignabilityCheck = new AssignabilityCheck(getBeanArchiveIndex(), applicationIndex); this.beanResolver = new BeanResolverImpl(this); this.delegateInjectionPointResolver = new DelegateInjectionPointResolverImpl(this); this.interceptorResolver = new InterceptorResolver(this); @@ -517,12 +518,17 @@ public Collection getStereotypes() { } /** - * This index was used to discover components (beans, interceptors, qualifiers, etc.) and during type-safe resolution. + * Returns the index that was used during discovery and type-safe resolution. + *

+ * In general, the returned index is usually "computing" which means that it attempts to compute the information for the + * classes that were not part of the initial bean archive index. I.e. the returned index corresponds to + * {@link BeanProcessor.Builder#setComputingBeanArchiveIndex(IndexView)}. However, if the computing index was not set then + * the index set by {@link BeanProcessor.Builder#setImmutableBeanArchiveIndex(IndexView)} is used instead. * * @return the bean archive index */ public IndexView getBeanArchiveIndex() { - return beanArchiveIndex; + return beanArchiveComputingIndex != null ? beanArchiveComputingIndex : beanArchiveImmutableIndex; } /** @@ -670,9 +676,9 @@ private void buildContextPut(String key, Object value) { } } - private Map findQualifiers(IndexView index) { + private Map findQualifiers() { Map qualifiers = new HashMap<>(); - for (AnnotationInstance qualifier : index.getAnnotations(DotNames.QUALIFIER)) { + for (AnnotationInstance qualifier : beanArchiveImmutableIndex.getAnnotations(DotNames.QUALIFIER)) { ClassInfo qualifierClass = qualifier.target().asClass(); if (isExcluded(qualifierClass)) { continue; @@ -682,23 +688,23 @@ private Map findQualifiers(IndexView index) { return qualifiers; } - private Map findContainerAnnotations(Map annotations, IndexView index) { + private Map findContainerAnnotations(Map annotations) { Map containerAnnotations = new HashMap<>(); for (ClassInfo annotation : annotations.values()) { AnnotationInstance repeatableMetaAnnotation = annotation.declaredAnnotation(DotNames.REPEATABLE); if (repeatableMetaAnnotation != null) { DotName containerAnnotationName = repeatableMetaAnnotation.value().asClass().name(); - ClassInfo containerClass = getClassByName(index, containerAnnotationName); + ClassInfo containerClass = getClassByName(getBeanArchiveIndex(), containerAnnotationName); containerAnnotations.put(containerAnnotationName, containerClass); } } return containerAnnotations; } - private Map findInterceptorBindings(IndexView index) { + private Map findInterceptorBindings() { Map bindings = new HashMap<>(); // Note: doesn't use AnnotationStore, this will operate on classes without applying annotation transformers - for (AnnotationInstance binding : index.getAnnotations(DotNames.INTERCEPTOR_BINDING)) { + for (AnnotationInstance binding : beanArchiveImmutableIndex.getAnnotations(DotNames.INTERCEPTOR_BINDING)) { ClassInfo bindingClass = binding.target().asClass(); if (isExcluded(bindingClass)) { continue; @@ -709,7 +715,6 @@ private Map findInterceptorBindings(IndexView index) { } private static Map> findTransitiveInterceptorBindings(Collection initialBindings, - IndexView index, Map> result, Map interceptorBindings, AnnotationStore annotationStore) { // for all known interceptor bindings @@ -748,20 +753,20 @@ private static Set recursiveBuild(DotName name, return result; } - private Map findStereotypes(IndexView index, Map interceptorBindings, + private Map findStereotypes(Map interceptorBindings, Map> customContexts, Set additionalStereotypes, AnnotationStore annotationStore) { Map stereotypes = new HashMap<>(); Set stereotypeNames = new HashSet<>(); - for (AnnotationInstance annotation : index.getAnnotations(DotNames.STEREOTYPE)) { + for (AnnotationInstance annotation : beanArchiveImmutableIndex.getAnnotations(DotNames.STEREOTYPE)) { stereotypeNames.add(annotation.target().asClass().name()); } stereotypeNames.addAll(additionalStereotypes); for (DotName stereotypeName : stereotypeNames) { - ClassInfo stereotypeClass = getClassByName(index, stereotypeName); + ClassInfo stereotypeClass = getClassByName(getBeanArchiveIndex(), stereotypeName); if (stereotypeClass != null && !isExcluded(stereotypeClass)) { boolean isAlternative = false; @@ -852,7 +857,8 @@ private List findBeans(Collection beanDefiningAnnotations, Li .map(StereotypeInfo::getName) .collect(Collectors.toSet()); - for (ClassInfo beanClass : beanArchiveIndex.getKnownClasses()) { + // If needed use the specialized immutable index to discover beans + for (ClassInfo beanClass : beanArchiveImmutableIndex.getKnownClasses()) { if (Modifier.isInterface(beanClass.flags()) || Modifier.isAbstract(beanClass.flags()) || beanClass.isAnnotation() || beanClass.isEnum()) { @@ -987,7 +993,7 @@ private List findBeans(Collection beanDefiningAnnotations, Li } DotName superType = aClass.superName(); aClass = superType != null && !superType.equals(DotNames.OBJECT) - ? getClassByName(beanArchiveIndex, superType) + ? getClassByName(getBeanArchiveIndex(), superType) : null; } for (FieldInfo field : beanClass.fields()) { @@ -1233,7 +1239,7 @@ static void processErrors(List errors) { private List findInterceptors(List injectionPoints) { Map interceptorClasses = new HashMap<>(); - for (AnnotationInstance annotation : beanArchiveIndex.getAnnotations(DotNames.INTERCEPTOR)) { + for (AnnotationInstance annotation : beanArchiveImmutableIndex.getAnnotations(DotNames.INTERCEPTOR)) { if (Kind.CLASS.equals(annotation.target().kind())) { interceptorClasses.put(annotation.target().asClass().name(), annotation.target().asClass()); } @@ -1260,7 +1266,7 @@ private List findInterceptors(List injectio private List findDecorators(List injectionPoints) { Map decoratorClasses = new HashMap<>(); - for (AnnotationInstance annotation : beanArchiveIndex.getAnnotations(DotNames.DECORATOR)) { + for (AnnotationInstance annotation : beanArchiveImmutableIndex.getAnnotations(DotNames.DECORATOR)) { if (Kind.CLASS.equals(annotation.target().kind())) { decoratorClasses.put(annotation.target().asClass().name(), annotation.target().asClass()); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index b869031c5461b..4427cfe4bea47 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -91,7 +91,10 @@ private BeanProcessor(Builder builder) { this.applicationClassPredicate = builder.applicationClassPredicate; this.name = builder.name; this.output = builder.output; - this.annotationLiterals = new AnnotationLiteralProcessor(builder.beanArchiveIndex, applicationClassPredicate); + this.annotationLiterals = new AnnotationLiteralProcessor( + builder.beanArchiveComputingIndex != null ? builder.beanArchiveComputingIndex + : builder.beanArchiveImmutableIndex, + applicationClassPredicate); this.generateSources = builder.generateSources; this.allowMocking = builder.allowMocking; this.transformUnproxyableClasses = builder.transformUnproxyableClasses; @@ -99,7 +102,9 @@ private BeanProcessor(Builder builder) { // Initialize all build processors buildContext = new BuildContextImpl(); - buildContext.putInternal(Key.INDEX.asString(), builder.beanArchiveIndex); + buildContext.putInternal(Key.INDEX.asString(), + builder.beanArchiveComputingIndex != null ? builder.beanArchiveComputingIndex + : builder.beanArchiveImmutableIndex); this.beanRegistrars = initAndSort(builder.beanRegistrars, buildContext); this.observerRegistrars = initAndSort(builder.observerRegistrars, buildContext); @@ -440,7 +445,8 @@ public Predicate getInjectionPointAnnotationsPredicate() { public static class Builder { String name; - IndexView beanArchiveIndex; + IndexView beanArchiveComputingIndex; + IndexView beanArchiveImmutableIndex; IndexView applicationIndex; Collection additionalBeanDefiningAnnotations; ResourceOutput output; @@ -510,14 +516,33 @@ public Builder setName(String name) { } /** - * Set the bean archive index. This index is mandatory and is used to discover components (beans, interceptors, - * qualifiers, etc.) and during type-safe resolution. + * Set the computing bean archive index. This index is optional and can be used for example during type-safe resolution. + * If it's not set then the immutable index is used instead. + *

+ * The computing index must be built on top of the immutable index and compute only the classes that are not part of the + * immutable index. + *

+ * This index is never used to discover components (beans, observers, etc.). + * + * @param index + * @return self + * @see Builder#setImmutableBeanArchiveIndex(IndexView) + */ + public Builder setComputingBeanArchiveIndex(IndexView index) { + this.beanArchiveComputingIndex = index; + return this; + } + + /** + * Set the immutable bean archive index. This index is mandatory and is used to discover components (beans, observers, + * etc.). * - * @param beanArchiveIndex + * @param index * @return self + * @see Builder#setComputingBeanArchiveIndex(IndexView) */ - public Builder setBeanArchiveIndex(IndexView beanArchiveIndex) { - this.beanArchiveIndex = beanArchiveIndex; + public Builder setImmutableBeanArchiveIndex(IndexView index) { + this.beanArchiveImmutableIndex = index; return this; } @@ -526,11 +551,11 @@ public Builder setBeanArchiveIndex(IndexView beanArchiveIndex) { *

* Some types may not be part of the bean archive index but are still needed during type-safe resolution. * - * @param applicationIndex + * @param index * @return self */ - public Builder setApplicationIndex(IndexView applicationIndex) { - this.applicationIndex = applicationIndex; + public Builder setApplicationIndex(IndexView index) { + this.applicationIndex = index; return this; } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java index 981ae74c5bd4c..ff22bbd568845 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java @@ -42,7 +42,7 @@ public void testInjections() throws IOException { Type listStringType = ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null); - BeanDeployment deployment = BeanProcessor.builder().setBeanArchiveIndex(index).build().getBeanDeployment(); + BeanDeployment deployment = BeanProcessor.builder().setImmutableBeanArchiveIndex(index).build().getBeanDeployment(); deployment.registerCustomContexts(Collections.emptyList()); deployment.registerBeans(Collections.emptyList()); BeanInfo barBean = deployment.getBeans().stream().filter(b -> b.getTarget().get().equals(barClass)).findFirst().get(); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java index 6e5203478b4a4..3c95a2c557f76 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java @@ -37,7 +37,7 @@ public void testQualifiers() throws IOException { ClassInfo fooClass = index.getClassByName(fooName); BeanInfo bean = Beans.createClassBean(fooClass, - BeanProcessor.builder().setBeanArchiveIndex(index).build().getBeanDeployment(), + BeanProcessor.builder().setImmutableBeanArchiveIndex(index).build().getBeanDeployment(), null); AnnotationInstance requiredFooQualifier = index.getAnnotations(fooQualifierName).stream() diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java index 75dd27ee2e3e8..bc53674cd78a9 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java @@ -37,7 +37,7 @@ public void testResolver() throws IOException { Collection.class, List.class, Iterable.class, Object.class, String.class); - BeanDeployment deployment = BeanProcessor.builder().setBeanArchiveIndex(index).build().getBeanDeployment(); + BeanDeployment deployment = BeanProcessor.builder().setImmutableBeanArchiveIndex(index).build().getBeanDeployment(); DotName fooName = name(Foo.class); ClassInfo fooClass = index.getClassByName(fooName); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index b6bcc93594732..fbe3e19e240f9 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -32,7 +32,8 @@ public void testGetTypeClosure() throws IOException { DotName producerName = DotName.createSimple(Producer.class.getName()); ClassInfo fooClass = index.getClassByName(fooName); Map> resolvedTypeVariables = new HashMap<>(); - BeanDeployment dummyDeployment = BeanProcessor.builder().setBeanArchiveIndex(index).build().getBeanDeployment(); + BeanDeployment dummyDeployment = BeanProcessor.builder().setImmutableBeanArchiveIndex(index).build() + .getBeanDeployment(); // Baz, Foo, Object Set bazTypes = Types.getTypeClosure(index.getClassByName(bazName), null, diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java index 6d428b83335a6..fda3d072e7158 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java @@ -20,6 +20,7 @@ import org.jboss.jandex.DotName; import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; import org.jboss.jandex.Indexer; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -322,14 +323,14 @@ private ClassLoader init(ExtensionContext context) { Arc.shutdown(); // Build index - Index beanArchiveIndex; + IndexView immutableBeanArchiveIndex; try { - beanArchiveIndex = index(beanClasses); + immutableBeanArchiveIndex = BeanArchives.buildImmutableBeanArchiveIndex(index(beanClasses)); } catch (IOException e) { throw new IllegalStateException("Failed to create index", e); } - Index applicationIndex; + IndexView applicationIndex; if (additionalClasses.isEmpty()) { applicationIndex = null; } else { @@ -370,8 +371,9 @@ private ClassLoader init(ExtensionContext context) { BeanProcessor.Builder builder = BeanProcessor.builder() .setName(testClass.getSimpleName()) - .setBeanArchiveIndex(BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), - new ConcurrentHashMap<>(), beanArchiveIndex)) + .setImmutableBeanArchiveIndex(immutableBeanArchiveIndex) + .setComputingBeanArchiveIndex(BeanArchives.buildComputingBeanArchiveIndex(getClass().getClassLoader(), + new ConcurrentHashMap<>(), immutableBeanArchiveIndex)) .setApplicationIndex(applicationIndex); if (!resourceAnnotations.isEmpty()) { builder.addResourceAnnotations(resourceAnnotations.stream() From 20c883c25cfd123c1089530f1783351190e178f2 Mon Sep 17 00:00:00 2001 From: glefloch Date: Mon, 5 Dec 2022 15:26:49 +0100 Subject: [PATCH 22/28] Allow using Gradle addExtension task without property file (cherry picked from commit 1c16ec12e22dcfe7a311aa1ed02cbce2a73dbb37) --- .../buildfile/AbstractGradleBuildFile.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java index dba8d5bd5a341..dd5ea2931ce1e 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java @@ -46,16 +46,20 @@ public AbstractGradleBuildFile(final Path projectDirPath, final ExtensionCatalog public void writeToDisk() throws IOException { if (rootProjectPath != null) { Files.write(rootProjectPath.resolve(getSettingsGradlePath()), getModel().getRootSettingsContent().getBytes()); - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - getModel().getRootPropertiesContent().store(out, "Gradle properties"); - Files.write(rootProjectPath.resolve(GRADLE_PROPERTIES_PATH), - out.toByteArray()); + if (hasRootProjectFile(GRADLE_PROPERTIES_PATH)) { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + getModel().getRootPropertiesContent().store(out, "Gradle properties"); + Files.write(rootProjectPath.resolve(GRADLE_PROPERTIES_PATH), + out.toByteArray()); + } } } else { writeToProjectFile(getSettingsGradlePath(), getModel().getSettingsContent().getBytes()); - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - getModel().getPropertiesContent().store(out, "Gradle properties"); - writeToProjectFile(GRADLE_PROPERTIES_PATH, out.toByteArray()); + if (hasProjectFile(GRADLE_PROPERTIES_PATH)) { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + getModel().getPropertiesContent().store(out, "Gradle properties"); + writeToProjectFile(GRADLE_PROPERTIES_PATH, out.toByteArray()); + } } } writeToProjectFile(getBuildGradlePath(), getModel().getBuildContent().getBytes()); From b7eb75e25bf1cca3aba1b2217b198058657f920d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 6 Dec 2022 10:12:29 +0200 Subject: [PATCH 23/28] Polish Kubernetes Service Binding (cherry picked from commit 2c3865c7f1f7b70a1b48e9de062a06e235ee1f72) --- .../DatasourceServiceBindingConfigSourceFactory.java | 2 +- .../KubernetesServiceBindingConfigSourceProvider.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java index c667b637c9590..055d10de9d221 100644 --- a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java +++ b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java @@ -63,7 +63,7 @@ private Map getServiceBindingProperties() { } properties.put(urlPropertyName, formatUrl(urlFormat, serviceBinding.getType(), host, database, portPart)); } else { - log.debugf("One or more of 'host' or 'database' properties were not found for datasource of type %s", + log.warnf("One or more of 'host' or 'database' properties were not found for datasource of type %s", serviceBinding.getType()); } diff --git a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/KubernetesServiceBindingConfigSourceProvider.java b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/KubernetesServiceBindingConfigSourceProvider.java index 32e4708d73fb9..337bff813c3b2 100644 --- a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/KubernetesServiceBindingConfigSourceProvider.java +++ b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/KubernetesServiceBindingConfigSourceProvider.java @@ -33,7 +33,7 @@ public KubernetesServiceBindingConfigSourceProvider(String bindingRoot) { this.serviceBindingConverters = serviceBindingConverters; Path p = Paths.get(bindingRoot); if (!Files.exists(p)) { - log.warn("Service Binding root '" + p.toAbsolutePath().toString() + "' does not exist"); + log.warn("Service Binding root '" + p.toAbsolutePath() + "' does not exist"); serviceBindings = Collections.emptyList(); return; } @@ -43,7 +43,7 @@ public KubernetesServiceBindingConfigSourceProvider(String bindingRoot) { File[] files = p.toFile().listFiles(); if (files == null) { - log.warn("Service Binding root '" + p.toAbsolutePath().toString() + "' does not contain any sub-directories"); + log.warn("Service Binding root '" + p.toAbsolutePath() + "' does not contain any sub-directories"); serviceBindings = Collections.emptyList(); } else { log.debug("Found " + files.length + " potential Service Binding directories"); @@ -54,10 +54,10 @@ public KubernetesServiceBindingConfigSourceProvider(String bindingRoot) { if (log.isDebugEnabled()) { log.debug(String.format("Directory '%s' contains %d %s and will be used as Service Binding %s", f.toPath().toAbsolutePath(), sb.getProperties().size(), - sb.getProperties().size() == 1 ? "property" : "properties", sb.toString())); + sb.getProperties().size() == 1 ? "property" : "properties", sb)); } } - serviceBindings.sort(new Comparator() { + serviceBindings.sort(new Comparator<>() { @Override public int compare(ServiceBinding o1, ServiceBinding o2) { if (!o1.getName().equals(o2.getName())) { From 613b58a8696998cfb8925fc4238f98a52849f06e Mon Sep 17 00:00:00 2001 From: pablo gonzalez granados Date: Mon, 5 Dec 2022 17:45:54 +0100 Subject: [PATCH 24/28] Fix wrong extension reference on WebauthN documentation (cherry picked from commit 9f7ee4e5555cc1efb7eaae2d6b9b33f645b79a95) --- docs/src/main/asciidoc/security-webauthn.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/security-webauthn.adoc b/docs/src/main/asciidoc/security-webauthn.adoc index 4d199cc126d7f..5c21349b3614f 100644 --- a/docs/src/main/asciidoc/security-webauthn.adoc +++ b/docs/src/main/asciidoc/security-webauthn.adoc @@ -87,7 +87,7 @@ The solution is located in the `security-webauthn-quickstart` {quickstarts-tree- First, we need a new project. Create a new project with the following command: :create-app-artifact-id: security-webauthn-quickstart -:create-app-extensions: security-webauthn,reactive-pg-client,resteasy-reactive,hibernate-reactive-panache,test-security-webauthn +:create-app-extensions: security-webauthn,reactive-pg-client,resteasy-reactive,hibernate-reactive-panache include::{includes}/devtools/create-app.adoc[] [NOTE] @@ -1059,7 +1059,7 @@ Quarkus WebAuthn endpoints to defer those calls to the worker pool. == Testing WebAuthn Testing WebAuthn can be complicated because normally you need a hardware token, which is why we've made the -`quarkus-test-security-webauthn` extension: +`quarkus-test-security-webauthn` helper library: [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml From e39cee311d3e6992143e229b2cc0a1d472efdf3b Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Sun, 4 Dec 2022 20:14:51 +0000 Subject: [PATCH 25/28] Update CORS Access-Control-Allow-Headers test (cherry picked from commit b3edfef44da798bb2a858b9948a52594635459da) --- .../java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java | 4 ++-- .../deployment/src/test/resources/conf/cors-config.properties | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java index d099842238f66..839d63e15e20b 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java @@ -22,7 +22,7 @@ public class CORSHandlerTestCase { public void corsPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; String methods = "GET,POST"; - String headers = "X-Custom"; + String headers = "X-Custom, content-type"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) .header("Access-Control-Request-Headers", headers) @@ -40,7 +40,7 @@ public void corsPreflightTestServlet() { public void corsNoPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; String methods = "GET,POST"; - String headers = "X-Custom"; + String headers = "x-custom, CONTENT-TYPE"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) .header("Access-Control-Request-Headers", headers) diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties index d9a2687cc383d..f202ef1064bf6 100644 --- a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties +++ b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties @@ -2,3 +2,4 @@ quarkus.http.cors=true # whitespaces added to test that they are not taken into account config is parsed quarkus.http.cors.methods=GET, OPTIONS, POST quarkus.http.cors.access-control-allow-credentials=true +quarkus.http.cors.access-control-allow-headers=x-custom,CONTENT-TYPE From 1ae066675b70af0bc2db0fcda0fc6f1129581c66 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Mon, 5 Dec 2022 14:08:11 +0000 Subject: [PATCH 26/28] Return allowed CORS headers in the letter case they were submitted in (cherry picked from commit bd521fc1a2b7f83dea384202a18740ec4f07753d) --- .../vertx/http/cors/CORSHandlerTestCase.java | 22 +++++++++++++++++-- .../resources/conf/cors-config.properties | 2 +- .../vertx/http/runtime/cors/CORSFilter.java | 13 ++++++----- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java index 839d63e15e20b..404627d4a32c3 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/cors/CORSHandlerTestCase.java @@ -1,6 +1,7 @@ package io.quarkus.vertx.http.cors; import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; import org.junit.jupiter.api.DisplayName; @@ -22,7 +23,7 @@ public class CORSHandlerTestCase { public void corsPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; String methods = "GET,POST"; - String headers = "X-Custom, content-type"; + String headers = "X-Custom,content-type"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) .header("Access-Control-Request-Headers", headers) @@ -35,12 +36,29 @@ public void corsPreflightTestServlet() { .header("Access-Control-Allow-Headers", headers); } + @Test + public void corsPreflightTestUnmatchedHeader() { + String origin = "http://custom.origin.quarkus"; + String methods = "GET,POST"; + String headers = "X-Customs,content-types"; + given().header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Headers", headers) + .when() + .options("/test").then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Credentials", "true") + .header("Access-Control-Allow-Headers", nullValue()); + } + @Test @DisplayName("Handles a direct CORS request correctly") public void corsNoPreflightTestServlet() { String origin = "http://custom.origin.quarkus"; String methods = "GET,POST"; - String headers = "x-custom, CONTENT-TYPE"; + String headers = "x-custom,CONTENT-TYPE"; given().header("Origin", origin) .header("Access-Control-Request-Method", methods) .header("Access-Control-Request-Headers", headers) diff --git a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties index f202ef1064bf6..59bdec217dfd2 100644 --- a/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties +++ b/extensions/vertx-http/deployment/src/test/resources/conf/cors-config.properties @@ -2,4 +2,4 @@ quarkus.http.cors=true # whitespaces added to test that they are not taken into account config is parsed quarkus.http.cors.methods=GET, OPTIONS, POST quarkus.http.cors.access-control-allow-credentials=true -quarkus.http.cors.access-control-allow-headers=x-custom,CONTENT-TYPE +quarkus.http.cors.headers=x-custom,CONTENT-TYPE diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java index 4a7fee1a14e4f..c5b476789e188 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/cors/CORSFilter.java @@ -2,7 +2,9 @@ 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.regex.Pattern; @@ -98,24 +100,25 @@ private void processRequestedHeaders(HttpServerResponse response, String allowHe if (isConfiguredWithWildcard(corsConfig.headers)) { response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeadersValue); } else { - List requestedHeaders; + Map requestedHeaders; String[] allowedParts = COMMA_SEPARATED_SPLIT_REGEX.split(allowHeadersValue); - requestedHeaders = new ArrayList<>(allowedParts.length); + requestedHeaders = new HashMap<>(); for (String requestedHeader : allowedParts) { - requestedHeaders.add(requestedHeader.toLowerCase()); + requestedHeaders.put(requestedHeader.toLowerCase(), requestedHeader); } List corsConfigHeaders = corsConfig.headers.get(); StringBuilder allowedHeaders = new StringBuilder(); boolean isFirst = true; for (String configHeader : corsConfigHeaders) { - if (requestedHeaders.contains(configHeader.toLowerCase())) { + String configHeaderLowerCase = configHeader.toLowerCase(); + if (requestedHeaders.containsKey(configHeaderLowerCase)) { if (isFirst) { isFirst = false; } else { allowedHeaders.append(','); } - allowedHeaders.append(configHeader); + allowedHeaders.append(requestedHeaders.get(configHeaderLowerCase)); } } From bb7a01f76e6a34db6a371e26dd32a5a69c05c533 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 6 Dec 2022 12:38:36 +0200 Subject: [PATCH 27/28] Introduce support for the `uri` file in k8s service binding According to https://github.com/servicebinding/spec#workload-projection, `uri` is part of the standard (cherry picked from commit c205494e02bc5d5a7cede08227bde5cf26ea2138) --- .../DatasourceServiceBindingConfigSourceFactory.java | 11 ++++++++--- .../src/test/resources/k8s-sb/fruit-db/database | 1 - .../src/test/resources/k8s-sb/fruit-db/host | 1 - .../src/test/resources/k8s-sb/fruit-db/port | 1 - .../src/test/resources/k8s-sb/fruit-db/uri | 1 + .../kubernetes-with-auto-postgres-binding.properties | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) delete mode 100644 integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/database delete mode 100644 integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/host delete mode 100644 integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/port create mode 100644 integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/uri diff --git a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java index 055d10de9d221..26f6f31dd35ef 100644 --- a/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java +++ b/extensions/kubernetes-service-binding/runtime/src/main/java/io/quarkus/kubernetes/service/binding/runtime/DatasourceServiceBindingConfigSourceFactory.java @@ -47,9 +47,14 @@ private Map getServiceBindingProperties() { log.debugf("Property 'password' was not found for datasource of type %s", serviceBinding.getType()); } - String url = bindingProperties.get("jdbc-url"); - if (url != null) { - properties.put(urlPropertyName, url); + String jdbcURL = bindingProperties.get("jdbc-url"); + if (jdbcURL != null) { + properties.put(urlPropertyName, jdbcURL); + return properties; + } + String uri = bindingProperties.get("uri"); + if (uri != null) { + properties.put(urlPropertyName, uri); return properties; } diff --git a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/database b/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/database deleted file mode 100644 index 39cd31f5ffa44..0000000000000 --- a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/database +++ /dev/null @@ -1 +0,0 @@ -quarkus_test diff --git a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/host b/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/host deleted file mode 100644 index 2fbb50c4a8dc7..0000000000000 --- a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/host +++ /dev/null @@ -1 +0,0 @@ -localhost diff --git a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/port b/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/port deleted file mode 100644 index 7e30bed39582f..0000000000000 --- a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/port +++ /dev/null @@ -1 +0,0 @@ -5431 diff --git a/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/uri b/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/uri new file mode 100644 index 0000000000000..02dca2d9bc469 --- /dev/null +++ b/integration-tests/kubernetes-service-binding-jdbc/src/test/resources/k8s-sb/fruit-db/uri @@ -0,0 +1 @@ +jdbc:postgresql://localhost:5431/quarkus_test diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-auto-postgres-binding.properties b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-auto-postgres-binding.properties index 3f62f1265363d..ee2a4ec049780 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-auto-postgres-binding.properties +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/kubernetes-with-auto-postgres-binding.properties @@ -1 +1 @@ -quarkus.datasource.db-kind=postgresql \ No newline at end of file +quarkus.datasource.db-kind=postgresql From 15c75c587277f7833f2e83a4d6ef23b150effa6f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 6 Dec 2022 14:41:59 +0100 Subject: [PATCH 28/28] Fix ClientProxyGenerator - make sure that an interface method of an interface-based client proxy is invoked upon the provider type and not the type that declares the method - fixes #29593 (cherry picked from commit 4d51083875e174105e2a53139f240b22e62814c8) --- .../arc/processor/ClientProxyGenerator.java | 7 +++++- .../packageprivate/BaseInterface.java | 7 ++++++ .../packageprivate/MyInterface.java | 5 ++++ ...ackagePrivateInterfaceInHierarchyTest.java | 24 +++++++++++++++++++ .../packageprivate/foo/MyInterface2.java | 7 ++++++ .../packageprivate/foo/Producer.java | 20 ++++++++++++++++ 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/BaseInterface.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/MyInterface.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/PackagePrivateInterfaceInHierarchyTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/MyInterface2.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/Producer.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java index a3c63d605ddf7..a0e5fa6f38592 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java @@ -205,7 +205,12 @@ Collection generate(BeanInfo bean, String beanClassName, // Always use invokevirtual and the original descriptor for java.lang.Object#toString() ret = forward.invokeVirtualMethod(originalMethodDescriptor, delegate, params); } else if (isInterface) { - ret = forward.invokeInterfaceMethod(method, delegate, params); + // make sure we invoke the method upon the provider type, i.e. don't use the original method descriptor + MethodDescriptor virtualMethod = MethodDescriptor.ofMethod(providerType.className(), + originalMethodDescriptor.getName(), + originalMethodDescriptor.getReturnType(), + originalMethodDescriptor.getParameterTypes()); + ret = forward.invokeInterfaceMethod(virtualMethod, delegate, params); } else if (isReflectionFallbackNeeded(method, targetPackage)) { // Reflection fallback ResultHandle paramTypesArray = forward.newArray(Class.class, forward.load(method.parametersCount())); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/BaseInterface.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/BaseInterface.java new file mode 100644 index 0000000000000..c1ea1982b7adf --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/BaseInterface.java @@ -0,0 +1,7 @@ +package io.quarkus.arc.test.clientproxy.packageprivate; + +// this interface is intentionally package-private +interface BaseInterface { + + String ping(); +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/MyInterface.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/MyInterface.java new file mode 100644 index 0000000000000..7d87e859a45cd --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/MyInterface.java @@ -0,0 +1,5 @@ +package io.quarkus.arc.test.clientproxy.packageprivate; + +public interface MyInterface extends BaseInterface { + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/PackagePrivateInterfaceInHierarchyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/PackagePrivateInterfaceInHierarchyTest.java new file mode 100644 index 0000000000000..fd3ad09ca61df --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/PackagePrivateInterfaceInHierarchyTest.java @@ -0,0 +1,24 @@ +package io.quarkus.arc.test.clientproxy.packageprivate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import io.quarkus.arc.test.clientproxy.packageprivate.foo.MyInterface2; +import io.quarkus.arc.test.clientproxy.packageprivate.foo.Producer; + +public class PackagePrivateInterfaceInHierarchyTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(BaseInterface.class, MyInterface.class, MyInterface2.class, + Producer.class); + + @Test + public void testProducer() { + assertEquals("quarkus", Arc.container().instance(MyInterface2.class).get().ping()); + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/MyInterface2.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/MyInterface2.java new file mode 100644 index 0000000000000..f5fa6bd00d3f4 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/MyInterface2.java @@ -0,0 +1,7 @@ +package io.quarkus.arc.test.clientproxy.packageprivate.foo; + +import io.quarkus.arc.test.clientproxy.packageprivate.MyInterface; + +public interface MyInterface2 extends MyInterface { + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/Producer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/Producer.java new file mode 100644 index 0000000000000..3301df337c4c8 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/packageprivate/foo/Producer.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.test.clientproxy.packageprivate.foo; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +@ApplicationScoped +public class Producer { + + @Produces + @ApplicationScoped + public MyInterface2 myInterface2() { + return new MyInterface2() { + @Override + public String ping() { + return "quarkus"; + } + }; + } + +}