diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java index 0260ddb493d89..e5ed3637c6d39 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java @@ -68,6 +68,8 @@ public interface Capability { String RESTEASY_REACTIVE_JSON_JACKSON = RESTEASY_REACTIVE_JSON + ".jackson"; String RESTEASY_REACTIVE_JSON_JSONB = RESTEASY_REACTIVE_JSON + ".jsonb"; + String RESTEASY_MULTIPART = RESTEASY + ".multipart"; + String JWT = QUARKUS_PREFIX + ".jwt"; /** diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 41f376657d05d..15d58e6c94f71 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -519,14 +519,12 @@ private static void configureApplicationModelTask(Project project, QuarkusApplic task.getProjectDescriptor().set(projectDescriptor); task.getLaunchMode().set(launchMode); task.getOriginalClasspath().setFrom(classpath.getOriginalRuntimeClasspathAsInput()); - task.getAppClasspath().configureFrom(classpath.getRuntimeConfiguration()); + task.getAppClasspath().configureFrom(classpath.getRuntimeConfigurationWithoutResolvingDeployment()); task.getPlatformConfiguration().configureFrom(classpath.getPlatformConfiguration()); task.getDeploymentClasspath().configureFrom(classpath.getDeploymentConfiguration()); - task.getPlatformImportProperties().set(classpath.getPlatformImports().getPlatformProperties()); - task.getApplicationModel().set( - project.getLayout().getBuildDirectory() - .file(quarkusModelFile)); - + task.getDeploymentResolvedWorkaround().from(classpath.getDeploymentConfiguration().getIncoming().getFiles()); + task.getPlatformImportProperties().set(classpath.getPlatformImportsWithoutResolvingPlatform().getPlatformProperties()); + task.getApplicationModel().set(project.getLayout().getBuildDirectory().file(quarkusModelFile)); } private static void configureQuarkusBuildTask(Project project, QuarkusPluginExtension quarkusExt, QuarkusBuildTask task, diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java index 3fc1afa5533e1..0532cddf38cf4 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusApplicationModelTask.java @@ -48,6 +48,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.CompileClasspath; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.OutputFile; @@ -107,6 +108,9 @@ public abstract class QuarkusApplicationModelTask extends DefaultTask { @CompileClasspath public abstract ConfigurableFileCollection getOriginalClasspath(); + @InputFiles + public abstract ConfigurableFileCollection getDeploymentResolvedWorkaround(); + @Nested public abstract QuarkusResolvedClasspath getPlatformConfiguration(); diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java index ec5fb1bf5f1e8..70c195738f6bd 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ApplicationDeploymentClasspathBuilder.java @@ -288,6 +288,10 @@ public Configuration getRuntimeConfiguration() { return project.getConfigurations().getByName(this.runtimeConfigurationName); } + public Configuration getRuntimeConfigurationWithoutResolvingDeployment() { + return project.getConfigurations().getByName(this.runtimeConfigurationName); + } + public Configuration getDeploymentConfiguration() { return project.getConfigurations().getByName(this.deploymentConfigurationName); } @@ -308,6 +312,10 @@ public PlatformImports getPlatformImports() { return platformImports.get(this.platformImportName); } + public PlatformImports getPlatformImportsWithoutResolvingPlatform() { + return platformImports.get(this.platformImportName); + } + private Set> collectFirstMetQuarkusExtensions(Configuration configuration, Collection> knownExtensions) { diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 02672f4e528ea..aaafe90d46114 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -113,7 +113,7 @@ quarkus.index-dependency..artifact-id=(this one is optional) quarkus.index-dependency..classifier=(this one is optional) ---- -TIP: If no `artifact-id` is specified then all dependencies with the specificed `group-id` are indexed. +TIP: If no `artifact-id` is specified then all dependencies with the specified `group-id` are indexed. For example, the following entries ensure that the `org.acme:acme-api` dependency is indexed: diff --git a/docs/src/main/asciidoc/extension-faq.adoc b/docs/src/main/asciidoc/extension-faq.adoc new file mode 100644 index 0000000000000..957c350541a2e --- /dev/null +++ b/docs/src/main/asciidoc/extension-faq.adoc @@ -0,0 +1,97 @@ +//// +This document is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// +[id="extensions-faq"] += Frequently asked questions about writing extensions +include::_attributes.adoc[] +:diataxis-type: howto +:categories: extensions +//// +:extension-status: preview +TODO: uncomment the above for experimental or tech-preview content. +The document header ends at the first blank line. Do not remove the blank line between the header and the abstract summary. +//// + +## Should you write an extension? + +### Why would I want to write an extension? + +See the xref:writing-extensions#extension-philosophy[extension philosophy]. + +One useful thing extensions can do is bundle other extensions. +Have a look at the link:https://quarkus.io/extensions/io.quarkiverse.microprofile/quarkus-microprofile/[Quarkus MicroProfile extension] for an example of aggregator extensions. + +### Are there cases an extension isn't necessary? + +Not every problem needs an extension! + If you're just bundling up external libraries (that aren't already extensions) and making minor adjustments, you might not need an extension. + For example, plain libraries can create new configuration elements and register classes with Jandex (this link:https://www.loicmathieu.fr/wordpress/en/informatique/quarkus-tip-comment-ne-pas-creer-une-extension-quarkus/[blog shows how]). + + +## Bytecode transformation + +### How can I change the code of things on the classpath? + +A `BytecodeTransformerBuildItem` can be used to manipulate bytecode. +For example, see this link:https://quarkus.io/blog/solving-problems-with-extensions/[blog about removed problematic bridge methods from a dependency]. + +## CDI + +### I'm working with CDI, and I don't know how to ... + +The xref:cdi-integration.adoc[CDI integration guide] presents solutions to a number of CDI-related use cases for extension authors. + +### I have transformed a user class to add an injected field, but CDI isn't working + +What happens if an extension transforms a user class using `BytecodeTransformerBuildItem`, and replaces `@jakarta.annotation.Resource` with `@jakarta.inject.Inject`? The field will not be injected by Arc. +Debugging will show the transformed class being loaded in the app, but it looks like Arc doesn't see the new code. + +Arc-related transformations should generally be done with link:https://github.com/quarkusio/quarkus/blob/main/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AnnotationsTransformerBuildItem.java[AnnotationsTransformerBuildItem]. +The reason is that _all_ Quarkus's bytecode transformations are done after Jandex indexing. This means changes are never reflected back in Jandex. + +Most extensions use Jandex as a source of truth to find out what to do. Those extensions won't see new/modified endpoints in the bytecode itself. +The solution to this limitation is annotation transformers. You should also be aware that while Arc and Quarkus REST honour annotation transformers, not all extensions do. + +### Something in my classpath has @Inject annotations, which are confusing CDI. How can I fix that? + +You will need to implement an `AnnotationsTransformer` and strip out out the problematic injection sites. (Remember, if the use case involves CDI, it needs to be an `AnnotationsTransformer`, not a BytecodeTransformer`.) See link:https://quarkus.io/blog/solving-problems-with-extensions-2/[this blog] about on using an `AnnotationsTransformer` extension to clean non `@Inject` annotations from the Airline library so that it can be used in CDI-enabled runtimes. + +## Cross-cutting concerns + +### How can I redirect application logging to an external service? + +A `LogHandlerBuildItem` is a convenient way to redirect application logs. See this link:https://quarkus.io/blog/quarkus-aws-cloudwatch_extension/[worked example of an extension which directs output to AWS CloudWatch]. + +## Build and hosting infrastructure for extensions + +### Can I use Gradle to build my extension? + +Yes, but it's not the most typical pattern. +See the xref:building-my-first-extension.adoc#gradle-setup[Building Your First Extension Guide] for instructions on setting up a Gradle extension. Have a look at the link:https://quarkus.io/extensions/org.jobrunr/quarkus-jobrunr/[JobRunr extension] for an example implementation. + +### If I want my extension to be in code.quarkus.io, does it have to be in the Quarkiverse GitHub org? + +Registering an extension in the catalog is independent from where the source code is. +The link:https://hub.quarkiverse.io[quarkiverse repository] has some shortcuts to make releasing and testing extensions easier, but any extension can link:https://hub.quarkiverse.io/checklistfornewprojects/#make-your-extension-available-in-the-tooling[register into the catalog]. + +### My extension isn't showing up on extensions.quarkus.io + +Every extension in the link:https://github.com/quarkusio/quarkus-extension-catalog/tree/main/extensions[extension catalog] should appear in http://code.quarkus.io, http://extensions.quarkus.io, and the command line tools. +The web pages at http://extensions.quarkus.io are refreshed a few times a delay, so there may be a delay in new extensions showing up there. +To debug a missing extension, first: + +- Check your extension is present in link:https://central.sonatype.com/[Maven Central] +- Check the extension is included the link:https://github.com/quarkusio/quarkus-extension-catalog/tree/main/extensions[extensions catalog list] (it only needs to be included once, and future versions will be automatically detected) +- Check if the extension is listed in the http://https://registry.quarkus.io/q/swagger-ui/#/Client/get_client_extensions_all[Quarkus registry] list of all known extensions +- Check if there has been a green link:https://github.com/quarkusio/extensions/actions/workflows/build_and_publish.yml[build of the extensions site] since updating the catalog + +## Other topics + + +### What's the difference between a quickstart and a codestart? + +Both codestarts and quickstarts are designed to help users get coding quickly. +A codestarts is a generated application and a quickstart is browsable source code. +Codestarts allow the creation of customised apps, which makes them quite powerful. \ No newline at end of file diff --git a/docs/src/main/asciidoc/hibernate-reactive-panache.adoc b/docs/src/main/asciidoc/hibernate-reactive-panache.adoc index 3b542fc98833d..f129c82d5fca7 100644 --- a/docs/src/main/asciidoc/hibernate-reactive-panache.adoc +++ b/docs/src/main/asciidoc/hibernate-reactive-panache.adoc @@ -1133,7 +1133,7 @@ public class PanacheFunctionalityTest { } ---- <1> Make sure the test method is run on the Vert.x event loop. -<2> The injected `UniAsserter` agrument is used to make assertions. +<2> The injected `UniAsserter` argument is used to make assertions. == How and why we simplify Hibernate Reactive mappings diff --git a/docs/src/main/asciidoc/images/oidc-github-1.png b/docs/src/main/asciidoc/images/oidc-github-1.png index bb183b3a038c2..581f6e47f22bd 100644 Binary files a/docs/src/main/asciidoc/images/oidc-github-1.png and b/docs/src/main/asciidoc/images/oidc-github-1.png differ diff --git a/docs/src/main/asciidoc/images/oidc-github-2.png b/docs/src/main/asciidoc/images/oidc-github-2.png index 62a304bc580a8..8868c371daf01 100644 Binary files a/docs/src/main/asciidoc/images/oidc-github-2.png and b/docs/src/main/asciidoc/images/oidc-github-2.png differ diff --git a/docs/src/main/asciidoc/images/oidc-github-3.png b/docs/src/main/asciidoc/images/oidc-github-3.png index 407d184454750..ffa2e806a6af9 100644 Binary files a/docs/src/main/asciidoc/images/oidc-github-3.png and b/docs/src/main/asciidoc/images/oidc-github-3.png differ diff --git a/docs/src/main/asciidoc/security-openid-connect-providers.adoc b/docs/src/main/asciidoc/security-openid-connect-providers.adoc index e39981dbe0fa7..133f66253c0b5 100644 --- a/docs/src/main/asciidoc/security-openid-connect-providers.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-providers.adoc @@ -207,7 +207,7 @@ In order to set up OIDC for GitHub you need to create a new OAuth application in image::oidc-github-1.png[role="thumb"] -Make sure to fill in the appropriate details, but more importantly the Authorization Callback URL, set to `http://localhost:8080/_renarde/security/oidc-success` +Make sure to fill in the appropriate details, but more importantly the Authorization Callback URL, set to `http://localhost:8080/github` (if you intend to test this using the Quarkus dev mode). Now click on `Register application` and you'll be shown your application page: diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index f00618776c6ae..81c18ab151358 100644 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -17,7 +17,8 @@ Quarkus extensions add a new developer focused behavior to the core offering, an This means that metadata is only processed once at build time, which both saves on startup time, and also on memory usage as the classes etc that are used for processing are not loaded (or even present) in the runtime JVM. -NOTE: This is an in-depth documentation, see the xref:building-my-first-extension.adoc[building my first extension] if you need an introduction. +NOTE: This is an in-depth documentation, see the xref:building-my-first-extension.adoc[building my first extension] if you need an introduction, +or the xref:extension-faq.adoc[frequently asked questions]. == Extension philosophy diff --git a/extensions/resteasy-classic/resteasy-multipart/runtime/pom.xml b/extensions/resteasy-classic/resteasy-multipart/runtime/pom.xml index adb18f4005fe8..bcdb550e93cf3 100644 --- a/extensions/resteasy-classic/resteasy-multipart/runtime/pom.xml +++ b/extensions/resteasy-classic/resteasy-multipart/runtime/pom.xml @@ -59,6 +59,11 @@ io.quarkus quarkus-extension-maven-plugin + + + io.quarkus.resteasy.multipart + + maven-compiler-plugin diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/build.gradle.kts b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/build.gradle.kts new file mode 100644 index 0000000000000..adc10c534b09f --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + java + id("io.quarkus") +} + +repositories { + mavenCentral() + mavenLocal() +} + + +dependencies { + implementation("io.quarkus:quarkus-rest") + implementation("io.quarkus:quarkus-arc") + implementation(enforcedPlatform(project(":library"))) + testImplementation("io.quarkus:quarkus-junit5") + testImplementation("io.rest-assured:rest-assured") +} + +group = "org.acme" +version = "1.0.0-SNAPSHOT" + +tasks.withType { + systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") +} +tasks.withType { + options.encoding = "UTF-8" + options.compilerArgs.add("-parameters") +} + +tasks.all{} diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/gradle.properties b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/gradle.properties new file mode 100644 index 0000000000000..f15a724431c33 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/gradle.properties @@ -0,0 +1,3 @@ +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformGroupId=io.quarkus +kotlinVersion=${kotlin.version} diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/library/build.gradle.kts b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/library/build.gradle.kts new file mode 100644 index 0000000000000..28d6576a01232 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/library/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + `java-platform` +} + +val quarkusPlatformGroupId: String by project +val quarkusPlatformArtifactId: String by project +val quarkusPlatformVersion: String by project + +javaPlatform.allowDependencies() +dependencies{ + api(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")) + constraints{ + api("org.assertj:assertj-core:3.26.3") + } +} diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/settings.gradle b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/settings.gradle new file mode 100644 index 0000000000000..2ab26cc990ee6 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + includeGroup 'org.hibernate.orm' + } + } + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}" + id 'org.jetbrains.kotlin.plugin.allopen' version "${kotlinVersion}" + } +} +rootProject.name='java-platform-with-eager-resolution-project' +include(":library") diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/main/java/org/acme/GreetingResource.java b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/main/java/org/acme/GreetingResource.java new file mode 100644 index 0000000000000..244f294265375 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/main/java/org/acme/GreetingResource.java @@ -0,0 +1,16 @@ +package org.acme; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "Hello from Quarkus REST"; + } +} diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/main/resources/application.properties b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/main/resources/application.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/native-test/java/org/acme/GreetingResourceIT.java b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/native-test/java/org/acme/GreetingResourceIT.java new file mode 100644 index 0000000000000..cfa9d1b1aff2b --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/native-test/java/org/acme/GreetingResourceIT.java @@ -0,0 +1,8 @@ +package org.acme; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class GreetingResourceIT extends GreetingResourceTest { + // Execute the same tests but in packaged mode. +} diff --git a/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/test/java/org/acme/GreetingResourceTest.java b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/test/java/org/acme/GreetingResourceTest.java new file mode 100644 index 0000000000000..18332e4c7ad2f --- /dev/null +++ b/integration-tests/gradle/src/main/resources/java-platform-with-eager-resolution-project/src/test/java/org/acme/GreetingResourceTest.java @@ -0,0 +1,20 @@ +package org.acme; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +class GreetingResourceTest { + @Test + void testHelloEndpoint() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(is("Hello from Quarkus REST")); + } + +} \ No newline at end of file diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/JavaPlatformWithEagerResolutionTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JavaPlatformWithEagerResolutionTest.java new file mode 100644 index 0000000000000..3d85446455867 --- /dev/null +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/JavaPlatformWithEagerResolutionTest.java @@ -0,0 +1,26 @@ +package io.quarkus.gradle; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +public class JavaPlatformWithEagerResolutionTest extends QuarkusGradleWrapperTestBase { + + @Test + public void shouldImportConditionalDependency() throws IOException, URISyntaxException, InterruptedException { + + final File projectDir = getProjectDir("java-platform-with-eager-resolution-project"); + + runGradleWrapper(projectDir, "clean", ":quarkusBuild"); + + final File buildDir = new File(projectDir, "build"); + + final Path quarkusOutput = buildDir.toPath().resolve("quarkus-app"); + assertThat(quarkusOutput.resolve("quarkus-run.jar")).exists(); + } +}